summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--eval.c38
-rw-r--r--parser.y12
-rw-r--r--tests/00_syntax/20_list_expressions41
3 files changed, 71 insertions, 20 deletions
diff --git a/eval.c b/eval.c
index b7aece0..236a9ca 100644
--- a/eval.c
+++ b/eval.c
@@ -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 *
diff --git a/parser.y b/parser.y
index 87a6934..fc8cfe6 100644
--- a/parser.y
+++ b/parser.y
@@ -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 --