GLSLDecoder.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. import { Program, FunctionDeclaration, For, AccessorElements, Ternary, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, FunctionCall, Return, Accessor } from './AST.js';
  2. const unaryOperators = [
  3. '+', '-', '~', '!', '++', '--'
  4. ];
  5. const precedenceOperators = [
  6. '*', '/', '%',
  7. '-', '+',
  8. '<<', '>>',
  9. '<', '>', '<=', '>=',
  10. '==', '!=',
  11. '&',
  12. '^',
  13. '|',
  14. '&&',
  15. '^^',
  16. '||',
  17. '?',
  18. '=',
  19. '+=', '-=', '*=', '/=', '%=', '^=', '&=', '|=', '<<=', '>>=',
  20. ','
  21. ].reverse();
  22. const spaceRegExp = /^((\t| )\n*)+/;
  23. const lineRegExp = /^\n+/;
  24. const commentRegExp = /^\/\*[\s\S]*?\*\//;
  25. const inlineCommentRegExp = /^\/\/.*?(\n|$)/;
  26. const numberRegExp = /^((0x\w+)|(\.?\d+\.?\d*((e-?\d+)|\w)?))/;
  27. const stringDoubleRegExp = /^(\"((?:[^"\\]|\\.)*)\")/;
  28. const stringSingleRegExp = /^(\'((?:[^'\\]|\\.)*)\')/;
  29. const literalRegExp = /^[A-Za-z](\w|\.)*/;
  30. const operatorsRegExp = new RegExp( '^(\\' + [
  31. '<<=', '>>=', '++', '--', '<<', '>>', '+=', '-=', '*=', '/=', '%=', '&=', '^^', '^=', '|=',
  32. '<=', '>=', '==', '!=', '&&', '||',
  33. '(', ')', '[', ']', '{', '}',
  34. '.', ',', ';', '!', '=', '~', '*', '/', '%', '+', '-', '<', '>', '&', '^', '|', '?', ':', '#'
  35. ].join( '$' ).split( '' ).join( '\\' ).replace( /\\\$/g, '|' ) + ')' );
  36. function getGroupDelta( str ) {
  37. if ( str === '(' || str === '[' || str === '{' ) return 1;
  38. if ( str === ')' || str === ']' || str === '}' ) return - 1;
  39. return 0;
  40. }
  41. class Token {
  42. constructor( tokenizer, type, str, pos ) {
  43. this.tokenizer = tokenizer;
  44. this.type = type;
  45. this.str = str;
  46. this.pos = pos;
  47. this.tag = null;
  48. }
  49. get endPos() {
  50. return this.pos + this.str.length;
  51. }
  52. get isNumber() {
  53. return this.type === Token.NUMBER;
  54. }
  55. get isString() {
  56. return this.type === Token.STRING;
  57. }
  58. get isLiteral() {
  59. return this.type === Token.LITERAL;
  60. }
  61. get isOperator() {
  62. return this.type === Token.OPERATOR;
  63. }
  64. }
  65. Token.LINE = 'line';
  66. Token.COMMENT = 'comment';
  67. Token.NUMBER = 'number';
  68. Token.STRING = 'string';
  69. Token.LITERAL = 'literal';
  70. Token.OPERATOR = 'operator';
  71. const TokenParserList = [
  72. { type: Token.LINE, regexp: lineRegExp, isTag: true },
  73. { type: Token.COMMENT, regexp: commentRegExp, isTag: true },
  74. { type: Token.COMMENT, regexp: inlineCommentRegExp, isTag: true },
  75. { type: Token.NUMBER, regexp: numberRegExp },
  76. { type: Token.STRING, regexp: stringDoubleRegExp, group: 2 },
  77. { type: Token.STRING, regexp: stringSingleRegExp, group: 2 },
  78. { type: Token.LITERAL, regexp: literalRegExp },
  79. { type: Token.OPERATOR, regexp: operatorsRegExp }
  80. ];
  81. class Tokenizer {
  82. constructor( source ) {
  83. this.source = source;
  84. this.position = 0;
  85. this.tokens = [];
  86. }
  87. tokenize() {
  88. let token = this.readToken();
  89. while ( token ) {
  90. this.tokens.push( token );
  91. token = this.readToken();
  92. }
  93. return this;
  94. }
  95. skip( ...params ) {
  96. let remainingCode = this.source.substr( this.position );
  97. let i = params.length;
  98. while ( i -- ) {
  99. const skip = params[ i ].exec( remainingCode );
  100. const skipLength = skip ? skip[ 0 ].length : 0;
  101. if ( skipLength > 0 ) {
  102. this.position += skipLength;
  103. remainingCode = this.source.substr( this.position );
  104. // re-skip, new remainingCode is generated
  105. // maybe exist previous regexp non detected
  106. i = params.length;
  107. }
  108. }
  109. return remainingCode;
  110. }
  111. readToken() {
  112. const remainingCode = this.skip( spaceRegExp );
  113. for ( var i = 0; i < TokenParserList.length; i ++ ) {
  114. const parser = TokenParserList[ i ];
  115. const result = parser.regexp.exec( remainingCode );
  116. if ( result ) {
  117. const token = new Token( this, parser.type, result[ parser.group || 0 ], this.position );
  118. this.position += token.str.length;
  119. if ( parser.isTag ) {
  120. const nextToken = this.readToken();
  121. if ( nextToken ) {
  122. nextToken.tag = token;
  123. }
  124. return nextToken;
  125. }
  126. return token;
  127. }
  128. }
  129. }
  130. }
  131. const isType = ( str ) => /void|bool|float|u?int|(u|i)?vec[234]/.test( str );
  132. class GLSLDecoder {
  133. constructor() {
  134. this.index = 0;
  135. this.tokenizer = null;
  136. this.keywords = [];
  137. this._currentFunction = null;
  138. this.addKeyword( 'gl_FragCoord', 'vec2 gl_FragCoord = vec2( viewportCoordinate.x, viewportCoordinate.y.oneMinus() );' );
  139. }
  140. addKeyword( name, polyfill ) {
  141. this.keywords.push( { name, polyfill } );
  142. return this;
  143. }
  144. get tokens() {
  145. return this.tokenizer.tokens;
  146. }
  147. readToken() {
  148. return this.tokens[ this.index ++ ];
  149. }
  150. getToken( offset = 0 ) {
  151. return this.tokens[ this.index + offset ];
  152. }
  153. getTokensUntil( str, tokens, offset = 0 ) {
  154. const output = [];
  155. let groupIndex = 0;
  156. for ( let i = offset; i < tokens.length; i ++ ) {
  157. const token = tokens[ i ];
  158. groupIndex += getGroupDelta( token.str );
  159. output.push( token );
  160. if ( groupIndex === 0 && token.str === str ) {
  161. break;
  162. }
  163. }
  164. return output;
  165. }
  166. readTokensUntil( str ) {
  167. const tokens = this.getTokensUntil( str, this.tokens, this.index );
  168. this.index += tokens.length;
  169. return tokens;
  170. }
  171. parseExpressionFromTokens( tokens ) {
  172. if ( tokens.length === 0 ) return null;
  173. const firstToken = tokens[ 0 ];
  174. const lastToken = tokens[ tokens.length - 1 ];
  175. // precedence operators
  176. let groupIndex = 0;
  177. for ( const operator of precedenceOperators ) {
  178. for ( let i = 0; i < tokens.length; i ++ ) {
  179. const token = tokens[ i ];
  180. groupIndex += getGroupDelta( token.str );
  181. if ( ! token.isOperator || i === 0 || i === tokens.length - 1 ) continue;
  182. if ( groupIndex === 0 && token.str === operator ) {
  183. if ( operator === '?' ) {
  184. const conditionTokens = tokens.slice( 0, i );
  185. const leftTokens = this.getTokensUntil( ':', tokens, i + 1 ).slice( 0, - 1 );
  186. const rightTokens = tokens.slice( i + leftTokens.length + 2 );
  187. const condition = this.parseExpressionFromTokens( conditionTokens );
  188. const left = this.parseExpressionFromTokens( leftTokens );
  189. const right = this.parseExpressionFromTokens( rightTokens );
  190. return new Ternary( condition, left, right );
  191. } else {
  192. const left = this.parseExpressionFromTokens( tokens.slice( 0, i ) );
  193. const right = this.parseExpressionFromTokens( tokens.slice( i + 1, tokens.length ) );
  194. return this._evalOperator( new Operator( operator, left, right ) );
  195. }
  196. }
  197. if ( groupIndex < 0 ) {
  198. return this.parseExpressionFromTokens( tokens.slice( 0, i ) );
  199. }
  200. }
  201. }
  202. // unary operators (before)
  203. if ( firstToken.isOperator ) {
  204. for ( const operator of unaryOperators ) {
  205. if ( firstToken.str === operator ) {
  206. const right = this.parseExpressionFromTokens( tokens.slice( 1 ) );
  207. return new Unary( operator, right );
  208. }
  209. }
  210. }
  211. // unary operators (after)
  212. if ( lastToken.isOperator ) {
  213. for ( const operator of unaryOperators ) {
  214. if ( lastToken.str === operator ) {
  215. const left = this.parseExpressionFromTokens( tokens.slice( 0, tokens.length - 1 ) );
  216. return new Unary( operator, left, true );
  217. }
  218. }
  219. }
  220. // groups
  221. if ( firstToken.str === '(' ) {
  222. const leftTokens = this.getTokensUntil( ')', tokens );
  223. const left = this.parseExpressionFromTokens( leftTokens.slice( 1, leftTokens.length - 1 ) );
  224. const operator = tokens[ leftTokens.length ];
  225. if ( operator ) {
  226. const rightTokens = tokens.slice( leftTokens.length + 1 );
  227. const right = this.parseExpressionFromTokens( rightTokens );
  228. return this._evalOperator( new Operator( operator.str, left, right ) );
  229. }
  230. return left;
  231. }
  232. // primitives and accessors
  233. if ( firstToken.isNumber ) {
  234. let type;
  235. if ( /^(0x)/.test( firstToken.str ) ) type = 'int';
  236. else if ( /u$/.test( firstToken.str ) ) type = 'uint';
  237. else if ( /f|e|\./.test( firstToken.str ) ) type = 'float';
  238. else type = 'int';
  239. const str = firstToken.str.replace( /u$/, '' );
  240. return new Number( str, type );
  241. } else if ( firstToken.isLiteral ) {
  242. if ( firstToken.str === 'return' ) {
  243. return new Return( this.parseExpressionFromTokens( tokens.slice( 1 ) ) );
  244. }
  245. const secondToken = tokens[ 1 ];
  246. if ( secondToken ) {
  247. if ( secondToken.str === '(' ) {
  248. // function call
  249. const paramsTokens = this.parseFunctionParametersFromTokens( tokens.slice( 2, tokens.length - 1 ) );
  250. return new FunctionCall( firstToken.str, paramsTokens );
  251. } else if ( secondToken.str === '[' ) {
  252. // array accessor
  253. const elements = [];
  254. let currentTokens = tokens.slice( 1 );
  255. while ( currentTokens.length > 0 ) {
  256. const token = currentTokens[ 0 ];
  257. if ( token.str === '[' ) {
  258. const accessorTokens = this.getTokensUntil( ']', currentTokens );
  259. const element = this.parseExpressionFromTokens( accessorTokens.slice( 1, accessorTokens.length - 1 ) );
  260. currentTokens = currentTokens.slice( accessorTokens.length );
  261. elements.push( new DynamicElement( element ) );
  262. } else if ( token.str === '.' ) {
  263. const accessorTokens = currentTokens.slice( 1, 2 );
  264. const element = this.parseExpressionFromTokens( accessorTokens );
  265. currentTokens = currentTokens.slice( 2 );
  266. elements.push( new StaticElement( element ) );
  267. } else {
  268. console.error( 'Unknown accessor expression', token );
  269. break;
  270. }
  271. }
  272. return new AccessorElements( firstToken.str, elements );
  273. }
  274. }
  275. return new Accessor( firstToken.str );
  276. }
  277. }
  278. parseFunctionParametersFromTokens( tokens ) {
  279. if ( tokens.length === 0 ) return [];
  280. const expression = this.parseExpressionFromTokens( tokens );
  281. const params = [];
  282. let current = expression;
  283. while ( current.type === ',' ) {
  284. params.push( current.left );
  285. current = current.right;
  286. }
  287. params.push( current );
  288. return params;
  289. }
  290. parseExpression() {
  291. const tokens = this.readTokensUntil( ';' );
  292. const exp = this.parseExpressionFromTokens( tokens.slice( 0, tokens.length - 1 ) );
  293. return exp;
  294. }
  295. parseFunctionParams( tokens ) {
  296. const params = [];
  297. for ( let i = 0; i < tokens.length; i ++ ) {
  298. const immutable = tokens[ i ].str === 'const';
  299. if ( immutable ) i ++;
  300. let qualifier = tokens[ i ].str;
  301. if ( /^(in|out|inout)$/.test( qualifier ) ) {
  302. i ++;
  303. } else {
  304. qualifier = null;
  305. }
  306. const type = tokens[ i ++ ].str;
  307. const name = tokens[ i ++ ].str;
  308. params.push( new FunctionParameter( type, name, qualifier, immutable ) );
  309. if ( tokens[ i ] && tokens[ i ].str !== ',' ) throw new Error( 'Expected ","' );
  310. }
  311. return params;
  312. }
  313. parseFunction() {
  314. const type = this.readToken().str;
  315. const name = this.readToken().str;
  316. const paramsTokens = this.readTokensUntil( ')' );
  317. const params = this.parseFunctionParams( paramsTokens.slice( 1, paramsTokens.length - 1 ) );
  318. const func = new FunctionDeclaration( type, name, params );
  319. this._currentFunction = func;
  320. this.parseBlock( func );
  321. this._currentFunction = null;
  322. return func;
  323. }
  324. parseVariablesFromToken( tokens, type ) {
  325. let index = 0;
  326. const immutable = tokens[ 0 ].str === 'const';
  327. if ( immutable ) index ++;
  328. type = type || tokens[ index ++ ].str;
  329. const name = tokens[ index ++ ].str;
  330. const token = tokens[ index ];
  331. let init = null;
  332. let next = null;
  333. if ( token ) {
  334. const initTokens = this.getTokensUntil( ',', tokens, index );
  335. if ( initTokens[ 0 ].str === '=' ) {
  336. const expressionTokens = initTokens.slice( 1 );
  337. if ( expressionTokens[ expressionTokens.length - 1 ].str === ',' ) expressionTokens.pop();
  338. init = this.parseExpressionFromTokens( expressionTokens );
  339. }
  340. const nextTokens = tokens.slice( initTokens.length + ( index - 1 ) );
  341. if ( nextTokens[ 0 ] && nextTokens[ 0 ].str === ',' ) {
  342. next = this.parseVariablesFromToken( nextTokens.slice( 1 ), type );
  343. }
  344. }
  345. const variable = new VariableDeclaration( type, name, init, next, immutable );
  346. return variable;
  347. }
  348. parseVariables() {
  349. const tokens = this.readTokensUntil( ';' );
  350. return this.parseVariablesFromToken( tokens.slice( 0, tokens.length - 1 ) );
  351. }
  352. parseReturn() {
  353. this.readToken(); // skip 'return'
  354. const expression = this.parseExpression();
  355. return new Return( expression );
  356. }
  357. parseFor() {
  358. this.readToken(); // skip 'for'
  359. const forTokens = this.readTokensUntil( ')' ).slice( 1, - 1 );
  360. const initializationTokens = this.getTokensUntil( ';', forTokens, 0 ).slice( 0, - 1 );
  361. const conditionTokens = this.getTokensUntil( ';', forTokens, initializationTokens.length + 1 ).slice( 0, - 1 );
  362. const afterthoughtTokens = forTokens.slice( initializationTokens.length + conditionTokens.length + 2 );
  363. let initialization;
  364. if ( initializationTokens[ 0 ] && isType( initializationTokens[ 0 ].str ) ) {
  365. initialization = this.parseVariablesFromToken( initializationTokens );
  366. } else {
  367. initialization = this.parseExpressionFromTokens( initializationTokens );
  368. }
  369. const condition = this.parseExpressionFromTokens( conditionTokens );
  370. const afterthought = this.parseExpressionFromTokens( afterthoughtTokens );
  371. const statement = new For( initialization, condition, afterthought );
  372. if ( this.getToken().str === '{' ) {
  373. this.parseBlock( statement );
  374. } else {
  375. statement.body.push( this.parseExpression() );
  376. }
  377. return statement;
  378. }
  379. parseIf() {
  380. const parseIfExpression = () => {
  381. this.readToken(); // skip 'if'
  382. const condTokens = this.readTokensUntil( ')' );
  383. return this.parseExpressionFromTokens( condTokens.slice( 1, condTokens.length - 1 ) );
  384. };
  385. const parseIfBlock = ( cond ) => {
  386. if ( this.getToken().str === '{' ) {
  387. this.parseBlock( cond );
  388. } else {
  389. cond.body.push( this.parseExpression() );
  390. }
  391. };
  392. //
  393. const conditional = new Conditional( parseIfExpression() );
  394. parseIfBlock( conditional );
  395. //
  396. let current = conditional;
  397. while ( this.getToken().str === 'else' ) {
  398. this.readToken(); // skip 'else'
  399. const previous = current;
  400. if ( this.getToken().str === 'if' ) {
  401. current = new Conditional( parseIfExpression() );
  402. } else {
  403. current = new Conditional();
  404. }
  405. previous.elseConditional = current;
  406. parseIfBlock( current );
  407. }
  408. return conditional;
  409. }
  410. parseBlock( scope ) {
  411. const firstToken = this.getToken();
  412. if ( firstToken.str === '{' ) {
  413. this.readToken(); // skip '{'
  414. }
  415. let groupIndex = 0;
  416. while ( this.index < this.tokens.length ) {
  417. const token = this.getToken();
  418. let statement = null;
  419. groupIndex += getGroupDelta( token.str );
  420. if ( groupIndex < 0 ) {
  421. this.readToken(); // skip '}'
  422. break;
  423. }
  424. //
  425. if ( token.isLiteral ) {
  426. if ( token.str === 'const' ) {
  427. statement = this.parseVariables();
  428. } else if ( isType( token.str ) ) {
  429. if ( this.getToken( 2 ).str === '(' ) {
  430. statement = this.parseFunction();
  431. } else {
  432. statement = this.parseVariables();
  433. }
  434. } else if ( token.str === 'return' ) {
  435. statement = this.parseReturn();
  436. } else if ( token.str === 'if' ) {
  437. statement = this.parseIf();
  438. } else if ( token.str === 'for' ) {
  439. statement = this.parseFor();
  440. } else {
  441. statement = this.parseExpression();
  442. }
  443. }
  444. if ( statement ) {
  445. scope.body.push( statement );
  446. } else {
  447. this.index ++;
  448. }
  449. }
  450. }
  451. _evalOperator( operator ) {
  452. if ( operator.type.includes( '=' ) ) {
  453. const parameter = this._getFunctionParameter( operator.left.property );
  454. if ( parameter !== undefined ) {
  455. // Parameters are immutable in WGSL
  456. parameter.immutable = false;
  457. }
  458. }
  459. return operator;
  460. }
  461. _getFunctionParameter( name ) {
  462. if ( this._currentFunction ) {
  463. for ( const param of this._currentFunction.params ) {
  464. if ( param.name === name ) {
  465. return param;
  466. }
  467. }
  468. }
  469. }
  470. parse( source ) {
  471. let polyfill = '';
  472. for ( const keyword of this.keywords ) {
  473. if ( new RegExp( `(^|\\b)${ keyword.name }($|\\b)`, 'gm' ).test( source ) ) {
  474. polyfill += keyword.polyfill + '\n';
  475. }
  476. }
  477. if ( polyfill ) {
  478. polyfill = '// Polyfills\n\n' + polyfill + '\n';
  479. }
  480. this.index = 0;
  481. this.tokenizer = new Tokenizer( polyfill + source ).tokenize();
  482. const program = new Program();
  483. this.parseBlock( program );
  484. return program;
  485. }
  486. }
  487. export default GLSLDecoder;