942 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			942 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								import { Program, FunctionDeclaration, For, AccessorElements, Ternary, Varying, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, String, FunctionCall, Return, Accessor, Uniform } from './AST.js';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const unaryOperators = [
							 | 
						||
| 
								 | 
							
									'+', '-', '~', '!', '++', '--'
							 | 
						||
| 
								 | 
							
								];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const precedenceOperators = [
							 | 
						||
| 
								 | 
							
									'*', '/', '%',
							 | 
						||
| 
								 | 
							
									'-', '+',
							 | 
						||
| 
								 | 
							
									'<<', '>>',
							 | 
						||
| 
								 | 
							
									'<', '>', '<=', '>=',
							 | 
						||
| 
								 | 
							
									'==', '!=',
							 | 
						||
| 
								 | 
							
									'&',
							 | 
						||
| 
								 | 
							
									'^',
							 | 
						||
| 
								 | 
							
									'|',
							 | 
						||
| 
								 | 
							
									'&&',
							 | 
						||
| 
								 | 
							
									'^^',
							 | 
						||
| 
								 | 
							
									'||',
							 | 
						||
| 
								 | 
							
									'?',
							 | 
						||
| 
								 | 
							
									'=',
							 | 
						||
| 
								 | 
							
									'+=', '-=', '*=', '/=', '%=', '^=', '&=', '|=', '<<=', '>>=',
							 | 
						||
| 
								 | 
							
									','
							 | 
						||
| 
								 | 
							
								].reverse();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const spaceRegExp = /^((\t| )\n*)+/;
							 | 
						||
| 
								 | 
							
								const lineRegExp = /^\n+/;
							 | 
						||
| 
								 | 
							
								const commentRegExp = /^\/\*[\s\S]*?\*\//;
							 | 
						||
| 
								 | 
							
								const inlineCommentRegExp = /^\/\/.*?(\n|$)/;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const numberRegExp = /^((0x\w+)|(\.?\d+\.?\d*((e-?\d+)|\w)?))/;
							 | 
						||
| 
								 | 
							
								const stringDoubleRegExp = /^(\"((?:[^"\\]|\\.)*)\")/;
							 | 
						||
| 
								 | 
							
								const stringSingleRegExp = /^(\'((?:[^'\\]|\\.)*)\')/;
							 | 
						||
| 
								 | 
							
								const literalRegExp = /^[A-Za-z](\w|\.)*/;
							 | 
						||
