summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2020-11-12 00:02:36 +0100
committerJo-Philipp Wich <jo@mein.io>2020-11-12 00:17:42 +0100
commitc447e58ebf3b04f129009f642d5d1be4ca8cee92 (patch)
tree8ed4788d85cfbf7a870fa748549606cb9c96ce65
parent6893c896561387fa88f610a2f54ba59867021cd8 (diff)
syntax: implement key/value for-in loop iteration
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--eval.c43
-rw-r--r--parser.y137
-rw-r--r--tests/00_syntax/16_for_loop131
3 files changed, 278 insertions, 33 deletions
diff --git a/eval.c b/eval.c
index 236a9ca..7a76dde 100644
--- a/eval.c
+++ b/eval.c
@@ -474,13 +474,13 @@ ut_execute_if(struct ut_state *state, uint32_t off)
static struct json_object *
ut_execute_for(struct ut_state *state, uint32_t off)
{
- struct json_object *scope, *val, *item, *iv, *rv = NULL;
+ struct json_object *kscope, *vscope, *val, *item, *ik, *iv = NULL, *rv = NULL;
struct ut_op *loop = ut_get_op(state, off);
struct ut_op *init = ut_get_child(state, off, 0);
uint32_t test = loop ? loop->tree.operand[1] : 0;
uint32_t incr = loop ? loop->tree.operand[2] : 0;
uint32_t body = loop ? loop->tree.operand[3] : 0;
- struct ut_op *ivar, *tag;
+ struct ut_op *ikvar, *ivvar, *tag;
size_t arridx, arrlen;
bool local = false;
@@ -491,21 +491,21 @@ ut_execute_for(struct ut_state *state, uint32_t off)
init = ut_get_op(state, init->tree.operand[0]);
}
- if (init->type != T_IN)
- return ut_new_exception(state, init->off,
- "Syntax error: missing ';' after for loop initializer");
+ ikvar = ut_get_op(state, init->tree.operand[0]);
+ ik = ikvar->val;
+ kscope = local ? state->scope->scope : ut_getref(state, ut_get_off(state, ikvar), NULL);
- ivar = ut_get_op(state, init->tree.operand[0]);
+ if (ut_is_type(kscope, T_EXCEPTION))
+ return kscope;
- if (!ivar || ivar->type != T_LABEL)
- return ut_new_exception(state, init->off,
- "Syntax error: invalid for-in left-hand side");
+ if (ikvar->tree.next) {
+ ivvar = ut_get_op(state, ikvar->tree.next);
+ iv = ivvar->val;
+ vscope = local ? kscope : ut_getref(state, ut_get_off(state, ivvar), NULL);
- iv = ivar->val;
- scope = local ? state->scope->scope : ut_getref(state, ut_get_off(state, ivar), NULL);
-
- if (ut_is_type(scope, T_EXCEPTION))
- return scope;
+ if (ut_is_type(vscope, T_EXCEPTION))
+ return vscope;
+ }
val = ut_execute_op_sequence(state, init->tree.operand[1]);
@@ -517,7 +517,14 @@ ut_execute_for(struct ut_state *state, uint32_t off)
arridx < arrlen; arridx++) {
item = json_object_array_get_idx(val, arridx);
- ut_setval(scope, iv, item);
+ if (iv) {
+ ut_setval(kscope, ik, xjs_new_int64(arridx));
+ ut_setval(vscope, iv, item);
+ }
+ else {
+ ut_setval(kscope, ik, item);
+ }
+
json_object_put(rv);
rv = ut_execute_op_sequence(state, body);
@@ -540,7 +547,11 @@ ut_execute_for(struct ut_state *state, uint32_t off)
}
else if (json_object_is_type(val, json_type_object)) {
json_object_object_foreach(val, key, item) {
- json_object_put(ut_setval(scope, iv, xjs_new_string(key)));
+ json_object_put(ut_setval(kscope, ik, xjs_new_string(key)));
+
+ if (iv)
+ ut_setval(vscope, iv, item);
+
json_object_put(rv);
rv = ut_execute_op_sequence(state, body);
diff --git a/parser.y b/parser.y
index 6c64c2d..e5f9853 100644
--- a/parser.y
+++ b/parser.y
@@ -92,20 +92,122 @@ ut_add_else(struct ut_state *s, uint32_t off, uint32_t add)
}
static inline uint32_t
-ut_check_arglist(struct ut_state *s, uint32_t off)
+ut_expect_token(struct ut_state *s, uint32_t off, int token)
{
uint64_t tokens[(__T_MAX + 63) & -64] = {};
+
+ tokens[token / 64] |= ((uint64_t)1 << (token % 64));
+ ut_parse_error(s, off, tokens, token);
+
+ return 0;
+}
+
+static inline uint32_t
+ut_check_op_seq_type(struct ut_state *s, uint32_t off, int type)
+{
struct ut_op *arg = ut_get_op(s, off);
while (arg) {
- if (arg->type != T_LABEL) {
- tokens[T_LABEL / 64] |= ((uint64_t)1 << (T_LABEL % 64));
- ut_parse_error(s, ut_get_off(s, arg), tokens, T_LABEL);
+ if (arg->type != type)
+ return ut_expect_token(s, ut_get_off(s, arg), type);
+
+ arg = ut_get_op(s, arg->tree.next);
+ }
+
+ return off;
+}
+
+static inline uint32_t
+ut_reject_local(struct ut_state *s, uint32_t off)
+{
+ struct ut_op *op = ut_get_op(s, off);
+
+ if (op->type == T_LOCAL) {
+ ut_new_exception(s, op->off, "Syntax error: Unexpected token\nDeclaration not allowed in this context");
+
+ return 0;
+ }
+
+ return off;
+}
+
+static inline uint32_t
+ut_check_for_in(struct ut_state *s, uint32_t off)
+{
+ struct ut_op *op = ut_get_op(s, off);
+ struct ut_op *arg;
+ uint32_t idx = 0;
+
+ /* for (let ... in ...) */
+ if (op->type == T_LOCAL) {
+ arg = ut_get_op(s, op->tree.operand[0]);
+
+ if (arg->type == T_ASSIGN) {
+ if (arg->tree.operand[1])
+ return ut_expect_token(s, op->tree.operand[0], T_COMMA);
+
+ if (!arg->tree.next) {
+ arg = ut_get_op(s, arg->tree.operand[0]);
+ ut_new_exception(s, arg->off + json_object_get_string_len(arg->val),
+ "Syntax error: Unexpected token\nExpecting ',' or 'in'");
+
+ return 0;
+ }
+
+ idx = arg->tree.operand[0];
+ arg = ut_get_op(s, arg->tree.next);
+ }
+
+ if (arg->type != T_IN || arg->tree.next) {
+ if (arg->type == T_IN && arg->tree.next)
+ arg = ut_get_op(s, arg->tree.next);
+
+ ut_new_exception(s, arg->off, "Syntax error: Invalid for-in expression");
return 0;
}
- arg = ut_get_op(s, arg->tree.next);
+ /* transform T_LOCAL(T_ASSIGN(T_LABEL)->T_IN(T_LABEL,...)) into
+ * T_LOCAL(T_IN(T_LABEL->T_LABEL,...)) */
+ if (idx)
+ arg->tree.operand[0] = append_op(idx, arg->tree.operand[0]);
+
+ op->tree.operand[0] = ut_get_off(s, arg);
+ op->tree.operand[1] = 0;
+ }
+
+ /* for (... in ...) */
+ else {
+ arg = op;
+
+ if (arg->type == T_LABEL) {
+ idx = off;
+
+ if (!arg->tree.next) {
+ ut_new_exception(s, arg->off + json_object_get_string_len(arg->val),
+ "Syntax error: Unexpected token\nExpecting ',' or 'in'");
+
+ return 0;
+ }
+
+ arg = ut_get_op(s, arg->tree.next);
+ }
+
+ if (arg->type != T_IN || arg->tree.next || ut_get_op(s, arg->tree.operand[0])->type != T_LABEL) {
+ if (arg->type == T_IN && arg->tree.next)
+ arg = ut_get_op(s, arg->tree.next);
+
+ ut_new_exception(s, arg->off, "Syntax error: Invalid for-in expression");
+
+ return 0;
+ }
+
+ /* transform T_LABEL->T_IN(T_LABEL,...) into T_IN(T_LABEL->T_LABEL,...) */
+ if (idx) {
+ op->tree.next = 0;
+ arg->tree.operand[0] = append_op(idx, arg->tree.operand[0]);
+ off = ut_get_off(s, arg);
+ }
}
return off;
@@ -183,10 +285,10 @@ iter_stmt(A) ::= T_WHILE(B) T_LPAREN exp(C) T_RPAREN stmt(D).
{ A = wrap_op(B, C, no_empty_obj(D)); }
iter_stmt(A) ::= T_WHILE(B) T_LPAREN exp(C) T_RPAREN T_COLON chunks(D) T_ENDWHILE.
{ A = wrap_op(B, C, D); }
-iter_stmt(A) ::= T_FOR(B) T_LPAREN for_in_exp(C) T_RPAREN stmt(D).
- { A = wrap_op(B, C, NULL, NULL, no_empty_obj(D)); ut_get_op(s, A)->is_for_in = 1; }
-iter_stmt(A) ::= T_FOR(B) T_LPAREN for_in_exp(C) T_RPAREN T_COLON chunks(D) T_ENDFOR.
- { A = wrap_op(B, C, NULL, NULL, no_empty_obj(D)); ut_get_op(s, A)->is_for_in = 1; }
+iter_stmt(A) ::= T_FOR(B) paren_exp(C) stmt(D).
+ { A = wrap_op(B, ut_check_for_in(s, C), NULL, NULL, no_empty_obj(D)); ut_get_op(s, A)->is_for_in = 1; }
+iter_stmt(A) ::= T_FOR(B) paren_exp(C) T_COLON chunks(D) T_ENDFOR.
+ { A = wrap_op(B, ut_check_for_in(s, C), NULL, NULL, no_empty_obj(D)); ut_get_op(s, A)->is_for_in = 1; }
iter_stmt(A) ::= T_FOR(B) T_LPAREN decl_or_exp(C) exp_stmt(D) T_RPAREN stmt(E).
{ A = wrap_op(B, C, D, NULL, no_empty_obj(E)); }
iter_stmt(A) ::= T_FOR(B) T_LPAREN decl_or_exp(C) exp_stmt(D) exp(E) T_RPAREN stmt(F).
@@ -236,9 +338,6 @@ args(A) ::= sargs(B). { A = B; }
sargs(A) ::= sargs(B) T_COMMA T_LABEL(C). { A = append_op(B, C); }
sargs(A) ::= T_LABEL(B). { A = B; }
-for_in_exp(A) ::= rel_exp(B). { A = B; }
-for_in_exp(A) ::= T_LOCAL(B) rel_exp(C). { A = wrap_op(B, C); }
-
decl_or_exp(A) ::= exp_stmt(B). { A = B; }
decl_or_exp(A) ::= decl_stmt(B). { A = B; }
@@ -248,13 +347,14 @@ ret_stmt(A) ::= T_RETURN(B) T_SCOL. { A = B; }
break_stmt(A) ::= T_BREAK(B) T_SCOL. { A = B; }
break_stmt(A) ::= T_CONTINUE(B) T_SCOL. { A = B; }
-decl_stmt(A) ::= T_LOCAL(B) decls(C) T_SCOL. { A = wrap_op(B, C); }
+decl_stmt(A) ::= T_LOCAL(B) decls(C) T_SCOL. { A = wrap_op(B, ut_check_op_seq_type(s, C, T_ASSIGN)); }
decls(A) ::= decls(B) T_COMMA decl(C). { A = append_op(B, C); }
decls(A) ::= decl(B). { A = B; }
decl(A) ::= T_LABEL(B) T_ASSIGN(C) arrow_exp(D). { A = wrap_op(C, B, D); }
-decl(A) ::= T_LABEL(B). { A = new_op(T_ASSIGN, NULL, B); }
+decl(A) ::= T_LABEL(B) T_IN(C) arrow_exp(D). { A = wrap_op(C, B, D); }
+decl(A) ::= T_LABEL(B). { A = new_op(T_ASSIGN, NULL, B); ut_get_op(s, A)->off = ut_get_op(s, B)->off; }
arrowfn_body(A) ::= cpd_stmt(B). { A = B; }
arrowfn_body(A) ::= assign_exp(B). { A = no_empty_obj(B); }
@@ -278,13 +378,13 @@ assign_exp(A) ::= unary_exp(B) T_ASBOR arrow_exp(C). { A = new_op(T_BOR, NULL, B
assign_exp(A) ::= arrow_exp(B). { A = B; }
arrow_exp(A) ::= unary_exp(B) T_ARROW(C) arrowfn_body(D).
- { A = wrap_op(C, 0, ut_check_arglist(s, B), D); }
+ { A = wrap_op(C, 0, ut_check_op_seq_type(s, B, T_LABEL), D); }
arrow_exp(A) ::= T_LPAREN T_RPAREN T_ARROW(C) arrowfn_body(D).
{ A = wrap_op(C, 0, 0, D); }
arrow_exp(A) ::= T_LPAREN T_ELLIP T_LABEL(B) T_RPAREN T_ARROW(C) arrowfn_body(D).
{ A = wrap_op(C, 0, B, D); ut_get_op(s, B)->is_ellip = 1; }
arrow_exp(A) ::= T_LPAREN exp(B) T_COMMA T_ELLIP T_LABEL(C) T_RPAREN T_ARROW(D) arrowfn_body(E).
- { A = append_op(B, C); A = wrap_op(D, 0, ut_check_arglist(s, A), E); ut_get_op(s, C)->is_ellip = 1; }
+ { A = append_op(B, C); A = wrap_op(D, 0, ut_check_op_seq_type(s, A, T_LABEL), E); ut_get_op(s, C)->is_ellip = 1; }
arrow_exp(A) ::= ternary_exp(B). { A = B; }
ternary_exp(A) ::= or_exp(B) T_QMARK(C) assign_exp(D) T_COLON ternary_exp(E).
@@ -360,7 +460,7 @@ primary_exp(A) ::= T_NULL(B). { A = B; }
primary_exp(A) ::= T_THIS(B). { A = B; }
primary_exp(A) ::= array(B). { A = B; }
primary_exp(A) ::= object(B). { A = B; }
-primary_exp(A) ::= T_LPAREN exp(B) T_RPAREN. { A = B; }
+primary_exp(A) ::= paren_exp(B). { A = ut_reject_local(s, B); }
primary_exp(A) ::= T_FUNC T_LPAREN T_RPAREN empty_object.
{ A = new_op(T_FUNC, NULL, 0, 0, 0); }
primary_exp(A) ::= T_FUNC T_LPAREN args(B) T_RPAREN empty_object.
@@ -370,6 +470,9 @@ primary_exp(A) ::= T_FUNC T_LPAREN T_RPAREN cpd_stmt(B).
primary_exp(A) ::= T_FUNC T_LPAREN args(B) T_RPAREN cpd_stmt(C).
{ A = new_op(T_FUNC, NULL, 0, B, C); }
+paren_exp(A) ::= T_LPAREN exp(B) T_RPAREN. { A = B; }
+paren_exp(A) ::= T_LPAREN T_LOCAL(B) decls(C) T_RPAREN. { A = wrap_op(B, C); }
+
array(A) ::= T_LBRACK(B) T_RBRACK. { A = B; }
array(A) ::= T_LBRACK(B) items(C) T_RBRACK. { A = wrap_op(B, C); }
diff --git a/tests/00_syntax/16_for_loop b/tests/00_syntax/16_for_loop
index 4253b5e..2f8ca9f 100644
--- a/tests/00_syntax/16_for_loop
+++ b/tests/00_syntax/16_for_loop
@@ -164,3 +164,134 @@ Iteration {{ i }}
Item {{ n }}
{% endfor %}
-- End --
+
+
+By specifying two loop variables in for-in loop expressions, keys
+and values can be iterated simultaneously.
+
+-- Expect stdout --
+true
+false
+123
+456
+[ 0, true ]
+[ 1, false ]
+[ 2, 123 ]
+[ 3, 456 ]
+foo
+bar
+baz
+qrx
+[ "foo", true ]
+[ "bar", false ]
+[ "baz", 123 ]
+[ "qrx", 456 ]
+-- End --
+
+-- Testcase --
+{%
+ local arr = [ true, false, 123, 456 ];
+ local obj = { foo: true, bar: false, baz: 123, qrx: 456 };
+
+ // iterating arrays with one loop variable yields the array values
+ for (local x in arr)
+ print(x, "\n");
+
+ // iterating arrays with two loop variables yields the array indexes
+ // and their corresponding values
+ for (local x, y in arr)
+ print([x, y], "\n");
+
+ // iterating objects with one loop variable yields the object keys
+ for (local x in obj)
+ print(x, "\n");
+
+ // iterating objects with two loop variables yields the object keys
+ // and their corresponding values
+ for (local x, y in obj)
+ print([x, y], "\n");
+%}
+-- End --
+
+
+Ensure that for-in loop expressions with more than two variables are
+rejected.
+
+-- Expect stderr --
+Syntax error: Invalid for-in expression
+In line 2, byte 16:
+
+ ` for (local x, y, z in {})`
+ Near here --------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ for (local x, y, z in {})
+ ;
+%}
+-- End --
+
+
+Ensure that assignments in for-in loop expressions are rejected.
+
+-- Expect stderr --
+Syntax error: Unexpected token
+Expecting ','
+In line 2, byte 15:
+
+ ` for (local x = 1, y in {})`
+ Near here -------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ for (local x = 1, y in {})
+ ;
+%}
+-- End --
+
+
+Ensure that too short for-in loop expressions are rejected (1/2).
+
+-- Expect stderr --
+Syntax error: Unexpected token
+Expecting ',' or 'in'
+In line 2, byte 14:
+
+ ` for (local x)`
+ Near here ------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ for (local x)
+ ;
+%}
+-- End --
+
+
+Ensure that too short for-in loop expressions are rejected (2/2).
+
+-- Expect stderr --
+Syntax error: Invalid for-in expression
+In line 2, byte 16:
+
+ ` for (local x, y)`
+ Near here --------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ for (local x, y)
+ ;
+%}
+-- End --