@@ -28,10 +28,10 @@ npm install --save @shaderfrog/glsl-parser
2828import { parser , generate } from ' @shaderfrog/glsl-parser' ;
2929
3030// To parse a GLSL program's source code into an AST:
31- const ast = parser .parse (' float a = 1.0;' );
31+ const program = parser .parse (' float a = 1.0;' );
3232
3333// To turn a parsed AST back into a source program
34- const program = generate (ast );
34+ const transpiled = generate (program );
3535```
3636
3737The parser accepts an optional second ` options ` argument:
@@ -41,18 +41,24 @@ parser.parse('float a = 1.0;', options);
4141
4242Where ` options ` is:
4343
44- ``` js
45- {
44+ ``` typescript
45+ type ParserOptions = {
4646 // Hide warnings. If set to false or not set, then the parser logs warnings
47- // like undefined functions and variables
48- quiet: boolean,
47+ // like undefined functions and variables. If `failOnWarn` is set to true,
48+ // warnings will still cause the parser to raise an error. Defaults to false.
49+ quiet: boolean ;
4950 // The origin of the GLSL, for debugging. For example, "main.js", If the
5051 // parser raises an error (specifically a GrammarError), and you call
51- // error.format([]) on it, the error shows { source: 'main.js', ... }
52- grammarSource: string,
52+ // error.format([]) on it, the error shows { source: 'main.js', ... }.
53+ // Defaults to null.
54+ grammarSource: string ;
5355 // If true, sets location information on each AST node, in the form of
54- // { column: number, line: number, offset: number }
55- includeLocation: boolean
56+ // { column: number, line: number, offset: number }. Defaults to false.
57+ includeLocation: boolean ;
58+ // If true, causes the parser to raise an error instead of log a warning.
59+ // The parser does limited type checking, and things like undeclared variables
60+ // are treated as warnings. Defaults to false.
61+ failOnWarn: boolean ;
5662}
5763` ` `
5864
@@ -76,8 +82,8 @@ console.log(preprocess(`
7682
7783Where ` options ` is:
7884
79- ``` js
80- {
85+ ``` typescript
86+ type PreprocessorOptions = {
8187 // Don't strip comments before preprocessing
8288 preserveComments: boolean ,
8389 // Macro definitions to use when preprocessing
@@ -109,16 +115,98 @@ import {
109115const commentsRemoved = preprocessComments (` float a = 1.0; ` )
110116
111117// Parse the source text into an AST
112- const ast = parser .parse (commentsRemoved);
118+ const program = parser .parse (commentsRemoved );
113119
114120// Then preproces it, expanding #defines, evaluating #ifs, etc
115- preprocessAst (ast );
121+ preprocessAst (program );
116122
117123// Then convert it back into a program string, which can be passed to the
118124// core glsl parser
119- const preprocessed = preprocessorGenerate (ast );
125+ const preprocessed = preprocessorGenerate (program );
120126```
121127
128+ ## Scope
129+
130+ ` parse() ` returns a [ ` Program ` ] , which has a ` scopes ` array on it. A scope looks
131+ like:
132+ ``` typescript
133+ type Scope = {
134+ name: string ;
135+ parent? : Scope ;
136+ bindings: ScopeIndex ;
137+ types: TypeScopeIndex ;
138+ functions: FunctionScopeIndex ;
139+ location? : LocationObject ;
140+ }
141+ ` ` `
142+
143+ The ` name ` of a scope is either ` " global" ` , the name of the function that
144+ introduced the scope, or in anonymous blocks, ` " {" ` . In each scope, ` bindings ` represents variables,
145+ ` types ` represents user-created types (structs in GLSL), and ` functions ` represents
146+ functions.
147+
148+ For ` bindings ` and ` types ` , the scope index looks like:
149+ ` ` ` typescript
150+ type ScopeIndex = {
151+ [name : string ]: {
152+ declaration? : AstNode ;
153+ references: AstNode [];
154+ }
155+ }
156+ ` ` `
157+
158+ Where ` name ` is the name of the variable or type. ` declaration ` is the AST node
159+ where the variable was declared. In the case the variable is used without being
160+ declared, ` declaration ` won't be present. If you set the [ ` failOnWarn ` parser
161+ option](#Parsing) to ` true ` , the parser will throw an error when encountering
162+ an undeclared variable, rather than allow a scope entry without a declaration.
163+
164+ For ` functions ` , the scope index is slighty different:
165+ ` ` ` typescript
166+ type FunctionScopeIndex = {
167+ [name : string ]: {
168+ [signature : string ]: {
169+ returnType: string ;
170+ parameterTypes: string [];
171+ declaration? : FunctionNode ;
172+ references: AstNode [];
173+ }
174+ }
175+ };
176+ ```
177+
178+ Where ` name ` is the name of the function, and ` signature ` is a string representing
179+ the function's return and parameter types, in the form of ` "returnType: paramType1, paramType2, ..." `
180+ or ` "returnType: void" ` in the case of no arguments. Each ` signature ` in this
181+ index represents an "overloaded" function in GLSL, as in:
182+
183+ ``` glsl
184+ void someFunction(int x) {};
185+ void someFunction(int x, int y) {};
186+ ```
187+
188+ With this source code, there will be two entries under ` name ` , one for each
189+ overload signature. The ` references ` are the uses of that specific overloaded
190+ version of the function. ` references ` also contains the function prototypes
191+ for the overloaded function, if present.
192+
193+ In the case there is only one declaration for a function, there will still be
194+ a single entry under ` name ` with the function's ` signature ` .
195+
196+ ⚠️ Caution! This parser does very limited type checking. This leads to a known
197+ case where a function call can match to the wrong overload in scope:
198+
199+ ``` glsl
200+ void someFunction(float, float);
201+ void someFunction(bool, bool);
202+ someFunction(true, true); // This will be attributed to the wrong scope entry
203+ ```
204+
205+ The parser doesn't know the type of the operands in the function call, so it
206+ matches based on the name and arity of the functions.
207+
208+ See also [ #Utility-Functions] for renaming scope references.
209+
122210## Manipulating and Searching ASTs
123211
124212### Visitors
@@ -283,7 +371,17 @@ and `#extension` have no effect, and can be fully preserved as part of parsing.
283371
284372# Local Development
285373
286- To run the tests (and do other things), you must first build the parser files
287- using Peggy. Run ` ./build.sh ` to generate these files.
288-
289374To work on the tests, run ` npx jest --watch ` .
375+
376+ The GLSL grammar definition lives in ` src/parser/glsl-grammar.pegjs ` . Peggyjs
377+ supports inlining Javascript code in the ` .pegjs ` file to define utility
378+ functions, but that means you have to write in vanilla Javascript, which is
379+ terrible. Instead, I've pulled out utility functions into the ` grammar.ts `
380+ entrypoint. Some functions need access to Peggy's local variables, like
381+ ` location(s) ` , so the ` makeLocals() ` function uses a closure to provide that
382+ access.
383+
384+ To submit a change, please open a pull request. Tests are appreciated!
385+
386+ See [ the Github workflow] ( .github/workflows/main.yml ) for the checks run against
387+ each PR.
0 commit comments