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;
 |