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);
+
+