Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Pipeline operator #761

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -3944,6 +3948,7 @@ private void debuggerStatement() {
* <pre>
* PrimaryExpression :
* this
* %
* IdentifierReference
* Literal
* ArrayLiteral
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class ParserContextFunctionNode extends ParserContextBaseNode {
private List<Map.Entry<VarNode, Scope>> hoistedVarDeclarations;
private List<Map.Entry<VarNode, Scope>> hoistableBlockFunctionDeclarations;

private int pipeDepth = 0;

/**
* @param token The token for the function
* @param ident External function name
Expand Down Expand Up @@ -741,4 +743,17 @@ private static int calculateLength(final List<IdentNode> parameters) {
}
return length;
}
public int getPipeDepth(){
return pipeDepth;
}

public void increasePipeDepth(){
pipeDepth++;
}

public void decreasePipeDepth(){
if(pipeDepth > 0){
pipeDepth--;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, "..."),
Expand Down
123 changes: 123 additions & 0 deletions graal-js/src/com.oracle.truffle.js.test/js/pipeline.js
Original file line number Diff line number Diff line change
@@ -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);