Skip to content

[WIP] Support for shorthand object literal notation. #2432

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
74 changes: 70 additions & 4 deletions jerry-core/parser/js/js-lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,45 @@ static const keyword_string_t * const keyword_string_list[9] =
#undef LEXER_KEYWORD
#undef LEXER_KEYWORD_END

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
/**
* Checks if current identifier is a keyword or reserved word.
*
* @return true - if the current identifier is a keyword or reserved word
* false - otherwise
*/
bool
lexer_is_identifier_keyword (parser_context_t *context_p) /**< context */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a similar code for this. It would be good to not duplicate that.

You need to be smart here, invent a way which can be effectively optimized by tail merging or just "reparse" the string or whatever.

Copy link
Contributor Author

@AnthonyCalandra AnthonyCalandra Aug 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My updated patch doesn't yet address this; still thinking on it.

{
lexer_lit_location_t *literal_p = &context_p->token.lit_location;

JERRY_ASSERT (literal_p->type == LEXER_IDENT_LITERAL);
JERRY_ASSERT (literal_p->length <= PARSER_MAXIMUM_IDENT_LENGTH);

if (literal_p->has_escape || literal_p->length < 2 || literal_p->length > 10)
{
return false;
}

const keyword_string_t *keyword_p = keyword_string_list[literal_p->length - 2];

do
{
if (literal_p->char_p[0] == keyword_p->keyword_p[0]
&& literal_p->char_p[1] == keyword_p->keyword_p[1]
&& memcmp (literal_p->char_p, keyword_p->keyword_p, literal_p->length) == 0)
{
return true;
}

keyword_p++;
}
while (keyword_p->type != LEXER_EOS);

return false;
} /* lexer_is_identifier_keyword */
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

/**
* Parse identifier.
*/
Expand Down Expand Up @@ -1326,7 +1365,7 @@ lexer_check_colon (parser_context_t *context_p) /**< context */
&& context_p->source_p[0] == (uint8_t) LIT_CHAR_COLON);
} /* lexer_check_colon */

#ifndef CONFIG_DISABLE_ES2015_CLASS
#if !defined (CONFIG_DISABLE_ES2015_CLASS) || !defined (CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER)
/**
* Checks whether the next token is a left parenthesis.
*
Expand All @@ -1343,7 +1382,7 @@ lexer_check_left_paren (parser_context_t *context_p) /**< context */
return (context_p->source_p < context_p->source_end_p
&& context_p->source_p[0] == (uint8_t) LIT_CHAR_LEFT_PAREN);
} /* lexer_check_left_paren */
#endif /* !CONFIG_DISABLE_ES2015_CLASS */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you remove this check? Isn't this an ES6 feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It came from the ES6 classes pull request but I also use it for checking if we have a method (it checks to see if there is a left parenthesis after the identifier). If I keep the ifdef around it then if the user disables classes I won't have this function available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean this shorthand notation isn/t an es6 feature? Then we might need to change the ifdef.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I'm planning on adding computed properties at some point, so all these new language features to object initializers could fit nicely into this macro.

#endif /* !CONFIG_DISABLE_ES2015_CLASS || !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

#ifndef CONFIG_DISABLE_ES2015_ARROW_FUNCTION

