first commit
This commit is contained in:
270
public/sdk/three/jsm/transpiler/AST.js
Normal file
270
public/sdk/three/jsm/transpiler/AST.js
Normal file
@ -0,0 +1,270 @@
|
||||
export class Program {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.body = [];
|
||||
|
||||
this.isProgram = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class VariableDeclaration {
|
||||
|
||||
constructor( type, name, value = null, next = null, immutable = false ) {
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
|
||||
this.immutable = immutable;
|
||||
|
||||
this.isVariableDeclaration = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Uniform {
|
||||
|
||||
constructor( type, name ) {
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
|
||||
this.isUniform = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Varying {
|
||||
|
||||
constructor( type, name ) {
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
|
||||
this.isVarying = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FunctionParameter {
|
||||
|
||||
constructor( type, name, qualifier = null, immutable = true ) {
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.qualifier = qualifier;
|
||||
this.immutable = immutable;
|
||||
|
||||
this.isFunctionParameter = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FunctionDeclaration {
|
||||
|
||||
constructor( type, name, params = [] ) {
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
this.body = [];
|
||||
|
||||
this.isFunctionDeclaration = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Expression {
|
||||
|
||||
constructor( expression ) {
|
||||
|
||||
this.expression = expression;
|
||||
|
||||
this.isExpression = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Ternary {
|
||||
|
||||
constructor( cond, left, right ) {
|
||||
|
||||
this.cond = cond;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
|
||||
this.isTernary = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Operator {
|
||||
|
||||
constructor( type, left, right ) {
|
||||
|
||||
this.type = type;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
|
||||
this.isOperator = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Unary {
|
||||
|
||||
constructor( type, expression, after = false ) {
|
||||
|
||||
this.type = type;
|
||||
this.expression = expression;
|
||||
this.after = after;
|
||||
|
||||
this.isUnary = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Number {
|
||||
|
||||
constructor( value, type = 'float' ) {
|
||||
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
|
||||
this.isNumber = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class String {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isString = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Conditional {
|
||||
|
||||
constructor( cond = null ) {
|
||||
|
||||
this.cond = cond;
|
||||
|
||||
this.body = [];
|
||||
this.elseConditional = null;
|
||||
|
||||
this.isConditional = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class FunctionCall {
|
||||
|
||||
constructor( name, params = [] ) {
|
||||
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
|
||||
this.isFunctionCall = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Return {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isReturn = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Accessor {
|
||||
|
||||
constructor( property ) {
|
||||
|
||||
this.property = property;
|
||||
|
||||
this.isAccessor = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class StaticElement {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isStaticElement = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DynamicElement {
|
||||
|
||||
constructor( value ) {
|
||||
|
||||
this.value = value;
|
||||
|
||||
this.isDynamicElement = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class AccessorElements {
|
||||
|
||||
constructor( property, elements = [] ) {
|
||||
|
||||
this.property = property;
|
||||
this.elements = elements;
|
||||
|
||||
this.isAccessorElements = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class For {
|
||||
|
||||
constructor( initialization, condition, afterthought ) {
|
||||
|
||||
this.initialization = initialization;
|
||||
this.condition = condition;
|
||||
this.afterthought = afterthought;
|
||||
|
||||
this.body = [];
|
||||
|
||||
this.isFor = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
941
public/sdk/three/jsm/transpiler/GLSLDecoder.js
Normal file
941
public/sdk/three/jsm/transpiler/GLSLDecoder.js
Normal file
@ -0,0 +1,941 @@
|
||||
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', 'vec3 gl_FragCoord = vec3( viewportCoordinate.x, viewportCoordinate.y.oneMinus(), viewportCoordinate.z );' );
|
||||
|
||||
}
|
||||
|
||||
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;
|
49
public/sdk/three/jsm/transpiler/ShaderToyDecoder.js
Normal file
49
public/sdk/three/jsm/transpiler/ShaderToyDecoder.js
Normal file
@ -0,0 +1,49 @@
|
||||
import { Return, VariableDeclaration, Accessor } from './AST.js';
|
||||
import GLSLDecoder from './GLSLDecoder.js';
|
||||
|
||||
class ShaderToyDecoder extends GLSLDecoder {
|
||||
|
||||
constructor() {
|
||||
|
||||
super();
|
||||
|
||||
this.addPolyfill( 'iTime', 'float iTime = timerGlobal();' );
|
||||
this.addPolyfill( 'iResolution', 'vec2 iResolution = viewportResolution;' );
|
||||
this.addPolyfill( 'fragCoord', 'vec3 fragCoord = vec3( viewportCoordinate.x, viewportResolution.y - viewportCoordinate.y, viewportCoordinate.z );' );
|
||||
|
||||
}
|
||||
|
||||
parseFunction() {
|
||||
|
||||
const node = super.parseFunction();
|
||||
|
||||
if ( node.name === 'mainImage' ) {
|
||||
|
||||
node.params = []; // remove default parameters
|
||||
node.type = 'vec4';
|
||||
node.layout = false; // for now
|
||||
|
||||
const fragColor = new Accessor( 'fragColor' );
|
||||
|
||||
for ( const subNode of node.body ) {
|
||||
|
||||
if ( subNode.isReturn ) {
|
||||
|
||||
subNode.value = fragColor;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
node.body.unshift( new VariableDeclaration( 'vec4', 'fragColor' ) );
|
||||
node.body.push( new Return( fragColor ) );
|
||||
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ShaderToyDecoder;
|
715
public/sdk/three/jsm/transpiler/TSLEncoder.js
Normal file
715
public/sdk/three/jsm/transpiler/TSLEncoder.js
Normal file
@ -0,0 +1,715 @@
|
||||
import { REVISION } from 'three';
|
||||
import { VariableDeclaration, Accessor } from './AST.js';
|
||||
import * as Nodes from '../nodes/Nodes.js';
|
||||
|
||||
const opLib = {
|
||||
'=': 'assign',
|
||||
'+': 'add',
|
||||
'-': 'sub',
|
||||
'*': 'mul',
|
||||
'/': 'div',
|
||||
'%': 'remainder',
|
||||
'<': 'lessThan',
|
||||
'>': 'greaterThan',
|
||||
'<=': 'lessThanEqual',
|
||||
'>=': 'greaterThanEqual',
|
||||
'==': 'equal',
|
||||
'&&': 'and',
|
||||
'||': 'or',
|
||||
'^^': 'xor',
|
||||
'&': 'bitAnd',
|
||||
'|': 'bitOr',
|
||||
'^': 'bitXor',
|
||||
'<<': 'shiftLeft',
|
||||
'>>': 'shiftRight',
|
||||
'+=': 'addAssign',
|
||||
'-=': 'subAssign',
|
||||
'*=': 'mulAssign',
|
||||
'/=': 'divAssign',
|
||||
'%=': 'remainderAssign',
|
||||
'^=': 'bitXorAssign',
|
||||
'&=': 'bitAndAssign',
|
||||
'|=': 'bitOrAssign',
|
||||
'<<=': 'shiftLeftAssign',
|
||||
'>>=': 'shiftRightAssign'
|
||||
};
|
||||
|
||||
const unaryLib = {
|
||||
'+': '', // positive
|
||||
'-': 'negate',
|
||||
'~': 'bitNot',
|
||||
'!': 'not',
|
||||
'++': 'increment', // incrementBefore
|
||||
'--': 'decrement' // decrementBefore
|
||||
};
|
||||
|
||||
const isPrimitive = ( value ) => /^(true|false|-?\d)/.test( value );
|
||||
|
||||
class TSLEncoder {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.tab = '';
|
||||
this.imports = new Set();
|
||||
this.global = new Set();
|
||||
this.overloadings = new Map();
|
||||
this.layoutsCode = '';
|
||||
this.iife = false;
|
||||
this.uniqueNames = false;
|
||||
this.reference = false;
|
||||
|
||||
this._currentProperties = {};
|
||||
this._lastStatement = null;
|
||||
|
||||
}
|
||||
|
||||
addImport( name ) {
|
||||
|
||||
// import only if it's a node
|
||||
|
||||
name = name.split( '.' )[ 0 ];
|
||||
|
||||
if ( Nodes[ name ] !== undefined && this.global.has( name ) === false && this._currentProperties[ name ] === undefined ) {
|
||||
|
||||
this.imports.add( name );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
emitUniform( node ) {
|
||||
|
||||
let code = `const ${ node.name } = `;
|
||||
|
||||
if ( this.reference === true ) {
|
||||
|
||||
this.addImport( 'reference' );
|
||||
|
||||
this.global.add( node.name );
|
||||
|
||||
//code += `reference( '${ node.name }', '${ node.type }', uniforms )`;
|
||||
|
||||
// legacy
|
||||
code += `reference( 'value', '${ node.type }', uniforms[ '${ node.name }' ] )`;
|
||||
|
||||
} else {
|
||||
|
||||
this.addImport( 'uniform' );
|
||||
|
||||
this.global.add( node.name );
|
||||
|
||||
code += `uniform( '${ node.type }' )`;
|
||||
|
||||
}
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
emitExpression( node ) {
|
||||
|
||||
let code;
|
||||
|
||||
/*@TODO: else if ( node.isVarying ) {
|
||||
|
||||
code = this.emitVarying( node );
|
||||
|
||||
}*/
|
||||
|
||||
if ( node.isAccessor ) {
|
||||
|
||||
this.addImport( node.property );
|
||||
|
||||
code = node.property;
|
||||
|
||||
} else if ( node.isNumber ) {
|
||||
|
||||
if ( node.type === 'int' || node.type === 'uint' ) {
|
||||
|
||||
code = node.type + '( ' + node.value + ' )';
|
||||
|
||||
this.addImport( node.type );
|
||||
|
||||
} else {
|
||||
|
||||
code = node.value;
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isString ) {
|
||||
|
||||
code = '\'' + node.value + '\'';
|
||||
|
||||
} else if ( node.isOperator ) {
|
||||
|
||||
const opFn = opLib[ node.type ] || node.type;
|
||||
|
||||
const left = this.emitExpression( node.left );
|
||||
const right = this.emitExpression( node.right );
|
||||
|
||||
if ( isPrimitive( left ) && isPrimitive( right ) ) {
|
||||
|
||||
return left + ' ' + node.type + ' ' + right;
|
||||
|
||||
}
|
||||
|
||||
if ( isPrimitive( left ) ) {
|
||||
|
||||
code = opFn + '( ' + left + ', ' + right + ' )';
|
||||
|
||||
this.addImport( opFn );
|
||||
|
||||
} else {
|
||||
|
||||
code = left + '.' + opFn + '( ' + right + ' )';
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isFunctionCall ) {
|
||||
|
||||
const params = [];
|
||||
|
||||
for ( const parameter of node.params ) {
|
||||
|
||||
params.push( this.emitExpression( parameter ) );
|
||||
|
||||
}
|
||||
|
||||
this.addImport( node.name );
|
||||
|
||||
const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : '';
|
||||
|
||||
code = `${ node.name }(${ paramsStr })`;
|
||||
|
||||
} else if ( node.isReturn ) {
|
||||
|
||||
code = 'return';
|
||||
|
||||
if ( node.value ) {
|
||||
|
||||
code += ' ' + this.emitExpression( node.value );
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isAccessorElements ) {
|
||||
|
||||
code = node.property;
|
||||
|
||||
for ( const element of node.elements ) {
|
||||
|
||||
if ( element.isStaticElement ) {
|
||||
|
||||
code += '.' + this.emitExpression( element.value );
|
||||
|
||||
} else if ( element.isDynamicElement ) {
|
||||
|
||||
const value = this.emitExpression( element.value );
|
||||
|
||||
if ( isPrimitive( value ) ) {
|
||||
|
||||
code += `[ ${ value } ]`;
|
||||
|
||||
} else {
|
||||
|
||||
code += `.element( ${ value } )`;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if ( node.isDynamicElement ) {
|
||||
|
||||
code = this.emitExpression( node.value );
|
||||
|
||||
} else if ( node.isStaticElement ) {
|
||||
|
||||
code = this.emitExpression( node.value );
|
||||
|
||||
} else if ( node.isFor ) {
|
||||
|
||||
code = this.emitFor( node );
|
||||
|
||||
} else if ( node.isVariableDeclaration ) {
|
||||
|
||||
code = this.emitVariables( node );
|
||||
|
||||
} else if ( node.isUniform ) {
|
||||
|
||||
code = this.emitUniform( node );
|
||||
|
||||
} else if ( node.isTernary ) {
|
||||
|
||||
code = this.emitTernary( node );
|
||||
|
||||
} else if ( node.isConditional ) {
|
||||
|
||||
code = this.emitConditional( node );
|
||||
|
||||
} else if ( node.isUnary && node.expression.isNumber ) {
|
||||
|
||||
code = node.type + ' ' + node.expression.value;
|
||||
|
||||
} else if ( node.isUnary ) {
|
||||
|
||||
let type = unaryLib[ node.type ];
|
||||
|
||||
if ( node.after === false && ( node.type === '++' || node.type === '--' ) ) {
|
||||
|
||||
type += 'Before';
|
||||
|
||||
}
|
||||
|
||||
const exp = this.emitExpression( node.expression );
|
||||
|
||||
if ( isPrimitive( exp ) ) {
|
||||
|
||||
this.addImport( type );
|
||||
|
||||
code = type + '( ' + exp + ' )';
|
||||
|
||||
} else {
|
||||
|
||||
code = exp + '.' + type + '()';
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
console.warn( 'Unknown node type', node );
|
||||
|
||||
}
|
||||
|
||||
if ( ! code ) code = '/* unknown statement */';
|
||||
|
||||
return code;
|
||||
|
||||
}
|
||||
|
||||
emitBody( body ) {
|
||||
|
||||
this.setLastStatement( null );
|
||||
|
||||
let code = '';
|
||||
|
||||
this.tab += '\t';
|
||||
|
||||
for ( const statement of body ) {
|
||||
|
||||
code += this.emitExtraLine( statement );
|
||||
code += this.tab + this.emitExpression( statement );
|
||||
|
||||
if ( code.slice( - 1 ) !== '}' ) code += ';';
|
||||
|
||||
code += '\n';
|
||||
|
||||
this.setLastStatement( statement );
|
||||
|
||||
}
|
||||
|
||||
code = code.slice( 0, - 1 ); // remove the last extra line
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
|
||||
return code;
|
||||
|
||||
|
||||
}
|
||||
|
||||
emitTernary( node ) {
|
||||
|
||||
const condStr = this.emitExpression( node.cond );
|
||||
const leftStr = this.emitExpression( node.left );
|
||||
const rightStr = this.emitExpression( node.right );
|
||||
|
||||
this.addImport( 'cond' );
|
||||
|
||||
return `cond( ${ condStr }, ${ leftStr }, ${ rightStr } )`;
|
||||
|
||||
}
|
||||
|
||||
emitConditional( node ) {
|
||||
|
||||
const condStr = this.emitExpression( node.cond );
|
||||
const bodyStr = this.emitBody( node.body );
|
||||
|
||||
let ifStr = `If( ${ condStr }, () => {
|
||||
|
||||
${ bodyStr }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
let current = node;
|
||||
|
||||
while ( current.elseConditional ) {
|
||||
|
||||
const elseBodyStr = this.emitBody( current.elseConditional.body );
|
||||
|
||||
if ( current.elseConditional.cond ) {
|
||||
|
||||
const elseCondStr = this.emitExpression( current.elseConditional.cond );
|
||||
|
||||
ifStr += `.elseif( ${ elseCondStr }, () => {
|
||||
|
||||
${ elseBodyStr }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
} else {
|
||||
|
||||
ifStr += `.else( () => {
|
||||
|
||||
${ elseBodyStr }
|
||||
|
||||
${ this.tab }} )`;
|
||||
|
||||
}
|
||||
|
||||
current = current.elseConditional;
|
||||
|
||||
|
||||
}
|
||||
|
||||
this.imports.add( 'If' );
|
||||
|
||||
return ifStr;
|
||||
|
||||
}
|
||||
|
||||
emitLoop( node ) {
|
||||
|
||||
const start = this.emitExpression( node.initialization.value );
|
||||
const end = this.emitExpression( node.condition.right );
|
||||
|
||||
const name = node.initialization.name;
|
||||
const type = node.initialization.type;
|
||||
const condition = node.condition.type;
|
||||
const update = node.afterthought.type;
|
||||
|
||||
const nameParam = name !== 'i' ? `, name: '${ name }'` : '';
|
||||
const typeParam = type !== 'int' ? `, type: '${ type }'` : '';
|
||||
const conditionParam = condition !== '<' ? `, condition: '${ condition }'` : '';
|
||||
const updateParam = update !== '++' ? `, update: '${ update }'` : '';
|
||||
|
||||
let loopStr = `loop( { start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }, ( { ${ name } } ) => {\n\n`;
|
||||
|
||||
loopStr += this.emitBody( node.body ) + '\n\n';
|
||||
|
||||
loopStr += this.tab + '} )';
|
||||
|
||||
this.imports.add( 'loop' );
|
||||
|
||||
return loopStr;
|
||||
|
||||
}
|
||||
|
||||
emitFor( node ) {
|
||||
|
||||
const { initialization, condition, afterthought } = node;
|
||||
|
||||
if ( ( initialization && initialization.isVariableDeclaration && initialization.next === null ) &&
|
||||
( condition && condition.left.isAccessor && condition.left.property === initialization.name ) &&
|
||||
( afterthought && afterthought.isUnary ) &&
|
||||
( initialization.name === afterthought.expression.property )
|
||||
) {
|
||||
|
||||
return this.emitLoop( node );
|
||||
|
||||
}
|
||||
|
||||
return this.emitForWhile( node );
|
||||
|
||||
}
|
||||
|
||||
emitForWhile( node ) {
|
||||
|
||||
const initialization = this.emitExpression( node.initialization );
|
||||
const condition = this.emitExpression( node.condition );
|
||||
const afterthought = this.emitExpression( node.afterthought );
|
||||
|
||||
this.tab += '\t';
|
||||
|
||||
let forStr = '{\n\n' + this.tab + initialization + ';\n\n';
|
||||
forStr += `${ this.tab }While( ${ condition }, () => {\n\n`;
|
||||
|
||||
forStr += this.emitBody( node.body ) + '\n\n';
|
||||
|
||||
forStr += this.tab + '\t' + afterthought + ';\n\n';
|
||||
|
||||
forStr += this.tab + '} )\n\n';
|
||||
|
||||
this.tab = this.tab.slice( 0, - 1 );
|
||||
|
||||
forStr += this.tab + '}';
|
||||
|
||||
this.imports.add( 'While' );
|
||||
|
||||
return forStr;
|
||||
|
||||
}
|
||||
|
||||
emitVariables( node, isRoot = true ) {
|
||||
|
||||
const { name, type, value, next } = node;
|
||||
|
||||
const valueStr = value ? this.emitExpression( value ) : '';
|
||||
|
||||
let varStr = isRoot ? 'const ' : '';
|
||||
varStr += name;
|
||||
|
||||
if ( value ) {
|
||||
|
||||
if ( value.isFunctionCall && value.name === type ) {
|
||||
|
||||
varStr += ' = ' + valueStr;
|
||||
|
||||
} else {
|
||||
|
||||
varStr += ` = ${ type }( ${ valueStr } )`;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
varStr += ` = ${ type }()`;
|
||||
|
||||
}
|
||||
|
||||
if ( node.immutable === false ) {
|
||||
|
||||
varStr += '.toVar()';
|
||||
|
||||
}
|
||||
|
||||
if ( next ) {
|
||||
|
||||
varStr += ', ' + this.emitVariables( next, false );
|
||||
|
||||
}
|
||||
|
||||
this.addImport( type );
|
||||
|
||||
return varStr;
|
||||
|
||||
}
|
||||
|
||||
/*emitVarying( node ) { }*/
|
||||
|
||||
emitOverloadingFunction( nodes ) {
|
||||
|
||||
const { name } = nodes[ 0 ];
|
||||
|
||||
this.addImport( 'overloadingFn' );
|
||||
|
||||
return `const ${ name } = overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
|
||||
|
||||
}
|
||||
|
||||
emitFunction( node ) {
|
||||
|
||||
const { name, type } = node;
|
||||
|
||||
this._currentProperties = { name: node };
|
||||
|
||||
const params = [];
|
||||
const inputs = [];
|
||||
const mutableParams = [];
|
||||
|
||||
let hasPointer = false;
|
||||
|
||||
for ( const param of node.params ) {
|
||||
|
||||
let str = `{ name: '${ param.name }', type: '${ param.type }'`;
|
||||
|
||||
let name = param.name;
|
||||
|
||||
if ( param.immutable === false && ( param.qualifier !== 'inout' && param.qualifier !== 'out' ) ) {
|
||||
|
||||
name = name + '_immutable';
|
||||
|
||||
mutableParams.push( param );
|
||||
|
||||
}
|
||||
|
||||
if ( param.qualifier ) {
|
||||
|
||||
if ( param.qualifier === 'inout' || param.qualifier === 'out' ) {
|
||||
|
||||
hasPointer = true;
|
||||
|
||||
}
|
||||
|
||||
str += ', qualifier: \'' + param.qualifier + '\'';
|
||||
|
||||
}
|
||||
|
||||
inputs.push( str + ' }' );
|
||||
params.push( name );
|
||||
|
||||
this._currentProperties[ name ] = param;
|
||||
|
||||
}
|
||||
|
||||
for ( const param of mutableParams ) {
|
||||
|
||||
node.body.unshift( new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ) ) );
|
||||
|
||||
}
|
||||
|
||||
const paramsStr = params.length > 0 ? ' [ ' + params.join( ', ' ) + ' ] ' : '';
|
||||
const bodyStr = this.emitBody( node.body );
|
||||
|
||||
let fnName = name;
|
||||
let overloadingNodes = null;
|
||||
|
||||
if ( this.overloadings.has( name ) ) {
|
||||
|
||||
const overloadings = this.overloadings.get( name );
|
||||
|
||||
if ( overloadings.length > 1 ) {
|
||||
|
||||
const index = overloadings.indexOf( node );
|
||||
|
||||
fnName += '_' + index;
|
||||
|
||||
if ( index === overloadings.length - 1 ) {
|
||||
|
||||
overloadingNodes = overloadings;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let funcStr = `const ${ fnName } = tslFn( (${ paramsStr }) => {
|
||||
|
||||
${ bodyStr }
|
||||
|
||||
${ this.tab }} );\n`;
|
||||
|
||||
const layoutInput = inputs.length > 0 ? '\n\t\t' + this.tab + inputs.join( ',\n\t\t' + this.tab ) + '\n\t' + this.tab : '';
|
||||
|
||||
if ( node.layout !== false && hasPointer === false ) {
|
||||
|
||||
const uniqueName = this.uniqueNames ? fnName + '_' + Math.random().toString( 36 ).slice( 2 ) : fnName;
|
||||
|
||||
this.layoutsCode += `${ this.tab + fnName }.setLayout( {
|
||||
${ this.tab }\tname: '${ uniqueName }',
|
||||
${ this.tab }\ttype: '${ type }',
|
||||
${ this.tab }\tinputs: [${ layoutInput }]
|
||||
${ this.tab }} );\n\n`;
|
||||
|
||||
}
|
||||
|
||||
this.imports.add( 'tslFn' );
|
||||
|
||||
this.global.add( node.name );
|
||||
|
||||
if ( overloadingNodes !== null ) {
|
||||
|
||||
funcStr += '\n' + this.emitOverloadingFunction( overloadingNodes );
|
||||
|
||||
}
|
||||
|
||||
return funcStr;
|
||||
|
||||
}
|
||||
|
||||
setLastStatement( statement ) {
|
||||
|
||||
this._lastStatement = statement;
|
||||
|
||||
}
|
||||
|
||||
emitExtraLine( statement ) {
|
||||
|
||||
const last = this._lastStatement;
|
||||
if ( last === null ) return '';
|
||||
|
||||
if ( statement.isReturn ) return '\n';
|
||||
|
||||
const isExpression = ( st ) => st.isFunctionDeclaration !== true && st.isFor !== true && st.isConditional !== true;
|
||||
const lastExp = isExpression( last );
|
||||
const currExp = isExpression( statement );
|
||||
|
||||
if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
emit( ast ) {
|
||||
|
||||
let code = '\n';
|
||||
|
||||
if ( this.iife ) this.tab += '\t';
|
||||
|
||||
const overloadings = this.overloadings;
|
||||
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
if ( overloadings.has( statement.name ) === false ) {
|
||||
|
||||
overloadings.set( statement.name, [] );
|
||||
|
||||
}
|
||||
|
||||
overloadings.get( statement.name ).push( statement );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for ( const statement of ast.body ) {
|
||||
|
||||
code += this.emitExtraLine( statement );
|
||||
|
||||
if ( statement.isFunctionDeclaration ) {
|
||||
|
||||
code += this.tab + this.emitFunction( statement );
|
||||
|
||||
} else {
|
||||
|
||||
code += this.tab + this.emitExpression( statement ) + ';\n';
|
||||
|
||||
}
|
||||
|
||||
this.setLastStatement( statement );
|
||||
|
||||
}
|
||||
|
||||
const imports = [ ...this.imports ];
|
||||
const exports = [ ...this.global ];
|
||||
|
||||
const layouts = this.layoutsCode.length > 0 ? `\n${ this.tab }// layouts\n\n` + this.layoutsCode : '';
|
||||
|
||||
let header = '// Three.js Transpiler r' + REVISION + '\n\n';
|
||||
let footer = '';
|
||||
|
||||
if ( this.iife ) {
|
||||
|
||||
header += '( function ( TSL, uniforms ) {\n\n';
|
||||
|
||||
header += imports.length > 0 ? '\tconst { ' + imports.join( ', ' ) + ' } = TSL;\n' : '';
|
||||
footer += exports.length > 0 ? '\treturn { ' + exports.join( ', ' ) + ' };\n' : '';
|
||||
|
||||
footer += '\n} );';
|
||||
|
||||
} else {
|
||||
|
||||
header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/nodes\';\n' : '';
|
||||
footer += exports.length > 0 ? 'export { ' + exports.join( ', ' ) + ' };\n' : '';
|
||||
|
||||
}
|
||||
|
||||
return header + code + layouts + footer;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TSLEncoder;
|
18
public/sdk/three/jsm/transpiler/Transpiler.js
Normal file
18
public/sdk/three/jsm/transpiler/Transpiler.js
Normal file
@ -0,0 +1,18 @@
|
||||
class Transpiler {
|
||||
|
||||
constructor( decoder, encoder ) {
|
||||
|
||||
this.decoder = decoder;
|
||||
this.encoder = encoder;
|
||||
|
||||
}
|
||||
|
||||
parse( source ) {
|
||||
|
||||
return this.encoder.emit( this.decoder.parse( source ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Transpiler;
|
Reference in New Issue
Block a user