TSLEncoder.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. import * as THREE from 'three';
  2. import { VariableDeclaration, Accessor } from './AST.js';
  3. const opLib = {
  4. '=': 'assign',
  5. '+': 'add',
  6. '-': 'sub',
  7. '*': 'mul',
  8. '/': 'div',
  9. '%': 'remainder',
  10. '<': 'lessThan',
  11. '>': 'greaterThan',
  12. '<=': 'lessThanEqual',
  13. '>=': 'greaterThanEqual',
  14. '==': 'equal',
  15. '&&': 'and',
  16. '||': 'or',
  17. '^^': 'xor',
  18. '&': 'bitAnd',
  19. '|': 'bitOr',
  20. '^': 'bitXor',
  21. '<<': 'shiftLeft',
  22. '>>': 'shiftRight',
  23. '+=': 'addAssign',
  24. '-=': 'subAssign',
  25. '*=': 'mulAssign',
  26. '/=': 'divAssign',
  27. '%=': 'remainderAssign',
  28. '^=': 'bitXorAssign',
  29. '&=': 'bitAndAssign',
  30. '|=': 'bitOrAssign',
  31. '<<=': 'shiftLeftAssign',
  32. '>>=': 'shiftRightAssign'
  33. };
  34. const unaryLib = {
  35. '+': '', // positive
  36. '-': 'negate',
  37. '~': 'bitNot',
  38. '!': 'not',
  39. '++': 'increment', // incrementBefore
  40. '--': 'decrement' // decrementBefore
  41. };
  42. const isPrimitive = ( value ) => /^(true|false|-?\d)/.test( value );
  43. class TSLEncoder {
  44. constructor() {
  45. this.tab = '';
  46. this.imports = new Set();
  47. this.global = new Set();
  48. this.overloadings = new Map();
  49. this.iife = false;
  50. this.uniqueNames = false;
  51. this.reference = false;
  52. this._currentProperties = {};
  53. this._lastStatement = null;
  54. }
  55. addImport( name ) {
  56. // import only if it's a node
  57. name = name.split( '.' )[ 0 ];
  58. if ( THREE[ name ] !== undefined && this.global.has( name ) === false && this._currentProperties[ name ] === undefined ) {
  59. this.imports.add( name );
  60. }
  61. }
  62. emitUniform( node ) {
  63. let code = `const ${ node.name } = `;
  64. if ( this.reference === true ) {
  65. this.addImport( 'reference' );
  66. this.global.add( node.name );
  67. //code += `reference( '${ node.name }', '${ node.type }', uniforms )`;
  68. // legacy
  69. code += `reference( 'value', '${ node.type }', uniforms[ '${ node.name }' ] )`;
  70. } else {
  71. this.addImport( 'uniform' );
  72. this.global.add( node.name );
  73. code += `uniform( '${ node.type }' )`;
  74. }
  75. return code;
  76. }
  77. emitExpression( node ) {
  78. let code;
  79. /*@TODO: else if ( node.isVarying ) {
  80. code = this.emitVarying( node );
  81. }*/
  82. if ( node.isAccessor ) {
  83. this.addImport( node.property );
  84. code = node.property;
  85. } else if ( node.isNumber ) {
  86. if ( node.type === 'int' || node.type === 'uint' ) {
  87. code = node.type + '( ' + node.value + ' )';
  88. this.addImport( node.type );
  89. } else {
  90. code = node.value;
  91. }
  92. } else if ( node.isString ) {
  93. code = '\'' + node.value + '\'';
  94. } else if ( node.isOperator ) {
  95. const opFn = opLib[ node.type ] || node.type;
  96. const left = this.emitExpression( node.left );
  97. const right = this.emitExpression( node.right );
  98. if ( isPrimitive( left ) && isPrimitive( right ) ) {
  99. return left + ' ' + node.type + ' ' + right;
  100. }
  101. if ( isPrimitive( left ) ) {
  102. code = opFn + '( ' + left + ', ' + right + ' )';
  103. this.addImport( opFn );
  104. } else {
  105. code = left + '.' + opFn + '( ' + right + ' )';
  106. }
  107. } else if ( node.isFunctionCall ) {
  108. const params = [];
  109. for ( const parameter of node.params ) {
  110. params.push( this.emitExpression( parameter ) );
  111. }
  112. this.addImport( node.name );
  113. const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : '';
  114. code = `${ node.name }(${ paramsStr })`;
  115. } else if ( node.isReturn ) {
  116. code = 'return';
  117. if ( node.value ) {
  118. code += ' ' + this.emitExpression( node.value );
  119. }
  120. } else if ( node.isAccessorElements ) {
  121. code = node.property;
  122. for ( const element of node.elements ) {
  123. if ( element.isStaticElement ) {
  124. code += '.' + this.emitExpression( element.value );
  125. } else if ( element.isDynamicElement ) {
  126. const value = this.emitExpression( element.value );
  127. if ( isPrimitive( value ) ) {
  128. code += `[ ${ value } ]`;
  129. } else {
  130. code += `.element( ${ value } )`;
  131. }
  132. }
  133. }
  134. } else if ( node.isDynamicElement ) {
  135. code = this.emitExpression( node.value );
  136. } else if ( node.isStaticElement ) {
  137. code = this.emitExpression( node.value );
  138. } else if ( node.isFor ) {
  139. code = this.emitFor( node );
  140. } else if ( node.isVariableDeclaration ) {
  141. code = this.emitVariables( node );
  142. } else if ( node.isUniform ) {
  143. code = this.emitUniform( node );
  144. } else if ( node.isTernary ) {
  145. code = this.emitTernary( node );
  146. } else if ( node.isConditional ) {
  147. code = this.emitConditional( node );
  148. } else if ( node.isUnary && node.expression.isNumber ) {
  149. code = node.type + ' ' + node.expression.value;
  150. } else if ( node.isUnary ) {
  151. let type = unaryLib[ node.type ];
  152. if ( node.after === false && ( node.type === '++' || node.type === '--' ) ) {
  153. type += 'Before';
  154. }
  155. const exp = this.emitExpression( node.expression );
  156. if ( isPrimitive( exp ) ) {
  157. this.addImport( type );
  158. code = type + '( ' + exp + ' )';
  159. } else {
  160. code = exp + '.' + type + '()';
  161. }
  162. } else {
  163. console.warn( 'Unknown node type', node );
  164. }
  165. if ( ! code ) code = '/* unknown statement */';
  166. return code;
  167. }
  168. emitBody( body ) {
  169. this.setLastStatement( null );
  170. let code = '';
  171. this.tab += '\t';
  172. for ( const statement of body ) {
  173. code += this.emitExtraLine( statement );
  174. code += this.tab + this.emitExpression( statement );
  175. if ( code.slice( - 1 ) !== '}' ) code += ';';
  176. code += '\n';
  177. this.setLastStatement( statement );
  178. }
  179. code = code.slice( 0, - 1 ); // remove the last extra line
  180. this.tab = this.tab.slice( 0, - 1 );
  181. return code;
  182. }
  183. emitTernary( node ) {
  184. const condStr = this.emitExpression( node.cond );
  185. const leftStr = this.emitExpression( node.left );
  186. const rightStr = this.emitExpression( node.right );
  187. this.addImport( 'cond' );
  188. return `cond( ${ condStr }, ${ leftStr }, ${ rightStr } )`;
  189. }
  190. emitConditional( node ) {
  191. const condStr = this.emitExpression( node.cond );
  192. const bodyStr = this.emitBody( node.body );
  193. let ifStr = `If( ${ condStr }, () => {
  194. ${ bodyStr }
  195. ${ this.tab }} )`;
  196. let current = node;
  197. while ( current.elseConditional ) {
  198. const elseBodyStr = this.emitBody( current.elseConditional.body );
  199. if ( current.elseConditional.cond ) {
  200. const elseCondStr = this.emitExpression( current.elseConditional.cond );
  201. ifStr += `.elseif( ${ elseCondStr }, () => {
  202. ${ elseBodyStr }
  203. ${ this.tab }} )`;
  204. } else {
  205. ifStr += `.else( () => {
  206. ${ elseBodyStr }
  207. ${ this.tab }} )`;
  208. }
  209. current = current.elseConditional;
  210. }
  211. this.imports.add( 'If' );
  212. return ifStr;
  213. }
  214. emitLoop( node ) {
  215. const start = this.emitExpression( node.initialization.value );
  216. const end = this.emitExpression( node.condition.right );
  217. const name = node.initialization.name;
  218. const type = node.initialization.type;
  219. const condition = node.condition.type;
  220. const update = node.afterthought.type;
  221. const nameParam = name !== 'i' ? `, name: '${ name }'` : '';
  222. const typeParam = type !== 'int' ? `, type: '${ type }'` : '';
  223. const conditionParam = condition !== '<' ? `, condition: '${ condition }'` : '';
  224. const updateParam = update !== '++' ? `, update: '${ update }'` : '';
  225. let loopStr = `loop( { start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }, ( { ${ name } } ) => {\n\n`;
  226. loopStr += this.emitBody( node.body ) + '\n\n';
  227. loopStr += this.tab + '} )';
  228. this.imports.add( 'loop' );
  229. return loopStr;
  230. }
  231. emitFor( node ) {
  232. const { initialization, condition, afterthought } = node;
  233. if ( ( initialization && initialization.isVariableDeclaration && initialization.next === null ) &&
  234. ( condition && condition.left.isAccessor && condition.left.property === initialization.name ) &&
  235. ( afterthought && afterthought.isUnary ) &&
  236. ( initialization.name === afterthought.expression.property )
  237. ) {
  238. return this.emitLoop( node );
  239. }
  240. return this.emitForWhile( node );
  241. }
  242. emitForWhile( node ) {
  243. const initialization = this.emitExpression( node.initialization );
  244. const condition = this.emitExpression( node.condition );
  245. const afterthought = this.emitExpression( node.afterthought );
  246. this.tab += '\t';
  247. let forStr = '{\n\n' + this.tab + initialization + ';\n\n';
  248. forStr += `${ this.tab }While( ${ condition }, () => {\n\n`;
  249. forStr += this.emitBody( node.body ) + '\n\n';
  250. forStr += this.tab + '\t' + afterthought + ';\n\n';
  251. forStr += this.tab + '} )\n\n';
  252. this.tab = this.tab.slice( 0, - 1 );
  253. forStr += this.tab + '}';
  254. this.imports.add( 'While' );
  255. return forStr;
  256. }
  257. emitVariables( node, isRoot = true ) {
  258. const { name, type, value, next } = node;
  259. const valueStr = value ? this.emitExpression( value ) : '';
  260. let varStr = isRoot ? 'const ' : '';
  261. varStr += name;
  262. if ( value ) {
  263. if ( value.isFunctionCall && value.name === type ) {
  264. varStr += ' = ' + valueStr;
  265. } else {
  266. varStr += ` = ${ type }( ${ valueStr } )`;
  267. }
  268. } else {
  269. varStr += ` = ${ type }()`;
  270. }
  271. if ( node.immutable === false ) {
  272. varStr += '.toVar()';
  273. }
  274. if ( next ) {
  275. varStr += ', ' + this.emitVariables( next, false );
  276. }
  277. this.addImport( type );
  278. return varStr;
  279. }
  280. /*emitVarying( node ) { }*/
  281. emitOverloadingFunction( nodes ) {
  282. const { name } = nodes[ 0 ];
  283. this.addImport( 'overloadingFn' );
  284. const prefix = this.iife === false ? 'export ' : '';
  285. return `${ prefix }const ${ name } = /*#__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
  286. }
  287. emitFunction( node ) {
  288. const { name, type } = node;
  289. this._currentProperties = { name: node };
  290. const params = [];
  291. const inputs = [];
  292. const mutableParams = [];
  293. let hasPointer = false;
  294. for ( const param of node.params ) {
  295. let str = `{ name: '${ param.name }', type: '${ param.type }'`;
  296. let name = param.name;
  297. if ( param.immutable === false && ( param.qualifier !== 'inout' && param.qualifier !== 'out' ) ) {
  298. name = name + '_immutable';
  299. mutableParams.push( param );
  300. }
  301. if ( param.qualifier ) {
  302. if ( param.qualifier === 'inout' || param.qualifier === 'out' ) {
  303. hasPointer = true;
  304. }
  305. str += ', qualifier: \'' + param.qualifier + '\'';
  306. }
  307. inputs.push( str + ' }' );
  308. params.push( name );
  309. this._currentProperties[ name ] = param;
  310. }
  311. for ( const param of mutableParams ) {
  312. node.body.unshift( new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ) ) );
  313. }
  314. const paramsStr = params.length > 0 ? ' [ ' + params.join( ', ' ) + ' ] ' : '';
  315. const bodyStr = this.emitBody( node.body );
  316. let fnName = name;
  317. let overloadingNodes = null;
  318. if ( this.overloadings.has( name ) ) {
  319. const overloadings = this.overloadings.get( name );
  320. if ( overloadings.length > 1 ) {
  321. const index = overloadings.indexOf( node );
  322. fnName += '_' + index;
  323. if ( index === overloadings.length - 1 ) {
  324. overloadingNodes = overloadings;
  325. }
  326. }
  327. }
  328. const prefix = this.iife === false ? 'export ' : '';
  329. let funcStr = `${ prefix }const ${ fnName } = /*#__PURE__*/ tslFn( (${ paramsStr }) => {
  330. ${ bodyStr }
  331. ${ this.tab }} )`;
  332. const layoutInput = inputs.length > 0 ? '\n\t\t' + this.tab + inputs.join( ',\n\t\t' + this.tab ) + '\n\t' + this.tab : '';
  333. if ( node.layout !== false && hasPointer === false ) {
  334. const uniqueName = this.uniqueNames ? fnName + '_' + Math.random().toString( 36 ).slice( 2 ) : fnName;
  335. funcStr += `.setLayout( {
  336. ${ this.tab }\tname: '${ uniqueName }',
  337. ${ this.tab }\ttype: '${ type }',
  338. ${ this.tab }\tinputs: [${ layoutInput }]
  339. ${ this.tab }} )`;
  340. }
  341. funcStr += ';\n';
  342. this.imports.add( 'tslFn' );
  343. this.global.add( node.name );
  344. if ( overloadingNodes !== null ) {
  345. funcStr += '\n' + this.emitOverloadingFunction( overloadingNodes );
  346. }
  347. return funcStr;
  348. }
  349. setLastStatement( statement ) {
  350. this._lastStatement = statement;
  351. }
  352. emitExtraLine( statement ) {
  353. const last = this._lastStatement;
  354. if ( last === null ) return '';
  355. if ( statement.isReturn ) return '\n';
  356. const isExpression = ( st ) => st.isFunctionDeclaration !== true && st.isFor !== true && st.isConditional !== true;
  357. const lastExp = isExpression( last );
  358. const currExp = isExpression( statement );
  359. if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
  360. return '';
  361. }
  362. emit( ast ) {
  363. let code = '\n';
  364. if ( this.iife ) this.tab += '\t';
  365. const overloadings = this.overloadings;
  366. for ( const statement of ast.body ) {
  367. if ( statement.isFunctionDeclaration ) {
  368. if ( overloadings.has( statement.name ) === false ) {
  369. overloadings.set( statement.name, [] );
  370. }
  371. overloadings.get( statement.name ).push( statement );
  372. }
  373. }
  374. for ( const statement of ast.body ) {
  375. code += this.emitExtraLine( statement );
  376. if ( statement.isFunctionDeclaration ) {
  377. code += this.tab + this.emitFunction( statement );
  378. } else {
  379. code += this.tab + this.emitExpression( statement ) + ';\n';
  380. }
  381. this.setLastStatement( statement );
  382. }
  383. const imports = [ ...this.imports ];
  384. const exports = [ ...this.global ];
  385. let header = '// Three.js Transpiler r' + THREE.REVISION + '\n\n';
  386. let footer = '';
  387. if ( this.iife ) {
  388. header += '( function ( TSL, uniforms ) {\n\n';
  389. header += imports.length > 0 ? '\tconst { ' + imports.join( ', ' ) + ' } = TSL;\n' : '';
  390. footer += exports.length > 0 ? '\treturn { ' + exports.join( ', ' ) + ' };\n' : '';
  391. footer += '\n} );';
  392. } else {
  393. header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/tsl\';\n' : '';
  394. }
  395. return header + code + footer;
  396. }
  397. }
  398. export default TSLEncoder;