Expand Down Expand Up @@ -2271,6 +2310,7 @@ lexer_expect_object_literal_id (parser_context_t *context_p, /**< context */
#ifndef CONFIG_DISABLE_ES2015_CLASS
int is_class_method = ((ident_opts & LEXER_OBJ_IDENT_CLASS_METHOD)
&& !(ident_opts & LEXER_OBJ_IDENT_ONLY_IDENTIFIERS)
&& !(ident_opts & LEXER_OBJ_IDENT_OBJ_METHOD)
&& (context_p->token.type != LEXER_KEYW_STATIC));
#endif /* !CONFIG_DISABLE_ES2015_CLASS */

Expand All @@ -2290,8 +2330,18 @@ lexer_expect_object_literal_id (parser_context_t *context_p, /**< context */
{
lexer_skip_spaces (context_p);

if (context_p->source_p < context_p->source_end_p
&& context_p->source_p[0] != LIT_CHAR_COLON)
bool not_end_of_literal = (context_p->source_p < context_p->source_end_p
&& context_p->source_p[0] != LIT_CHAR_COLON);

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
not_end_of_literal = (not_end_of_literal
&& context_p->source_p[0] != LIT_CHAR_RIGHT_BRACE
&& context_p->source_p[0] != LIT_CHAR_COMMA
/* Shorthand notation allows methods named `get` and allows getters. */
&& !lexer_check_left_paren (context_p));
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

if (not_end_of_literal)
{
if (lexer_compare_raw_identifier_to_current (context_p, "get", 3))
{
Expand Down Expand Up @@ -2346,6 +2396,12 @@ lexer_expect_object_literal_id (parser_context_t *context_p, /**< context */
context_p->column++;
return;
}
#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
else if (context_p->source_p[0] == LIT_CHAR_LEFT_PAREN)
{
create_literal_object = true;
}
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
else
{
const uint8_t *char_p = context_p->source_p;
Expand All @@ -2365,6 +2421,16 @@ lexer_expect_object_literal_id (parser_context_t *context_p, /**< context */
}
}

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
if (!(ident_opts & LEXER_OBJ_IDENT_ONLY_IDENTIFIERS)
&& (ident_opts & LEXER_OBJ_IDENT_OBJ_METHOD)
&& context_p->source_p[0] == LIT_CHAR_LEFT_PAREN)
{
context_p->token.type = LEXER_PROPERTY_METHOD;
return;
}
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

if (create_literal_object)
{
#ifndef CONFIG_DISABLE_ES2015_CLASS
Expand Down
2 changes: 2 additions & 0 deletions jerry-core/parser/js/js-lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ typedef enum
LEXER_EXPRESSION_START, /**< expression start */
LEXER_PROPERTY_GETTER, /**< property getter function */
LEXER_PROPERTY_SETTER, /**< property setter function */
LEXER_PROPERTY_METHOD, /**< property method (ES6+) */
LEXER_COMMA_SEP_LIST, /**< comma separated bracketed expression list */
LEXER_SCAN_SWITCH, /**< special value for switch pre-scan */
LEXER_CLASS_CONSTRUCTOR, /**< special value for class constructor method */
Expand Down Expand Up @@ -216,6 +217,7 @@ typedef enum
LEXER_OBJ_IDENT_NO_OPTS = (1u << 0), /**< no options */
LEXER_OBJ_IDENT_ONLY_IDENTIFIERS = (1u << 1), /**< only identifiers are accepted */
LEXER_OBJ_IDENT_CLASS_METHOD = (1u << 2), /**< expect identifier inside a class body */
LEXER_OBJ_IDENT_OBJ_METHOD = (1u << 3), /**< expect method identifier inside object */
} lexer_obj_ident_opts_t;

/**
Expand Down
89 changes: 69 additions & 20 deletions jerry-core/parser/js/js-parser-expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ parser_parse_array_literal (parser_context_t *context_p) /**< context */
}
} /* parser_parse_array_literal */

#ifdef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
Copy link
Contributor Author

@AnthonyCalandra AnthonyCalandra Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zherczeg I noticed that everything in this macro were needed for my code so I removed the ifdefs here. I'm guessing this change in your computed properties PR was to save binary size?

/**
* Object literal item types.
*/
Expand Down Expand Up @@ -357,7 +356,6 @@ parser_append_object_literal_item (parser_context_t *context_p, /**< context */
context_p->stack_top_uint8 = PARSER_OBJECT_PROPERTY_BOTH_ACCESSORS;
}
} /* parser_append_object_literal_item */
#endif /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

#ifndef CONFIG_DISABLE_ES2015_CLASS

Expand Down Expand Up @@ -649,41 +647,58 @@ parser_parse_object_literal (parser_context_t *context_p) /**< context */

while (true)
{
#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
lexer_expect_object_literal_id (context_p, LEXER_OBJ_IDENT_OBJ_METHOD);
#else /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
lexer_expect_object_literal_id (context_p, LEXER_OBJ_IDENT_NO_OPTS);
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

if (context_p->token.type == LEXER_RIGHT_BRACE)
{
break;
}

if (context_p->token.type == LEXER_PROPERTY_GETTER
|| context_p->token.type == LEXER_PROPERTY_SETTER)
bool token_type_is_method = (context_p->token.type == LEXER_PROPERTY_GETTER
|| context_p->token.type == LEXER_PROPERTY_SETTER);

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
token_type_is_method = token_type_is_method || context_p->token.type == LEXER_PROPERTY_METHOD;
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

if (token_type_is_method)
{
uint32_t status_flags;
cbc_ext_opcode_t opcode;
uint16_t opcode;
uint16_t literal_index, function_literal_index;
#ifdef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
parser_object_literal_item_types_t item_type;
#endif /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
lexer_token_type_t token_type = context_p->token.type;

if (context_p->token.type == LEXER_PROPERTY_GETTER)
if (token_type == LEXER_PROPERTY_GETTER)
{
status_flags = PARSER_IS_FUNCTION | PARSER_IS_CLOSURE | PARSER_IS_PROPERTY_GETTER;
opcode = CBC_EXT_SET_GETTER;
#ifdef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SET_GETTER);
item_type = PARSER_OBJECT_PROPERTY_GETTER;
#endif /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
}
else
#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
else if (token_type == LEXER_PROPERTY_METHOD)
{
status_flags = PARSER_IS_FUNCTION | PARSER_IS_CLOSURE | PARSER_IS_FUNC_EXPRESSION;
opcode = CBC_SET_LITERAL_PROPERTY;
item_type = PARSER_OBJECT_PROPERTY_VALUE;
}
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
else /* token_type == LEXER_PROPERTY_SETTER */
{
status_flags = PARSER_IS_FUNCTION | PARSER_IS_CLOSURE | PARSER_IS_PROPERTY_SETTER;
opcode = CBC_EXT_SET_SETTER;
#ifdef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SET_SETTER);
item_type = PARSER_OBJECT_PROPERTY_SETTER;
#endif /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
}

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
lexer_expect_object_literal_id (context_p, LEXER_OBJ_IDENT_ONLY_IDENTIFIERS | LEXER_OBJ_IDENT_OBJ_METHOD);
#else /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
lexer_expect_object_literal_id (context_p, LEXER_OBJ_IDENT_ONLY_IDENTIFIERS);
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

/* This assignment is a nop for computed getters/setters. */
literal_index = context_p->lit_object.index;
Expand All @@ -694,6 +709,11 @@ parser_parse_object_literal (parser_context_t *context_p) /**< context */
opcode = ((opcode == CBC_EXT_SET_GETTER) ? CBC_EXT_SET_COMPUTED_GETTER
: CBC_EXT_SET_COMPUTED_SETTER);
}

if (opcode == CBC_SET_LITERAL_PROPERTY)
{
parser_append_object_literal_item (context_p, literal_index, item_type);
}
#else /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
parser_append_object_literal_item (context_p, literal_index, item_type);
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
Expand All @@ -706,14 +726,21 @@ parser_parse_object_literal (parser_context_t *context_p) /**< context */
{
literal_index = function_literal_index;
}
/* Property methods aren't extended opcodes, so swap the values here. */
else if (opcode == CBC_SET_LITERAL_PROPERTY)
{
literal_index ^= function_literal_index;
function_literal_index ^= literal_index;
literal_index ^= function_literal_index;
}
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

parser_emit_cbc_literal (context_p,
CBC_PUSH_LITERAL,
literal_index);

JERRY_ASSERT (context_p->last_cbc_opcode == CBC_PUSH_LITERAL);
context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (opcode);
context_p->last_cbc_opcode = opcode;
context_p->last_cbc.value = function_literal_index;

lexer_next_token (context_p);
Expand Down Expand Up @@ -751,13 +778,35 @@ parser_parse_object_literal (parser_context_t *context_p) /**< context */
#endif /* CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

lexer_next_token (context_p);
if (context_p->token.type != LEXER_COLON)

if (context_p->token.type == LEXER_COLON)
{
parser_raise_error (context_p, PARSER_ERR_COLON_EXPECTED);
lexer_next_token (context_p);
parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA);
}
#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
else if (context_p->token.type == LEXER_COMMA
|| context_p->token.type == LEXER_RIGHT_BRACE)
{
// TODO somehow clean this up -- is it possible to parse and check for keywords by this step?
lexer_lit_location_t prop_name_literal = context_p->token.lit_location;

lexer_next_token (context_p);
parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA);
if (prop_name_literal.type != LEXER_IDENT_LITERAL || lexer_is_identifier_keyword (context_p))
{
parser_raise_error (context_p, PARSER_ERR_COLON_EXPECTED);
}

lexer_construct_literal_object (context_p,
&context_p->token.lit_location,
context_p->token.lit_location.type);
parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_LITERAL);
JERRY_ASSERT (context_p->last_cbc_opcode == CBC_PUSH_LITERAL);
}
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
else
{
parser_raise_error (context_p, PARSER_ERR_COLON_EXPECTED);
}

