TSLEncoder.js 12 KB

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