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

Records & Tuples Proposal Implementation #433

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8cb48a8
Added parsing of tuples.
christianaistleitner Mar 9, 2021
324a8bd
Add basic tuple translation skeleton.
christianaistleitner Mar 20, 2021
31d2045
Update type conversion nodes/methods to support tuples.
christianaistleitner Mar 24, 2021
ff319f0
Fix tuple index access.
christianaistleitner Mar 30, 2021
dd06e3d
Add tuple prototype builtins.
christianaistleitner Mar 31, 2021
83784a8
Add parsing of records.
christianaistleitner Apr 4, 2021
1d593d2
Add record type and basic functionality.
christianaistleitner Apr 25, 2021
2821014
Merge remote-tracking branch 'upstream/master' into record-tuple
christianaistleitner Apr 25, 2021
052710d
Fix issues after merge.
christianaistleitner Apr 25, 2021
dd9b5d9
Add isRecordAndTupleEnabled-method for hiding functionality.
christianaistleitner Apr 25, 2021
05705eb
Add JavaDoc to type classes.
christianaistleitner Apr 26, 2021
1f2b8a6
Add record type conversion.
christianaistleitner Apr 27, 2021
8c0c293
Remove tuple sub-class TODOs.
christianaistleitner Apr 29, 2021
1471f77
Update typeof operator.
christianaistleitner Apr 29, 2021
3150e12
Add missing tuple prototype builtins and unit tests.
christianaistleitner May 5, 2021
bf567e6
Refactor tuple abstract operations.
christianaistleitner May 5, 2021
7720e1f
Add tuple object internal methods and unit tests.
christianaistleitner May 10, 2021
2fd9737
Add unit tests for record object internal methods.
christianaistleitner May 10, 2021
9213728
Add missing tuple function builtins and unit tests.
christianaistleitner May 11, 2021
0ae391b
Add missing constructor builtins and unit tests.
christianaistleitner May 11, 2021
866f1b9
Add record function builtins and unit tests.
christianaistleitner May 12, 2021
fda0a12
Combine tuple prototype and getter builtins.
christianaistleitner May 12, 2021
e7b3db8
Update comparison nodes/utils and add unit tests.
christianaistleitner May 16, 2021
8fc4ba8
Cleanup record and tuple literal nodes.
christianaistleitner May 17, 2021
4ea133b
Merge remote-tracking branch 'upstream/master' into record-tuple
christianaistleitner May 17, 2021
51166a8
Fix build error.
christianaistleitner May 17, 2021
48b4547
Add record and tuple support to JSON builtins.
christianaistleitner May 18, 2021
170e056
Cleanup and remove interop annotations.
christianaistleitner May 18, 2021
4bd95f1
Cleanup tuple prototype builtins.
christianaistleitner May 29, 2021
694e2d0
Use nodes for abstract operations RecordToString and TupleToString.
christianaistleitner May 29, 2021
e2488b3
Extract expectError and execute methods to a common base class.
christianaistleitner May 30, 2021
10e73e2
Add improvements and small fixes.
christianaistleitner May 30, 2021
f1e8524
Add predefined record and tuple errors.
christianaistleitner May 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 205 additions & 5 deletions graal-js/src/com.oracle.js.parser/src/com/oracle/js/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
import static com.oracle.js.parser.TokenType.SPREAD_ARGUMENT;
import static com.oracle.js.parser.TokenType.SPREAD_ARRAY;
import static com.oracle.js.parser.TokenType.SPREAD_OBJECT;
import static com.oracle.js.parser.TokenType.SPREAD_RECORD;
import static com.oracle.js.parser.TokenType.SPREAD_TUPLE;
import static com.oracle.js.parser.TokenType.STATIC;
import static com.oracle.js.parser.TokenType.STRING;
import static com.oracle.js.parser.TokenType.SUPER;
Expand Down Expand Up @@ -156,6 +158,8 @@
import com.oracle.js.parser.ir.ParameterNode;
import com.oracle.js.parser.ir.PropertyKey;
import com.oracle.js.parser.ir.PropertyNode;
import com.oracle.js.parser.ir.RecordNode;
import com.oracle.js.parser.ir.RecordPropertyNode;
import com.oracle.js.parser.ir.ReturnNode;
import com.oracle.js.parser.ir.Scope;
import com.oracle.js.parser.ir.Statement;
Expand Down Expand Up @@ -3664,6 +3668,10 @@ private Expression primaryExpression(boolean yield, boolean await) {
return arrayLiteral(yield, await);
case LBRACE:
return objectLiteral(yield, await);
case HASH_BRACE:
return recordLiteral(yield, await);
case HASH_BRACKET:
return tupleLiteral(yield, await);
case LPAREN:
return parenthesizedExpressionAndArrowParameterList(yield, await);
case TEMPLATE:
Expand Down Expand Up @@ -3799,7 +3807,8 @@ private LiteralNode<Expression[]> arrayLiteral(boolean yield, boolean await) {
* <pre>
* ObjectLiteral :
* { }
* { PropertyNameAndValueList } { PropertyNameAndValueList , }
* { PropertyNameAndValueList }
* { PropertyNameAndValueList , }
*
* PropertyNameAndValueList :
* PropertyAssignment
Expand Down Expand Up @@ -4286,6 +4295,194 @@ private static final class PropertyFunction {
}
}

/**
* Parse a record literal.
*
* <pre>
* RecordLiteral[Yield, Await] :
* #{ }
* #{ RecordPropertyDefinitionList[?Yield, ?Await] }
* #{ RecordPropertyDefinitionList[?Yield, ?Await] , }
*
* RecordPropertyDefinitionList[Yield, Await] :
* RecordPropertyDefinition[?Yield, ?Await]
* RecordPropertyDefinitionList[?Yield, ?Await] , RecordPropertyDefinition[?Yield, ?Await]
* </pre>
*
* @return Record node.
*/
private RecordNode recordLiteral(boolean yield, boolean await) {
// Capture HASH_LBRACE token.
final long recordToken = token;
// HASH_LBRACE tested in caller.
next();

// Accumulate record property elements.
final ArrayList<RecordPropertyNode> elements = new ArrayList<>();
boolean commaSeen = true;
loop: while (true) {
switch (type) {
case RBRACE:
next();
break loop;

case COMMARIGHT:
if (commaSeen) {
throw error(AbstractParser.message("expected.property.id", type.getNameOrType()));
}
next();
commaSeen = true;
break;

default:
if (!commaSeen) {
throw error(AbstractParser.message("expected.comma", type.getNameOrType()));
}
commaSeen = false;
// Get and add the next property.
final RecordPropertyNode property = recordPropertyDefinition(yield, await);
elements.add(property);
}
}

return new RecordNode(recordToken, finish, optimizeList(elements));
}

/**
* Parse an record literal property definition.
* Compared to its object literal counterpart it does not contain
* CoverInitializedName and MethodDefinition productions.
*
* <pre>
* RecordPropertyDefinition[Yield, Await] :
* IdentifierReference[?Yield, ?Await]
* PropertyName[?Yield, ?Await] : AssignmentExpression[+In, ?Yield, ?Await]
* ... AssignmentExpression[+In, ?Yield, ?Await]
* </pre>
*
* @return Record property node.
*/
private RecordPropertyNode recordPropertyDefinition(boolean yield, boolean await) {
// Capture firstToken.
final long propertyToken = token;

final Expression propertyKey;

if (type == ELLIPSIS) {
// ... AssignmentExpression[+In, ?Yield, ?Await]
long spreadToken = Token.recast(propertyToken, SPREAD_RECORD);
next();
propertyKey = new UnaryNode(spreadToken, assignmentExpression(true, yield, await));
return new RecordPropertyNode(propertyToken, finish, propertyKey, null, false);
}

final boolean computed = type == LBRACKET;
final boolean isIdentifier = isIdentifier();
if (isIdentifier) {
// IdentifierReference[?Yield, ?Await]
propertyKey = getIdent().setIsPropertyName();
if (type == COMMARIGHT || type == RBRACE) {
verifyIdent((IdentNode) propertyKey, yield, await);
return new RecordPropertyNode(propertyToken, finish, propertyKey, propertyKey, false);
}
} else {
// PropertyName[?Yield, ?Await]
propertyKey = propertyName(yield, await);
}

// : AssignmentExpression[+In, ?Yield, ?Await]
Expression propertyValue;
expect(COLON);

if (isIdentifier && PROTO_NAME.equals(((PropertyKey) propertyKey).getPropertyName())) {
throw error("'__proto__' is not allowed in Record expressions", propertyKey.getToken());
}

pushDefaultName(propertyKey);
try {
propertyValue = assignmentExpression(true, yield, await);
} finally {
popDefaultName();
}

if (!computed && isAnonymousFunctionDefinition(propertyValue) && propertyKey instanceof PropertyKey) {
propertyValue = setAnonymousFunctionName(propertyValue, ((PropertyKey) propertyKey).getPropertyName());
}

return new RecordPropertyNode(propertyToken, finish, propertyKey, propertyValue, computed);
}

/**
* Parse a tuple literal.
*
* <pre>
* TupleLiteral[Yield, Await] :
* #[ ]
* #[ TupleElementList[?Yield, ?Await] ]
* #[ TupleElementList[?Yield, ?Await] , ]
*
* TupleElementList[Yield, Await] :
* AssignmentExpression[+In, ?Yield, ?Await]
* SpreadElement[?Yield, ?Await]
* TupleElementList[?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await]
* TupleElementList[?Yield, ?Await] , SpreadElement[?Yield, ?Await]
* </pre>
*
* @return Literal node.
*/
private LiteralNode tupleLiteral(boolean yield, boolean await) {
final long tupleToken = token; // Capture HASH_BRACKET token.
next(); // HASH_BRACKET tested in caller.

// Prepare to accumulate elements.
final ArrayList<Expression> elements = new ArrayList<>();
// Track elisions.
boolean elision = true;
loop: while (true) {
long spreadToken = 0;
switch (type) {
case RBRACKET:
next();
break loop;

case COMMARIGHT:
if (elision) { // If no prior expression
throw error(AbstractParser.message("unexpected.token", type.getNameOrType()));
}
next();
elision = true;
break;

case ELLIPSIS:
spreadToken = token;
next();
// fall through

default:
if (!elision) {
throw error(AbstractParser.message("expected.comma", type.getNameOrType()));
}

// Add expression element.
Expression expression = assignmentExpression(true, yield, await, false);
if (expression != null) {
if (spreadToken != 0) {
expression = new UnaryNode(Token.recast(spreadToken, SPREAD_TUPLE), expression);
}
elements.add(expression);
} else {
// TODO: somehow assignmentExpression(...) can return null, but I'm not sure why we expect a RBRACKET then :/
expect(RBRACKET);
}

elision = false;
break;
}
}

return LiteralNode.newTupleInstance(tupleToken, finish, optimizeList(elements));
}

/**
* Parse left hand side expression.
*
Expand Down Expand Up @@ -5817,10 +6014,13 @@ private Expression assignmentExpression(boolean in, boolean yield, boolean await
/**
* AssignmentExpression.
*
* AssignmentExpression[In, Yield] : ConditionalExpression[?In, ?Yield] [+Yield]
* YieldExpression[?In] ArrowFunction[?In, ?Yield] AsyncArrowFunction
* LeftHandSideExpression[?Yield] = AssignmentExpression[?In, ?Yield]
* LeftHandSideExpression[?Yield] AssignmentOperator AssignmentExpression[?In, ?Yield]
* AssignmentExpression[In, Yield] :
* ConditionalExpression[?In, ?Yield, ?Await]
* [+Yield] YieldExpression[?In, ?Await]
* ArrowFunction[?In, ?Yield, ?Await]
* AsyncArrowFunction[?In, ?Yield, ?Await]
* LeftHandSideExpression[?Yield, ?Await] = AssignmentExpression[?In, ?Yield, ?Await]
* LeftHandSideExpression[?Yield, ?Await] AssignmentOperator AssignmentExpression[?In, ?Yield, ?Await]
*/
private Expression assignmentExpression(boolean in, boolean yield, boolean await, boolean inPatternPosition) {
if (type == YIELD && yield) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,16 @@ public enum TokenType {
SPREAD_OBJECT (IR, null),
YIELD_STAR (IR, null),
ASSIGN_INIT (IR, null),
NAMEDEVALUATION(IR, null);
NAMEDEVALUATION(IR, null),

// Records & Tuples Proposal tokens
RECORD (LITERAL, null),
TUPLE (LITERAL, null),
SPREAD_RECORD (IR, null),
SPREAD_TUPLE (IR, null),
// TODO: Associate with the correct ECMAScript Version
HASH_BRACKET (BRACKET, "#[", 0, true, 13),
HASH_BRACE (BRACKET, "#{", 0, true, 13);
//@formatter:on

/** Next token kind in token lookup table. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,4 +509,61 @@ public static LiteralNode<Expression[]> newInstance(long token, int finish, List
public static LiteralNode<Expression[]> newInstance(final long token, final int finish, final Expression[] value) {
return new ArrayLiteralNode(token, finish, value);
}

/**
* Tuple literal node class.
*/
public static final class TupleLiteralNode extends PrimitiveLiteralNode<Expression[]> {

/**
* Constructor
*
* @param token token
* @param finish finish
* @param value array literal value, a Node array
*/
protected TupleLiteralNode(final long token, final int finish, final Expression[] value) {
super(Token.recast(token, TokenType.TUPLE), finish, value);
}

/**
* Returns a list of tuple element expressions.
*/
@Override
public List<Expression> getElementExpressions() {
return Collections.unmodifiableList(Arrays.asList(value));
}

@Override
public void toString(final StringBuilder sb, final boolean printType) {
sb.append('[');
boolean first = true;
for (final Node node : value) {
if (!first) {
sb.append(',');
sb.append(' ');
}
if (node == null) {
sb.append("undefined");
} else {
node.toString(sb, printType);
}
first = false;
}
sb.append(']');
}
}

/**
* Create a new tuple literal of Nodes from a list of Node values
*
* @param token token
* @param finish finish
* @param value literal value list
*
* @return the new literal node
*/
public static LiteralNode<Expression[]> newTupleInstance(long token, int finish, List<Expression> value) {
return new TupleLiteralNode(token, finish, valueToArray(value));
}
}
Loading