diff options
-rw-r--r-- | eval.c | 38 | ||||
-rw-r--r-- | parser.y | 12 | ||||
-rw-r--r-- | tests/00_syntax/20_list_expressions | 41 |
3 files changed, 71 insertions, 20 deletions
@@ -137,6 +137,9 @@ static struct json_object * ut_execute_op(struct ut_state *state, uint32_t off); static struct json_object * +ut_execute_op_sequence(struct ut_state *state, uint32_t off); + +static struct json_object * ut_execute_list(struct ut_state *state, uint32_t off); static char * @@ -216,11 +219,11 @@ ut_getref(struct ut_state *state, uint32_t off, struct json_object **key) if (key) *key = off2 ? json_object_get(ut_get_op(state, off2)->val) : NULL; - return ut_execute_op(state, off1); + return ut_execute_op_sequence(state, off1); } else if (op && op->type == T_LBRACK && op->is_postfix) { if (key) { - val = off2 ? ut_execute_op(state, off2) : NULL; + val = off2 ? ut_execute_op_sequence(state, off2) : NULL; if (ut_is_type(val, T_EXCEPTION)) return val; @@ -228,7 +231,7 @@ ut_getref(struct ut_state *state, uint32_t off, struct json_object **key) *key = val; } - return ut_execute_op(state, off1); + return ut_execute_op_sequence(state, off1); } else if (op && op->type == T_LABEL) { sc = state->scope; @@ -397,7 +400,7 @@ ut_execute_assign(struct ut_state *state, uint32_t off) if (!key) return scope; - val = ut_execute_op(state, value); + val = ut_execute_op_sequence(state, value); if (!ut_is_type(val, T_EXCEPTION)) ut_setval(scope, key, val); @@ -422,7 +425,7 @@ ut_execute_local(struct ut_state *state, uint32_t off) if (alop) { label = alop->val; - val = asop->tree.operand[1] ? ut_execute_op(state, asop->tree.operand[1]) : NULL; + val = asop->tree.operand[1] ? ut_execute_op_sequence(state, asop->tree.operand[1]) : NULL; if (ut_is_type(val, T_EXCEPTION)) return val; @@ -504,7 +507,7 @@ ut_execute_for(struct ut_state *state, uint32_t off) if (ut_is_type(scope, T_EXCEPTION)) return scope; - val = ut_execute_op(state, init->tree.operand[1]); + val = ut_execute_op_sequence(state, init->tree.operand[1]); if (ut_is_type(val, T_EXCEPTION)) return val; @@ -663,7 +666,7 @@ ut_execute_and_or(struct ut_state *state, uint32_t off) for (i = 0; i < ARRAY_SIZE(op->tree.operand) && op->tree.operand[i]; i++) { json_object_put(val); - val = ut_execute_op(state, op->tree.operand[i]); + val = ut_execute_op_sequence(state, op->tree.operand[i]); if (ut_is_type(val, T_EXCEPTION)) break; @@ -749,7 +752,7 @@ _ut_get_operands(struct ut_state *state, struct ut_op *op, size_t n, struct json if (child && child->is_list) v[i] = ut_execute_list(state, ut_get_off(state, child)); else if (child) - v[i] = ut_execute_op(state, ut_get_off(state, child)); + v[i] = ut_execute_op_sequence(state, ut_get_off(state, child)); else v[i] = NULL; @@ -967,14 +970,12 @@ static struct json_object * ut_execute_object(struct ut_state *state, uint32_t off) { struct json_object *ex, *v, *obj = ut_new_object(NULL); - struct ut_op *key, *val; + struct ut_op *key; char *istr; size_t i; - for (key = ut_get_child(state, off, 0), val = ut_get_op(state, key ? key->tree.next : 0); - key != NULL && val != NULL; - key = ut_get_op(state, val->tree.next), val = ut_get_op(state, key ? key->tree.next : 0)) { - v = ut_execute_op(state, ut_get_off(state, val)); + for (key = ut_get_child(state, off, 0); key; key = ut_get_op(state, key->tree.next)) { + v = ut_execute_op_sequence(state, key->tree.operand[0]); if (ut_is_type(v, T_EXCEPTION)) { json_object_put(obj); @@ -1005,7 +1006,7 @@ ut_execute_object(struct ut_state *state, uint32_t off) break; default: - ex = ut_new_exception(state, val->off, "Type error: (%s) is not iterable", + ex = ut_new_exception(state, key->off, "Type error: (%s) is not iterable", json_object_get_string(v)); json_object_put(obj); @@ -1626,6 +1627,14 @@ ut_execute_lbrack(struct ut_state *state, uint32_t off) return ut_execute_list(state, op->tree.operand[0]); } +static struct json_object * +ut_execute_exp_list(struct ut_state *state, uint32_t off) +{ + struct ut_op *op = ut_get_op(state, off); + + return ut_execute_op_sequence(state, op->tree.operand[0]); +} + static struct json_object *(*fns[__T_MAX])(struct ut_state *, uint32_t) = { [T_NUMBER] = ut_execute_atom, [T_DOUBLE] = ut_execute_atom, @@ -1680,6 +1689,7 @@ static struct json_object *(*fns[__T_MAX])(struct ut_state *, uint32_t) = { [T_CONTINUE] = ut_execute_break_cont, [T_TRY] = ut_execute_try_catch, [T_SWITCH] = ut_execute_switch_case, + [T_COMMA] = ut_execute_exp_list, }; static struct json_object * @@ -346,7 +346,7 @@ postfix_exp(A) ::= unary_exp(B) T_LPAREN(C) T_RPAREN. { A = wrap_op(C, B); } postfix_exp(A) ::= unary_exp(B) T_LPAREN(C) arg_exps(D) T_RPAREN. { A = wrap_op(C, B, D); } postfix_exp(A) ::= postfix_exp(B) T_DOT(C) T_LABEL(D). { A = wrap_op(C, B, D); } -postfix_exp(A) ::= postfix_exp(B) T_LBRACK(C) assign_exp(D) T_RBRACK. +postfix_exp(A) ::= postfix_exp(B) T_LBRACK(C) exp(D) T_RBRACK. { A = wrap_op(C, B, D); ut_get_op(s, A)->is_postfix = 1; } postfix_exp(A) ::= primary_exp(B). { A = B; } @@ -376,8 +376,8 @@ array(A) ::= T_LBRACK(B) items(C) T_RBRACK. { A = wrap_op(B, C); } items(A) ::= items(B) T_COMMA item(C). { A = append_op(B, C); } items(A) ::= item(B). { A = B; } -item(A) ::= T_ELLIP assign_exp(B). { A = B; ut_get_op(s, A)->is_ellip = 1; } -item(A) ::= assign_exp(B). { A = B; } +item(A) ::= T_ELLIP assign_exp(B). { A = ut_get_op(s, B)->tree.next ? new_op(T_COMMA, NULL, B) : B; ut_get_op(s, A)->is_ellip = 1; } +item(A) ::= assign_exp(B). { A = ut_get_op(s, B)->tree.next ? new_op(T_COMMA, NULL, B) : B; } object(A) ::= empty_object(B). { A = B; } object(A) ::= T_LBRACE(B) tuples(C) T_RBRACE. { A = wrap_op(B, C); } @@ -387,9 +387,9 @@ empty_object(A) ::= T_LBRACE(B) T_RBRACE. { A = B; } tuples(A) ::= tuples(B) T_COMMA tuple(C). { A = append_op(B, C); } tuples(A) ::= tuple(B). { A = B; } -tuple(A) ::= T_LABEL(B) T_COLON exp(C). { A = append_op(B, C); } -tuple(A) ::= T_STRING(B) T_COLON exp(C). { A = append_op(B, C); } -tuple(A) ::= T_ELLIP(B) assign_exp(C). { A = append_op(B, C); } +tuple(A) ::= T_LABEL(B) T_COLON exp(C). { A = wrap_op(B, C); } +tuple(A) ::= T_STRING(B) T_COLON exp(C). { A = wrap_op(B, C); } +tuple(A) ::= T_ELLIP(B) assign_exp(C). { A = wrap_op(B, C); } arg_exps(A) ::= arg_exps(B) T_COMMA arg_exp(C). { A = append_op(B, C); ut_get_op(s, A)->is_list = 1; } arg_exps(A) ::= arg_exp(B). { A = B; ut_get_op(s, A)->is_list = 1; } diff --git a/tests/00_syntax/20_list_expressions b/tests/00_syntax/20_list_expressions new file mode 100644 index 0000000..ef1b0c4 --- /dev/null +++ b/tests/00_syntax/20_list_expressions @@ -0,0 +1,41 @@ +Similar to ES5, utpl's language grammar allows comma separated list expressions +in various contexts. Unless such lists happen to be part of a function call +or array construction expression, only the last result of such an expression +list should be used while still evaluating all sub-expressions, triggering +side effects such as function calls or variable assignments. + +-- Expect stdout -- +4 +[ 1, 3 ] +{ "a": true, "b": 1 } +function call +[ "test", "assigment" ] +true +true +true +-- End -- + +-- Testcase -- +{% + // only the last value is considered + print(1 + (2, 3), "\n"); + + // in array constructors, parenthesized lists are reduced to the last value + print([ (0, 1), (2, 3) ], "\n"); + + // in object constructors, parenthesized lists are reduced to the last value + print({ a: (false, true), b: (0, 1) }, "\n"); + + // all list expressions are evaluated and may have side effects, even if + // results are discareded + x = (print("function call\n"), y = "assigment", "test"); + print([x, y], "\n"); + + // property access operates on the last value of a parenthesized list expression + print(({foo: false}, {foo: true}).foo, "\n"); + print(({foo: false}, {foo: true})["foo"], "\n"); + + // computed property access uses the last list expression value + print(({foo: true})["bar", "baz", "foo"], "\n"); +%} +-- End -- |