| 
								 | 
							
								const operatorsRegExp = new RegExp( '^(\\' + [
							 | 
						||
| 
								 | 
							
									'<<=', '>>=', '++', '--', '<<', '>>', '+=', '-=', '*=', '/=', '%=', '&=', '^^', '^=', '|=',
							 | 
						||
| 
								 | 
							
									'<=', '>=', '==', '!=', '&&', '||',
							 | 
						||
| 
								 | 
							
									'(', ')', '[', ']', '{', '}',
							 | 
						||
| 
								 | 
							
									'.', ',', ';', '!', '=', '~', '*', '/', '%', '+', '-', '<', '>', '&', '^', '|', '?', ':', '#'
							 | 
						||
| 
								 | 
							
								].join( '$' ).split( '' ).join( '\\' ).replace( /\\\$/g, '|' ) + ')' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getGroupDelta( str ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if ( str === '(' || str === '[' || str === '{' ) return 1;
							 | 
						||
| 
								 | 
							
									if ( str === ')' || str === ']' || str === '}' ) return - 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Token {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									constructor( tokenizer, type, str, pos ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.tokenizer = tokenizer;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.type = type;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.str = str;
							 | 
						||
| 
								 | 
							
										this.pos = pos;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.tag = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									get endPos() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.pos + this.str.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									get isNumber() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.type === Token.NUMBER;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									get isString() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.type === Token.STRING;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									get isLiteral() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.type === Token.LITERAL;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									get isOperator() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.type === Token.OPERATOR;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Token.LINE = 'line';
							 | 
						||
| 
								 | 
							
								Token.COMMENT = 'comment';
							 | 
						||
| 
								 | 
							
								Token.NUMBER = 'number';
							 | 
						||
| 
								 | 
							
								Token.STRING = 'string';
							 | 
						||
| 
								 | 
							
								Token.LITERAL = 'literal';
							 | 
						||
| 
								 | 
							
								Token.OPERATOR = 'operator';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const TokenParserList = [
							 | 
						||
| 
								 | 
							
									{ type: Token.LINE, regexp: lineRegExp, isTag: true },
							 | 
						||
| 
								 | 
							
									{ type: Token.COMMENT, regexp: commentRegExp, isTag: true },
							 | 
						||
| 
								 | 
							
									{ type: Token.COMMENT, regexp: inlineCommentRegExp, isTag: true },
							 | 
						||
| 
								 | 
							
									{ type: Token.NUMBER, regexp: numberRegExp },
							 | 
						||
| 
								 | 
							
									{ type: Token.STRING, regexp: stringDoubleRegExp, group: 2 },
							 | 
						||
| 
								 | 
							
									{ type: Token.STRING, regexp: stringSingleRegExp, group: 2 },
							 | 
						||
| 
								 | 
							
									{ type: Token.LITERAL, regexp: literalRegExp },
							 | 
						||
| 
								 | 
							
									{ type: Token.OPERATOR, regexp: operatorsRegExp }
							 | 
						||
| 
								 | 
							
								];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Tokenizer {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									constructor( source ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.source = source;
							 | 
						||
| 
								 | 
							
										this.position = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.tokens = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									tokenize() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let token = this.readToken();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while ( token ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.tokens.push( token );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											token = this.readToken();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									skip( ...params ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let remainingCode = this.source.substr( this.position );
							 | 
						||
| 
								 | 
							
										let i = params.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while ( i -- ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const skip = params[ i ].exec( remainingCode );
							 | 
						||
| 
								 | 
							
											const skipLength = skip ? skip[ 0 ].length : 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( skipLength > 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.position += skipLength;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												remainingCode = this.source.substr( this.position );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												// re-skip, new remainingCode is generated
							 | 
						||
| 
								 | 
							
												// maybe exist previous regexp non detected
							 | 
						||
| 
								 | 
							
												i = params.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return remainingCode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									readToken() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const remainingCode = this.skip( spaceRegExp );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( var i = 0; i < TokenParserList.length; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const parser = TokenParserList[ i ];
							 | 
						||
| 
								 | 
							
											const result = parser.regexp.exec( remainingCode );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( result ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const token = new Token( this, parser.type, result[ parser.group || 0 ], this.position );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.position += result[ 0 ].length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( parser.isTag ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const nextToken = this.readToken();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													if ( nextToken ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														nextToken.tag = token;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return nextToken;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												return token;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const isType = ( str ) => /void|bool|float|u?int|(u|i)?vec[234]/.test( str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class GLSLDecoder {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									constructor() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.index = 0;
							 | 
						||
| 
								 | 
							
										this.tokenizer = null;
							 | 
						||
| 
								 | 
							
										this.keywords = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this._currentFunction = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.addPolyfill( 'gl_FragCoord', 'vec3 gl_FragCoord = vec3( viewportCoordinate.x, viewportCoordinate.y.oneMinus(), viewportCoordinate.z );' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									addPolyfill( name, polyfill ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.keywords.push( { name, polyfill } );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									get tokens() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.tokenizer.tokens;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									readToken() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.tokens[ this.index ++ ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									getToken( offset = 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.tokens[ this.index + offset ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									getTokensUntil( str, tokens, offset = 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const output = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let groupIndex = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( let i = offset; i < tokens.length; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const token = tokens[ i ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											groupIndex += getGroupDelta( token.str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											output.push( token );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( groupIndex === 0 && token.str === str ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return output;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									readTokensUntil( str ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const tokens = this.getTokensUntil( str, this.tokens, this.index );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.index += tokens.length;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return tokens;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseExpressionFromTokens( tokens ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( tokens.length === 0 ) return null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const firstToken = tokens[ 0 ];
							 | 
						||
| 
								 | 
							
										const lastToken = tokens[ tokens.length - 1 ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// precedence operators
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let groupIndex = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( const operator of precedenceOperators ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for ( let i = 0; i < tokens.length; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const token = tokens[ i ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												groupIndex += getGroupDelta( token.str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( ! token.isOperator || i === 0 || i === tokens.length - 1 ) continue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( groupIndex === 0 && token.str === operator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													if ( operator === '?' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														const conditionTokens = tokens.slice( 0, i );
							 | 
						||
| 
								 | 
							
														const leftTokens = this.getTokensUntil( ':', tokens, i + 1 ).slice( 0, - 1 );
							 | 
						||
| 
								 | 
							
														const rightTokens = tokens.slice( i + leftTokens.length + 2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														const condition = this.parseExpressionFromTokens( conditionTokens );
							 | 
						||
| 
								 | 
							
														const left = this.parseExpressionFromTokens( leftTokens );
							 | 
						||
| 
								 | 
							
														const right = this.parseExpressionFromTokens( rightTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														return new Ternary( condition, left, right );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														const left = this.parseExpressionFromTokens( tokens.slice( 0, i ) );
							 | 
						||
| 
								 | 
							
														const right = this.parseExpressionFromTokens( tokens.slice( i + 1, tokens.length ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														return this._evalOperator( new Operator( operator, left, right ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( groupIndex < 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return this.parseExpressionFromTokens( tokens.slice( 0, i ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// unary operators (before)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( firstToken.isOperator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for ( const operator of unaryOperators ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( firstToken.str === operator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const right = this.parseExpressionFromTokens( tokens.slice( 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return new Unary( operator, right );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// unary operators (after)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( lastToken.isOperator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for ( const operator of unaryOperators ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( lastToken.str === operator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const left = this.parseExpressionFromTokens( tokens.slice( 0, tokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return new Unary( operator, left, true );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// groups
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( firstToken.str === '(' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const leftTokens = this.getTokensUntil( ')', tokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const left = this.parseExpressionFromTokens( leftTokens.slice( 1, leftTokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const operator = tokens[ leftTokens.length ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( operator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const rightTokens = tokens.slice( leftTokens.length + 1 );
							 | 
						||
| 
								 | 
							
												const right = this.parseExpressionFromTokens( rightTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												return this._evalOperator( new Operator( operator.str, left, right ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											return left;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// primitives and accessors
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( firstToken.isNumber ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											let type;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const isHex = /^(0x)/.test( firstToken.str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( isHex ) type = 'int';
							 | 
						||
| 
								 | 
							
											else if ( /u$/.test( firstToken.str ) ) type = 'uint';
							 | 
						||
| 
								 | 
							
											else if ( /f|e|\./.test( firstToken.str ) ) type = 'float';
							 | 
						||
| 
								 | 
							
											else type = 'int';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											let str = firstToken.str.replace( /u|i$/, '' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( isHex === false ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												str = str.replace( /f$/, '' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											return new Number( str, type );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else if ( firstToken.isString ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											return new String( firstToken.str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else if ( firstToken.isLiteral ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( firstToken.str === 'return' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												return new Return( this.parseExpressionFromTokens( tokens.slice( 1 ) ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const secondToken = tokens[ 1 ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( secondToken ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( secondToken.str === '(' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													// function call
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const paramsTokens = this.parseFunctionParametersFromTokens( tokens.slice( 2, tokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return new FunctionCall( firstToken.str, paramsTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( secondToken.str === '[' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													// array accessor
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													const elements = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													let currentTokens = tokens.slice( 1 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													while ( currentTokens.length > 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														const token = currentTokens[ 0 ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														if ( token.str === '[' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															const accessorTokens = this.getTokensUntil( ']', currentTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															const element = this.parseExpressionFromTokens( accessorTokens.slice( 1, accessorTokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															currentTokens = currentTokens.slice( accessorTokens.length );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															elements.push( new DynamicElement( element ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														} else if ( token.str === '.' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															const accessorTokens = currentTokens.slice( 1, 2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															const element = this.parseExpressionFromTokens( accessorTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															currentTokens = currentTokens.slice( 2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															elements.push( new StaticElement( element ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															console.error( 'Unknown accessor expression', token );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
															break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return new AccessorElements( firstToken.str, elements );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											return new Accessor( firstToken.str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseFunctionParametersFromTokens( tokens ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( tokens.length === 0 ) return [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const expression = this.parseExpressionFromTokens( tokens );
							 | 
						||
| 
								 | 
							
										const params = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let current = expression;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while ( current.type === ',' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											params.push( current.left );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											current = current.right;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										params.push( current );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return params;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseExpression() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const tokens = this.readTokensUntil( ';' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const exp = this.parseExpressionFromTokens( tokens.slice( 0, tokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return exp;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseFunctionParams( tokens ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const params = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( let i = 0; i < tokens.length; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const immutable = tokens[ i ].str === 'const';
							 | 
						||
| 
								 | 
							
											if ( immutable ) i ++;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											let qualifier = tokens[ i ].str;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( /^(in|out|inout)$/.test( qualifier ) ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												i ++;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												qualifier = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const type = tokens[ i ++ ].str;
							 | 
						||
| 
								 | 
							
											const name = tokens[ i ++ ].str;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											params.push( new FunctionParameter( type, name, qualifier, immutable ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( tokens[ i ] && tokens[ i ].str !== ',' ) throw new Error( 'Expected ","' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return params;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseFunction() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const type = this.readToken().str;
							 | 
						||
| 
								 | 
							
										const name = this.readToken().str;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const paramsTokens = this.readTokensUntil( ')' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const params = this.parseFunctionParams( paramsTokens.slice( 1, paramsTokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const func = new FunctionDeclaration( type, name, params );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this._currentFunction = func;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.parseBlock( func );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this._currentFunction = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return func;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseVariablesFromToken( tokens, type ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let index = 0;
							 | 
						||
| 
								 | 
							
										const immutable = tokens[ 0 ].str === 'const';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( immutable ) index ++;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										type = type || tokens[ index ++ ].str;
							 | 
						||
| 
								 | 
							
										const name = tokens[ index ++ ].str;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const token = tokens[ index ];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let init = null;
							 | 
						||
| 
								 | 
							
										let next = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( token ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const initTokens = this.getTokensUntil( ',', tokens, index );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( initTokens[ 0 ].str === '=' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												const expressionTokens = initTokens.slice( 1 );
							 | 
						||
| 
								 | 
							
												if ( expressionTokens[ expressionTokens.length - 1 ].str === ',' ) expressionTokens.pop();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												init = this.parseExpressionFromTokens( expressionTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const nextTokens = tokens.slice( initTokens.length + ( index - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( nextTokens[ 0 ] && nextTokens[ 0 ].str === ',' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												next = this.parseVariablesFromToken( nextTokens.slice( 1 ), type );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const variable = new VariableDeclaration( type, name, init, next, immutable );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return variable;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseVariables() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const tokens = this.readTokensUntil( ';' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return this.parseVariablesFromToken( tokens.slice( 0, tokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseUniform() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const tokens = this.readTokensUntil( ';' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const type = tokens[ 1 ].str;
							 | 
						||
| 
								 | 
							
										const name = tokens[ 2 ].str;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return new Uniform( type, name );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseVarying() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const tokens = this.readTokensUntil( ';' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const type = tokens[ 1 ].str;
							 | 
						||
| 
								 | 
							
										const name = tokens[ 2 ].str;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return new Varying( type, name );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseReturn() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.readToken(); // skip 'return'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const expression = this.parseExpression();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return new Return( expression );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseFor() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.readToken(); // skip 'for'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const forTokens = this.readTokensUntil( ')' ).slice( 1, - 1 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const initializationTokens = this.getTokensUntil( ';', forTokens, 0 ).slice( 0, - 1 );
							 | 
						||
| 
								 | 
							
										const conditionTokens = this.getTokensUntil( ';', forTokens, initializationTokens.length + 1 ).slice( 0, - 1 );
							 | 
						||
| 
								 | 
							
										const afterthoughtTokens = forTokens.slice( initializationTokens.length + conditionTokens.length + 2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let initialization;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( initializationTokens[ 0 ] && isType( initializationTokens[ 0 ].str ) ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											initialization = this.parseVariablesFromToken( initializationTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											initialization = this.parseExpressionFromTokens( initializationTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const condition = this.parseExpressionFromTokens( conditionTokens );
							 | 
						||
| 
								 | 
							
										const afterthought = this.parseExpressionFromTokens( afterthoughtTokens );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const statement = new For( initialization, condition, afterthought );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( this.getToken().str === '{' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.parseBlock( statement );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											statement.body.push( this.parseExpression() );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return statement;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseIf() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const parseIfExpression = () => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.readToken(); // skip 'if'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const condTokens = this.readTokensUntil( ')' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											return this.parseExpressionFromTokens( condTokens.slice( 1, condTokens.length - 1 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const parseIfBlock = ( cond ) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( this.getToken().str === '{' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.parseBlock( cond );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												cond.body.push( this.parseExpression() );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const conditional = new Conditional( parseIfExpression() );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										parseIfBlock( conditional );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let current = conditional;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while ( this.getToken() && this.getToken().str === 'else' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.readToken(); // skip 'else'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const previous = current;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( this.getToken().str === 'if' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												current = new Conditional( parseIfExpression() );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												current = new Conditional();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											previous.elseConditional = current;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											parseIfBlock( current );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return conditional;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parseBlock( scope ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const firstToken = this.getToken();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( firstToken.str === '{' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											this.readToken(); // skip '{'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let groupIndex = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while ( this.index < this.tokens.length ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const token = this.getToken();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											let statement = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											groupIndex += getGroupDelta( token.str );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( groupIndex < 0 ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.readToken(); // skip '}'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											//
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( token.isLiteral ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( token.str === 'const' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseVariables();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( token.str === 'uniform' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseUniform();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( token.str === 'varying' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseVarying();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( isType( token.str ) ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													if ( this.getToken( 2 ).str === '(' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														statement = this.parseFunction();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														statement = this.parseVariables();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( token.str === 'return' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseReturn();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( token.str === 'if' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseIf();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else if ( token.str === 'for' ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseFor();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													statement = this.parseExpression();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( statement ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												scope.body.push( statement );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.index ++;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_evalOperator( operator ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( operator.type.includes( '=' ) ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											const parameter = this._getFunctionParameter( operator.left.property );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( parameter !== undefined ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												// Parameters are immutable in WGSL
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												parameter.immutable = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return operator;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									_getFunctionParameter( name ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( this._currentFunction ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for ( const param of this._currentFunction.params ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if ( param.name === name ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													return param;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									parse( source ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										let polyfill = '';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for ( const keyword of this.keywords ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if ( new RegExp( `(^|\\b)${ keyword.name }($|\\b)`, 'gm' ).test( source ) ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												polyfill += keyword.polyfill + '\n';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if ( polyfill ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											polyfill = '// Polyfills\n\n' + polyfill + '\n';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.index = 0;
							 | 
						||
| 
								 | 
							
										this.tokenizer = new Tokenizer( polyfill + source ).tokenize();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const program = new Program();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										this.parseBlock( program );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										return program;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export default GLSLDecoder;
							 |