Previously, the generator inferred reference intent from raw AST shape.
Now the parser marks the relevant property node as PROPERTY_REF before
building METHOD_CALL, assignment, or update nodes, and the generator
accepts both PROPERTY and PROPERTY_REF via
njs_generate_is_property_lvalue().
Introduce NJS_TOKEN_PROPERTY_REF as an explicit parser-side marker for
property accesses that carry reference semantics (assignment targets,
delete operands, increment/decrement, method-call receivers).
} njs_generator_log_assign_ctx_t;
+njs_inline njs_bool_t
+njs_generate_is_property_lvalue(njs_parser_node_t *node)
+{
+ return node->token_type == NJS_TOKEN_PROPERTY
+ || node->token_type == NJS_TOKEN_PROPERTY_REF;
+}
+
+
static u_char *njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator,
size_t size);
static njs_int_t njs_generate_code_map(njs_vm_t *vm, njs_generator_t *generator,
case NJS_TOKEN_REMAINDER:
case NJS_TOKEN_PROPERTY_DELETE:
case NJS_TOKEN_PROPERTY:
+ case NJS_TOKEN_PROPERTY_REF:
return njs_generate_3addr_operation(vm, generator, node, 0);
case NJS_TOKEN_IN:
foreach = node->left;
- if (foreach->left->token_type != NJS_TOKEN_PROPERTY) {
+ if (!njs_generate_is_property_lvalue(foreach->left)) {
name = foreach->left->right;
if (name != NULL) {
njs_generate_assignment_name, NULL, 0);
}
- /* lvalue->token == NJS_TOKEN_PROPERTY(_INIT) */
+ /*
+ * lvalue->token == NJS_TOKEN_PROPERTY(_REF|_INIT)
+ * or NJS_TOKEN_PROTO_INIT.
+ */
return njs_generate_property_lvalue(vm, generator, node,
njs_generate_assignment_prop,
&index, sizeof(njs_index_t));
}
- /* lvalue->token == NJS_TOKEN_PROPERTY */
+ if (!njs_generate_is_property_lvalue(lvalue)) {
+ njs_internal_error(vm, "unexpected assignment target");
+ return NJS_ERROR;
+ }
return njs_generate_property_lvalue(vm, generator, node,
njs_generate_operation_assignment_prop,
sizeof(njs_generator_log_assign_ctx_t));
}
- /* lvalue->token == NJS_TOKEN_PROPERTY */
+ if (!njs_generate_is_property_lvalue(lvalue)) {
+ njs_internal_error(vm, "unexpected logical assignment target");
+ return NJS_ERROR;
+ }
/* Object. */
if (call != NULL) {
preserve = call->u.object->left;
- if (preserve->token_type == NJS_TOKEN_PROPERTY) {
+ if (njs_generate_is_property_lvalue(preserve)) {
preserve->hoist = 1;
}
}
if (call != NULL) {
preserve = call->u.object->left;
- if (preserve->token_type != NJS_TOKEN_PROPERTY) {
+ if (!njs_generate_is_property_lvalue(preserve)) {
preserve = NULL;
}
} else {
return njs_generator_stack_pop(vm, generator, NULL);
}
- /* lvalue->token == NJS_TOKEN_PROPERTY */
+ if (!njs_generate_is_property_lvalue(lvalue)) {
+ njs_internal_error(vm, "unexpected increment/decrement target");
+ return NJS_ERROR;
+ }
return njs_generate_property_lvalue(vm, generator, node,
njs_generate_inc_dec_operation_prop,
NJS_TOKEN_OBJECT,
NJS_TOKEN_OBJECT_VALUE,
NJS_TOKEN_PROPERTY,
+ NJS_TOKEN_PROPERTY_REF,
NJS_TOKEN_PROPERTY_INIT,
NJS_TOKEN_PROPERTY_DELETE,
NJS_TOKEN_PROPERTY_GETTER,
njs_parser_node_t *func, njs_parser_state_func_t after);
static njs_int_t njs_parser_right_link_pop(njs_parser_t *parser);
+static njs_parser_node_t *njs_parser_property_ref(njs_parser_node_t *node);
+static njs_parser_node_t *njs_parser_lvalue_ref(njs_parser_node_t *node);
static njs_parser_node_t *njs_parser_optional_chain_method_call(
njs_parser_t *parser, njs_parser_node_t *node, uint32_t token_line);
static njs_int_t njs_parser_call_expression(njs_parser_t *parser,
}
+static njs_parser_node_t *
+njs_parser_property_ref(njs_parser_node_t *node)
+{
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (node->token_type == NJS_TOKEN_PROPERTY) {
+ node->token_type = NJS_TOKEN_PROPERTY_REF;
+ }
+
+ if (node->token_type == NJS_TOKEN_PROPERTY_REF) {
+ return node;
+ }
+
+ return NULL;
+}
+
+
+static njs_parser_node_t *
+njs_parser_lvalue_ref(njs_parser_node_t *node)
+{
+ if (node == NULL) {
+ return NULL;
+ }
+
+ if (node->token_type == NJS_TOKEN_NAME) {
+ return node;
+ }
+
+ return njs_parser_property_ref(node);
+}
+
+
static njs_parser_node_t *
njs_parser_create_call(njs_parser_t *parser, njs_parser_node_t *node,
uint8_t ctor)
break;
case NJS_TOKEN_PROPERTY:
+ case NJS_TOKEN_PROPERTY_REF:
+ node = njs_parser_property_ref(node);
+ if (node == NULL) {
+ return NULL;
+ }
+
func = njs_parser_node_new(parser, NJS_TOKEN_METHOD_CALL);
if (func == NULL) {
return NULL;
return NULL;
}
- if (node->token_type == NJS_TOKEN_PROPERTY) {
+ if (node->token_type == NJS_TOKEN_PROPERTY
+ || node->token_type == NJS_TOKEN_PROPERTY_REF)
+ {
return node;
}
if (node->token_type == NJS_TOKEN_OPTIONAL_CHAIN
&& node->right != NULL
- && node->right->token_type == NJS_TOKEN_PROPERTY)
+ && (node->right->token_type == NJS_TOKEN_PROPERTY
+ || node->right->token_type == NJS_TOKEN_PROPERTY_REF))
{
return node->right;
}
return NULL;
}
- prop = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY);
+ prop = njs_parser_node_new(parser, NJS_TOKEN_PROPERTY_REF);
if (prop == NULL) {
return NULL;
}
return NJS_DONE;
}
+ parser->node = njs_parser_lvalue_ref(parser->node);
+ if (parser->node == NULL) {
+ return NJS_ERROR;
+ }
+
node = njs_parser_node_new(parser, type);
if (node == NULL) {
return NJS_ERROR;
return NJS_DONE;
}
+ parser->node = njs_parser_lvalue_ref(parser->node);
+ if (parser->node == NULL) {
+ return NJS_ERROR;
+ }
+
parser->target->left = parser->node;
parser->node = parser->target;
{
double num;
njs_token_type_t type;
- njs_parser_node_t *node;
+ njs_parser_node_t *node, *prop;
type = parser->target->token_type;
node = parser->node;
switch (node->token_type) {
case NJS_TOKEN_PROPERTY:
+ case NJS_TOKEN_PROPERTY_REF:
+ node = njs_parser_property_ref(node);
+ if (node == NULL) {
+ return NJS_ERROR;
+ }
+
node->token_type = NJS_TOKEN_PROPERTY_DELETE;
node->u.operation = NJS_VMCODE_PROPERTY_DELETE;
return njs_parser_stack_pop(parser);
case NJS_TOKEN_OPTIONAL_CHAIN:
- if (node->right != NULL
- && node->right->token_type == NJS_TOKEN_PROPERTY)
- {
- node->right->token_type = NJS_TOKEN_PROPERTY_DELETE;
- node->right->u.operation = NJS_VMCODE_PROPERTY_DELETE;
+ prop = njs_parser_optional_chain_property(node);
+ if (prop != NULL) {
+ prop->token_type = NJS_TOKEN_PROPERTY_DELETE;
+ prop->u.operation = NJS_VMCODE_PROPERTY_DELETE;
}
break;
return NJS_DONE;
}
+ parser->node = njs_parser_lvalue_ref(parser->node);
+ if (parser->node == NULL) {
+ return NJS_ERROR;
+ }
+
node = njs_parser_node_new(parser, token->type);
if (node == NULL) {
return NJS_ERROR;
/* for-in */
- if (parser->node->token_type != NJS_TOKEN_NAME &&
- parser->node->token_type != NJS_TOKEN_PROPERTY)
- {
+ if (!njs_parser_is_lvalue(parser->node)) {
text = (njs_str_t *) parser->target;
njs_parser_ref_error(parser, "Invalid left-hand side \"%V\" "
return NJS_DONE;
}
+ parser->node = njs_parser_lvalue_ref(parser->node);
+ if (parser->node == NULL) {
+ return NJS_ERROR;
+ }
+
operation = NJS_VMCODE_PROPERTY_IN;
node = njs_parser_node_new(parser, token->type);
{
node = parser->node->left;
- if (node->token_type != NJS_TOKEN_NAME &&
- node->token_type != NJS_TOKEN_PROPERTY)
- {
+ if (!njs_parser_is_lvalue(node)) {
text = (njs_str_t *) parser->target;
return NJS_DONE;
}
+ node = njs_parser_lvalue_ref(node);
+ if (node == NULL) {
+ return NJS_ERROR;
+ }
+
+ parser->node->left = node;
+
njs_parser_next(parser, njs_parser_for_in_statement);
return NJS_OK;
}
njs_token_serialize(NJS_TOKEN_REGEXP);
njs_token_serialize(NJS_TOKEN_PROPERTY);
+ njs_token_serialize(NJS_TOKEN_PROPERTY_REF);
njs_token_serialize(NJS_TOKEN_PROPERTY_INIT);
njs_token_serialize(NJS_TOKEN_PROPERTY_DELETE);
njs_token_serialize(NJS_TOKEN_PROPERTY_GETTER);
#define njs_parser_is_lvalue(node) \
((node)->token_type == NJS_TOKEN_NAME \
- || (node)->token_type == NJS_TOKEN_PROPERTY)
+ || (node)->token_type == NJS_TOKEN_PROPERTY \
+ || (node)->token_type == NJS_TOKEN_PROPERTY_REF)
#define njs_parser_is_primitive(node) \
{ njs_str("var o = null; (o?.m)()"),
njs_str("TypeError: undefined is not a function") },
+ { njs_str("var o = {x: 7, t: function(s) {return this.x + s[0]}};"
+ "o.t`!`"),
+ njs_str("7!") },
+
{ njs_str("var o = {a: {b: 1}}; delete o?.a?.b; o.a.b"),
njs_str("undefined") },
{ njs_str("var a = []; for (var k in new Uint8Array([1,2,3])) { a.push(k); }; a"),
njs_str("0,1,2") },
+ { njs_str("var dst = {}; for (dst.a in {x: 1}) {} dst.a"),
+ njs_str("x") },
+
+ { njs_str("var dst = {}; for ((dst.a) in {x: 1}) {} dst.a"),
+ njs_str("x") },
+
{ njs_str("var i=0, a=[], r=[], d=[3,5];"
"function ret_a() {r.push('ret_a'); return a};"
"function ret_d() {r.push('ret_d'); return d};"
{ njs_str("var o = { x: 1, f: function() { return this.x } }; o.f()"),
njs_str("1") },
+ { njs_str("var o = { x: 1, f: function() { return this.x } }; (o.f)()"),
+ njs_str("1") },
+
{ njs_str("var o = { x: 1, f: function(a) { return this.x += a } };"
"o.f(5) +' '+ o.x"),
njs_str("6 6") },
"o.a = 1; o.a"),
njs_str("1") },
+ { njs_str("var o = {a:1}; (o.a) = 2; o.a"),
+ njs_str("2") },
+
+ { njs_str("var o = {a:1}; (o.a)++; o.a"),
+ njs_str("2") },
+
{ njs_str("var p = Object.create(Function);"
"Object.defineProperty(p, 'length', {writable: true});"
"p.length = 32; p.length"),
"delete o.a; o.a"),
njs_str("undefined") },
+ { njs_str("var o = {a:1}; delete (o.a); o.a"),
+ njs_str("undefined") },
+
{ njs_str("var o = {};"
"Object.defineProperty(o, 'a', {value:1, configurable:false});"
"delete o.a"),