Buckets:
ktongue/docker_container / simsite /frontend /node_modules /three /examples /jsm /transpiler /GLSLDecoder.js
| 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', 'vec2 gl_FragCoord = vec2( viewportCoordinate.x, viewportCoordinate.y.oneMinus() );' ); | |
| } | |
| 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; |
Xet Storage Details
- Size:
- 17.1 kB
- Xet hash:
- 7f2b10f8a87c2544172ec37ea4a931554e5b78d25a39fa99870fbbf315a8c48e
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.