if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL)
{
Expand Down
7 changes: 6 additions & 1 deletion jerry-core/parser/js/js-parser-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,10 +433,15 @@ void parser_set_continues_to_current_position (parser_context_t *context_p, pars

/* Lexer functions */

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
bool lexer_is_identifier_keyword (parser_context_t *context_p);
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
void lexer_next_token (parser_context_t *context_p);
bool lexer_check_colon (parser_context_t *context_p);
#ifndef CONFIG_DISABLE_ES2015_CLASS
#if !defined (CONFIG_DISABLE_ES2015_CLASS) || !defined (CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER)
bool lexer_check_left_paren (parser_context_t *context_p);
#endif /* !CONFIG_DISABLE_ES2015_CLASS || !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */
#ifndef CONFIG_DISABLE_ES2015_CLASS
void lexer_skip_empty_statements (parser_context_t *context_p);
#endif /* !CONFIG_DISABLE_ES2015_CLASS */
#ifndef CONFIG_DISABLE_ES2015_ARROW_FUNCTION
Expand Down
30 changes: 20 additions & 10 deletions jerry-core/parser/js/js-parser-scanner.c
Original file line number Diff line number Diff line change
Expand Up @@ -763,16 +763,16 @@ parser_scan_until (parser_context_t *context_p, /**< context */
}
case SCAN_MODE_FUNCTION_ARGUMENTS:
{
const bool correct_stack_top = (stack_top == SCAN_STACK_BLOCK_STATEMENT
|| stack_top == SCAN_STACK_BLOCK_EXPRESSION
#ifndef CONFIG_DISABLE_ES2015_CLASS
JERRY_ASSERT (stack_top == SCAN_STACK_BLOCK_STATEMENT
|| stack_top == SCAN_STACK_BLOCK_EXPRESSION
|| stack_top == SCAN_STACK_CLASS
|| stack_top == SCAN_STACK_BLOCK_PROPERTY);
#else /* CONFIG_DISABLE_ES2015_CLASS */
JERRY_ASSERT (stack_top == SCAN_STACK_BLOCK_STATEMENT
|| stack_top == SCAN_STACK_BLOCK_EXPRESSION
|| stack_top == SCAN_STACK_BLOCK_PROPERTY);
|| stack_top == SCAN_STACK_CLASS
#endif /* !CONFIG_DISABLE_ES2015_CLASS */
#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZERS
|| stack_top == SCAN_STACK_OBJECT_LITERAL
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZERS */
|| stack_top == SCAN_STACK_BLOCK_PROPERTY);
JERRY_ASSERT (correct_stack_top);

if (context_p->token.type == LEXER_LITERAL
&& (context_p->token.lit_location.type == LEXER_IDENT_LITERAL
Expand Down Expand Up @@ -834,15 +834,25 @@ parser_scan_until (parser_context_t *context_p, /**< context */
}

if (context_p->token.type == LEXER_PROPERTY_GETTER
|| context_p->token.type == LEXER_PROPERTY_SETTER)
|| context_p->token.type == LEXER_PROPERTY_SETTER
|| context_p->token.type == LEXER_PROPERTY_METHOD)
{
parser_stack_push_uint8 (context_p, SCAN_STACK_BLOCK_PROPERTY);
mode = SCAN_MODE_FUNCTION_ARGUMENTS;
break;
}

lexer_next_token (context_p);
if (context_p->token.type != LEXER_COLON)

bool not_end_of_property_name = context_p->token.type != LEXER_COLON;

#ifndef CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER
not_end_of_property_name = (not_end_of_property_name
&& context_p->token.type != LEXER_RIGHT_BRACE
&& context_p->token.type != LEXER_COMMA);
#endif /* !CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER */

if (not_end_of_property_name)
{
parser_raise_error (context_p, PARSER_ERR_COLON_EXPECTED);
}
Expand Down
2 changes: 1 addition & 1 deletion jerry-core/profiles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,4 @@ In JerryScript all of the features are enabled by default, so an empty profile f
* `CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN`:
Disable the [ArrayBuffer](http://www.ecma-international.org/ecma-262/6.0/#sec-arraybuffer-objects) and [TypedArray](http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects) built-ins.
* `CONFIG_DISABLE_ES2015`: Disable all of the implemented [ECMAScript2015 features](http://www.ecma-international.org/ecma-262/6.0/).
(equivalent to `CONFIG_DISABLE_ES2015_ARROW_FUNCTION`, `CONFIG_DISABLE_ES2015_BUILTIN`, `CONFIG_DISABLE_ES2015_CLASS`, `CONFIG_DISABLE_ES2015_PROMISE_BUILTIN`, `CONFIG_DISABLE_ES2015_TEMPLATE_STRINGS`, and `CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN`).
(equivalent to `CONFIG_DISABLE_ES2015_ARROW_FUNCTION`, `CONFIG_DISABLE_ES2015_BUILTIN`, `CONFIG_DISABLE_ES2015_CLASS`, `CONFIG_DISABLE_ES2015_PROMISE_BUILTIN`, `CONFIG_DISABLE_ES2015_TEMPLATE_STRINGS`, `CONFIG_DISABLE_ES2015_TYPEDARRAY_BUILTIN`, and `CONFIG_DISABLE_ES2015_OBJECT_INITIALIZER`).
Loading