AST JSON
The bwsl.ast.v3 schema printed by bwslc -ast-json for editors and structural tooling.
Turn the guide into code
Take the key idea from this page into the playground and validate it in a real shader instead of leaving it as theory.
Open PlaygroundRun bwslc source.bwsl -ast-json to print the parsed AST as JSON with schema: "bwsl.ast.v3", then exit without writing shader outputs. Use it for IDEs, code browsers, documentation extraction and other structural tooling.
The top-level object includes schema, sourceFile, roots, modules, pipelines and nodeCounts. Nodes carry id, type, index, line, column and end positions where available. Declarations preceded by /// or /** ... */ comments include a docs object with the raw text, summary, @param entries, return text and other tags.
This export also includes a referenceIndex (bwsl.references.v1). Its declaration
walk is also used to validate names during normal compilation.
Changes from v2
- Parameter, pattern-binding, and struct-field objects report their data type
as
dataTypeinstead oftype, sotypealways means a node kind. Struct fields keep the structured type astypeInfo. - Declarations carry
sourceFile(andsourceUrlfor standard-library modules), and named nodes carrynameLine/nameColumn; see below. - Module- and pipeline-level
constdeclarations are exported asVARIABLE_DECLnodes in aconstsarray.
Document roots
Use roots, an array of node IDs for modules and pipelines declared in the
scanned file, in source order. Imported modules can appear in modules but
are excluded from roots. Resolve these IDs against modules and pipelines.
The legacy root object is deprecated. It remains present for compatibility
and represents the compiler's selected pipeline, not the entire document.
Source files
Every module and pipeline entry has a sourceFile: the scanned document, the
path of an imported module file, or stdlib://modules/<File>.bwsl for an
embedded standard-library module. Standard-library entries also have a
sourceUrl pointing at that file on GitHub, pinned to the compiler's release
tag (or master for development builds).
Declarations inside them (functions, structs, enums, constants, attributes,
resources, passes, constraints) carry sourceFile too. This matters for
submodule X extends Y: members merged into Y keep the file they were
written in, and their line/column are relative to that file.
Positions
line/column is a node's primary position, which is the keyword for
MODULE/STRUCT_DECL/PIPELINE/PASS, the type for VARIABLE_DECL and
ATTRIBUTE_DECL, the . for MEMBER_ACCESS and method calls, and the ::
for qualified calls. Every named node also has nameLine/nameColumn, the
first character of its name (for PASS, of the name inside the quotes).
Positions are one-based.
Declaration and occurrence metadata
- Function parameters include
id,name,dataType,line,column,typeLine,typeColumn,nameLine, andnameColumn. Anonymous parameters omit name positions (line/columnis then the type); synthesized parameters may have no source positions. - Struct fields include
id(matching their reference-index symbol IDs),name,dataType,typeInfo, and the same position fields as parameters. - Pass
fragmentOutputsentries includeid(matching the reference index'sfragment-outputsymbols) and name/type positions. - Each pass's
usedAttributesentry includes an occurrenceid,name, and position. Look up itsattributeedge in the reference index to find the declaration. importsandusingImportsentries include occurrence IDs and positions, used byimportandusingedges.usingImports[].nameis the resolved module;writtenNameis present when the source used an import alias.- Module-qualified function calls retain
moduleNameand include aqualifieridentifier node, which is the source of the module'squalifieredge. - Resource declarations include
typeLine/typeColumnfor the first token of their type. Theirtypereference targets the payload type, including insidebuffer<...>,cbuffer<...>, and fixed arrays. ResourcetypeNameuses canonicalModule::Typespelling, including when an import alias was written. - A declaration written with a
Module::Typetype (variable, parameter, resource, or struct field) has atypeQualifieridentifier object with its ownid, position, and the qualifier as written; it is the source of aqualifieredge to the module. Resource declarations also retain this occurrence for the legacyModule.Typespelling and for qualified buffer element types. - Functions and methods include
returnTypeLine/returnTypeColumn, the first token of the written return type (the source of the function'sreturn-typeedge).returnTypekeeps aModule::Typequalification, and such a return type also has areturnTypeQualifierobject, shaped liketypeQualifier, with its ownqualifieredge. - The parser folds uses of named constants into literals. Such a
LITERALnode has afoldedFromidentifier object with the written name and its position (plus aqualifiernode forModule::NAME), which is the source of areadedge to the constant's declaration. - Attribute declarations use
compression: nullwhen no compression is declared; explicit compression settings remain strings. - For-loop initializer declarations carry the same name/type positions as other variable declarations.
Parameter IDs use FUNCTION:n/parameter:i; field IDs use
STRUCT_DECL:n/field:i; attribute-use IDs use PASS:n/used-attribute:i;
using IDs use <owner>/using:i; type qualifiers use
<declaration-id>/type-qualifier (FUNCTION:n/return-type-qualifier for
return types); folded constant uses use
LITERAL:n/folded-constant. These IDs are local to an export. Externally
addressable symbols also have stableId values for cross-invocation identity.
Array and declaration types
Parameters, local/constant declarations, and struct fields include the same
typeInfo shape. Functions have returnTypeInfo with that shape. Corresponding
parameter, variable, constant, struct-field, function, and method
symbols in referenceIndex include typeInfo too (a function symbol describes
its return type).
{
"elementType": "float3",
"coreType": "FLOAT3",
"componentCount": 3,
"arrayDimensions": 2,
"arrayLength": 6,
"arraySizes": [2, 3],
"arrayStride": 0
}
This describes float3[2][3]. arraySizes preserves dimensions in source
order; arrayDimensions is their count and arrayLength is their product.
Non-arrays, including vectors and matrices, have arraySizes: [],
arrayDimensions: 0, and arrayLength: 0. Sizes are resolved integer values,
including when written using named constants. arrayStride: 0 means no storage
stride has been computed; this parsed type metadata is not a buffer layout.
dataType on parameters/fields, declaredType on locals, and type on reference
symbols keep the element type, including module qualification. An array local
therefore reports float or Light, never the placeholder array. Existing
local arrayDimensions/arrayLength and field arraySize properties remain
for compatibility; use typeInfo for a consistent representation. Field
typeInfo.arrayDimensions now describes actual arrays instead of marking
vectors as arrays. Function stable IDs include parameter dimensions.
This export describes supported syntax; it does not add array return types or change array argument/overload checking.
Reference resolution
Unqualified names resolve within their lexical scope and explicit using
imports. Locals do not escape their functions. A module qualifier or method
receiver restricts lookup to that declaration's scope. Struct identities are
preserved through parameters, variables, fields, and function returns even
when different modules declare the same short name. Bare field names in methods
respect local and parameter shadowing. Explicit self receivers resolve to
the enclosing struct even when a local or parameter shadows a field name.
self.field has a member edge (or write when assigned), and
self.method() has a call edge, just like accesses through a typed variable.
Each self identifier also has a type edge to its enclosing declaration.
Stage-interface symbols take their type from the first value assigned to
them. Intrinsic calls are typed from the standard-library table: a fixed
result type (dot gives float), otherwise the widest argument the intrinsic
returns (normalize(float2) gives float2, smoothstep(0.0, 1.0, float3)
gives float3).
The index is built from the parsed AST, before shader lowering. Missing or ambiguous targets can have no reference edge; consumers must not treat the index as proof that the source has passed semantic compilation.