diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java index 7dc14bc8c7b..d852cd54804 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java @@ -81,9 +81,11 @@ import static com.oracle.js.parser.TokenType.LBRACKET; import static com.oracle.js.parser.TokenType.LET; import static com.oracle.js.parser.TokenType.LPAREN; +import static com.oracle.js.parser.TokenType.MOD; import static com.oracle.js.parser.TokenType.MUL; import static com.oracle.js.parser.TokenType.OF; import static com.oracle.js.parser.TokenType.PERIOD; +import static com.oracle.js.parser.TokenType.PIPELINE; import static com.oracle.js.parser.TokenType.PRIVATE_IDENT; import static com.oracle.js.parser.TokenType.RBRACE; import static com.oracle.js.parser.TokenType.RBRACKET; @@ -362,6 +364,8 @@ public class Parser extends AbstractParser { private boolean isModule; + private boolean topicReferenceUsed = false; + /** * Used to pass (async) arrow function flags from head to body. * @@ -3944,6 +3948,7 @@ private void debuggerStatement() { *
* PrimaryExpression : * this + * % * IdentifierReference * Literal * ArrayLiteral @@ -4031,6 +4036,15 @@ private Expression primaryExpression(boolean yield, boolean await, CoverExpressi TruffleString v8IntrinsicNameTS = lexer.stringIntern(v8IntrinsicName); return createIdentNode(v8IntrinsicToken, ident.getFinish(), v8IntrinsicNameTS); } + }else if(isES2023()){ + int pipeDepth = lc.getCurrentFunction().getPipeDepth(); + if(pipeDepth <= 0){ + throw error(JSErrorType.SyntaxError, "The topic reference can not be used here!"); + } + next(); + addIdentifierReference("%" + pipeDepth); + topicReferenceUsed = true; + return new IdentNode(Token.recast(token, IDENT), finish + 1, lexer.stringIntern("%" + pipeDepth)); } default: @@ -6341,7 +6355,7 @@ private Expression expression(Expression exprLhs, int minPrecedence, boolean in, int nextPrecedence = type.getPrecedence(); // Subtask greater precedence. - while (type.isOperator(in) && (nextPrecedence > precedence || (nextPrecedence == precedence && !type.isLeftAssociative()))) { + while (type.isOperator(in) && (nextPrecedence > precedence || (nextPrecedence == precedence && !type.isLeftAssociative()))){ rhs = expression(rhs, nextPrecedence, in, yield, await); nextPrecedence = type.getPrecedence(); } @@ -6432,6 +6446,34 @@ private Expression assignmentExpression(boolean in, boolean yield, boolean await popDefaultName(); } } + } else if(type == PIPELINE && isES2023()) { + boolean prevRef = topicReferenceUsed; + topicReferenceUsed = false; + lc.getCurrentFunction().increasePipeDepth(); + int pipeDepth = lc.getCurrentFunction().getPipeDepth(); + + next(); + + IdentNode placeHolder = new IdentNode(Token.recast(token, IDENT), + finish + 1, lexer.stringIntern("%" + pipeDepth)); + BinaryNode lhs = new BinaryNode(Token.recast(token, ASSIGN), placeHolder, exprLhs); + Expression rhs = assignmentExpression(in, yield, await); + + if(isStrictMode){ + final VarNode var = new VarNode(line, Token.recast(token, LET), placeHolder.getFinish(), placeHolder.setIsDeclaredHere(), null); + declareVar(lc.getCurrentScope(), var); + } + + if(!topicReferenceUsed){ + throw error("Pipe body must contain the topic reference token(%) at least once"); + } + + lc.getCurrentFunction().decreasePipeDepth(); + + BinaryNode result = new BinaryNode(Token.recast(token, COMMARIGHT), lhs, rhs); + topicReferenceUsed = prevRef; + + return result; } else { if (canBeAssignmentPattern) { if (coverExpression != CoverExpressionError.DENY) { diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ParserContextFunctionNode.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ParserContextFunctionNode.java index fc05a754a68..98782bd7679 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ParserContextFunctionNode.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/ParserContextFunctionNode.java @@ -109,6 +109,8 @@ class ParserContextFunctionNode extends ParserContextBaseNode { private List> hoistedVarDeclarations; private List > hoistableBlockFunctionDeclarations; + private int pipeDepth = 0; + /** * @param token The token for the function * @param ident External function name @@ -741,4 +743,17 @@ private static int calculateLength(final List parameters) { } return length; } + public int getPipeDepth(){ + return pipeDepth; + } + + public void increasePipeDepth(){ + pipeDepth++; + } + + public void decreasePipeDepth(){ + if(pipeDepth > 0){ + pipeDepth--; + } + } } diff --git a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java index bb44b027cbb..f134332a484 100644 --- a/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java +++ b/graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/TokenType.java @@ -122,6 +122,7 @@ public enum TokenType { ASSIGN_BIT_OR (BINARY, "|=", 2, false), OR (BINARY, "||", 4, true), ASSIGN_OR (BINARY, "||=", 2, false, 12), + PIPELINE (BINARY, "|>", 2, true), RBRACE (BRACKET, "}"), BIT_NOT (UNARY, "~", 15, false), ELLIPSIS (UNARY, "..."), diff --git a/graal-js/src/com.oracle.truffle.js.test/js/pipeline.js b/graal-js/src/com.oracle.truffle.js.test/js/pipeline.js new file mode 100644 index 00000000000..b12bab3679b --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js/pipeline.js @@ -0,0 +1,123 @@ +/** + * Pipeline operator proposal. + * + */ + +/** +*@option --ecmascript-version=staging +*/ + +load('assert.js'); + +function double(number){ + return number * 2; +} + +function add(number1, number2){ + return number1 + number2; +} + +class Rectangle { + constructor(height, width) { + this.height = height; + this.width = width; + } + + get area() { + return this.calcArea(); + } + + calcArea() { + return this.height * this.width; + } +} + +const array = ['Apple', 'Orange', 'Strawberry']; + +let unaryFuncBody = 5 |> double(%); +assertEqual(10, unaryFuncBody); + +let funcBody = double(3) |> add(%, 2); +assertEqual(8, funcBody); + +let methodPipeBody = new Rectangle(2, 3) |> %.calcArea(); +assertEqual(6, methodPipeBody); + +let arithmetic = (14 * 4) / 2 |> % + 1; +assertEqual(29, arithmetic); + +let arrayLiteral = array.indexOf('Orange') |> array[%]; +assertEqual('Orange', arrayLiteral); + +let arrayLiteral2 = 2 |> [1, %, 3]; +assertEqual(JSON.stringify([1, 2, 3]), JSON.stringify(arrayLiteral2)); + +let objectLiteral = 2 * 3 |> { type: "rectangle", area : %}; +assertEqual(JSON.stringify({type: "rectangle", area: 6}), JSON.stringify(objectLiteral)); + +let templateLiteral = array[2] |> `${%}`; +assertEqual('Strawberry', templateLiteral); + +let construction = (8/2) |> new Rectangle(2, %); +assertEqual(JSON.stringify(new Rectangle(2, 4)), JSON.stringify(construction)); + +function resolveAfter2Seconds(x) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(x); + }, 2000); + }); +} + +async function f1() { + const x = 10 |> await resolveAfter2Seconds(%); + console.log(x); +} + +f1(); + +//yield +function* counter(value) { + while (true) { + const step = value++ |> yield %; + + if (step) { + value += step; + } + } +} + +const generatorFunc = counter(0); +assertEqual(0, generatorFunc.next().value); +assertEqual(1, generatorFunc.next().value); +assertEqual(2, generatorFunc.next().value); + +//function body +let funcExpression = function test(value){ + let var1 = 4 + value; + let var2 = 7 |> % * var1; + console.log("Result: " + var2); +} |> %(2); + +/* +* Test chaining of pipeline +*/ + +const chainingExample1 = 7 |> new Rectangle(6, %) |> %.calcArea(); +assertEqual(42, chainingExample1); +const chainingExample2 = 7 |> new Rectangle(6, %) |> %.calcArea() |> % % %; +assertEqual(0, chainingExample2); +const chainingExample3 = 7 |> new Rectangle(6, %) |> %.calcArea() |> % % 2 |> array[%]; +assertEqual('Apple', chainingExample3); +const chainingExample4 = 7 |> new Rectangle(6, %) |> %.calcArea() |> % % 2 |> array[%] |> `${%}`; +assertEqual('Apple', chainingExample4); +const chainingExample5 = 7 |> new Rectangle(6, %) |> %.calcArea() |> % % 2 |> array[%] |> `${%}` |> array.indexOf(%); +assertEqual(0, chainingExample5); + +/* +* Error testing +*/ + +assertThrows(() => new Rectangle(2, 3) |> %.squareFootage(), TypeError); + +