GLSLDecoder.js 18 KB

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