diff options
author | Jo-Philipp Wich <jo@mein.io> | 2020-11-03 11:29:10 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2020-11-03 11:29:10 +0100 |
commit | e8e7692f169ae00434f3e0959f104d95284d2891 (patch) | |
tree | 8e4507bd6abe5a31c3538e45f0f6729028856826 | |
parent | 3cf36bbefa1c659861ef3376f3c4d97f1c9db844 (diff) |
syntax: implement ES6-like rest parameters for variadic functions
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r-- | ast.c | 21 | ||||
-rw-r--r-- | ast.h | 1 | ||||
-rw-r--r-- | eval.c | 28 | ||||
-rw-r--r-- | lexer.c | 1 | ||||
-rw-r--r-- | lexer.h | 2 | ||||
-rw-r--r-- | parser.y | 8 | ||||
-rw-r--r-- | tests/00_syntax/15_function_declarations | 41 |
7 files changed, 90 insertions, 12 deletions
@@ -292,10 +292,10 @@ func_free(struct json_object *v, void *ud) static int func_to_string(struct json_object *v, struct printbuf *pb, int level, int flags) { - bool strict = (level > 0) || (flags & JSON_C_TO_STRING_STRICT); + bool strict = (level > 0) || (flags & JSON_C_TO_STRING_STRICT), rest; struct ut_op *op = json_object_get_userdata(v); struct ut_function *fn = op->tag.data; - size_t i; + size_t i, len; sprintbuf(pb, "%sfunction%s%s(", strict ? "\"" : "", @@ -303,9 +303,13 @@ func_to_string(struct json_object *v, struct printbuf *pb, int level, int flags) fn->name ? fn->name : ""); if (fn->args) { - for (i = 0; i < json_object_array_length(fn->args); i++) { - sprintbuf(pb, "%s%s", + len = json_object_array_length(fn->args); + rest = (len > 1) && json_object_is_type(json_object_array_get_idx(fn->args, len - 1), json_type_null); + + for (i = 0; i < len - rest; i++) { + sprintbuf(pb, "%s%s%s", i ? ", " : "", + rest && i == len - 2 ? "..." : "", json_object_get_string(json_object_array_get_idx(fn->args, i))); } } @@ -340,8 +344,15 @@ ut_new_func(struct ut_state *s, struct ut_op *decl, struct ut_scope *scope) if (args) { fn->args = xjs_new_array(); - for (arg = args; arg; arg = ut_get_op(s, arg->tree.next)) + for (arg = args; arg; arg = ut_get_op(s, arg->tree.next)) { json_object_array_add(fn->args, json_object_get(arg->val)); + + /* if the last argument is a rest one (...arg), add extra null entry */ + if (arg->is_ellip) { + json_object_array_add(fn->args, NULL); + break; + } + } } fn->source = s->function ? s->function->source : NULL; @@ -58,6 +58,7 @@ struct ut_op { uint16_t is_reg_icase:1; uint16_t is_reg_newline:1; uint16_t is_reg_global:1; + uint16_t is_ellip:1; uint32_t off; struct json_object *val; union { @@ -971,12 +971,13 @@ ut_invoke(struct ut_state *state, uint32_t off, struct json_object *this, struct json_object *func, struct json_object *argvals) { struct ut_op *op, *tag = json_object_get_userdata(func); + struct json_object *arr, *rv = NULL; struct ut_callstack callstack = {}; struct ut_function *fn, *prev_fn; - struct json_object *rv = NULL; + size_t arridx, arglen; struct ut_scope *sc; - size_t arridx; ut_c_fn *fptr; + bool rest; if (!tag) return NULL; @@ -1011,10 +1012,29 @@ ut_invoke(struct ut_state *state, uint32_t off, struct json_object *this, sc = state->scope; state->scope = ut_acquire_scope(callstack.scope); - if (fn->args) - for (arridx = 0; arridx < json_object_array_length(fn->args); arridx++) + if (fn->args) { + arglen = json_object_array_length(fn->args); + rest = (arglen > 1) && json_object_is_type(json_object_array_get_idx(fn->args, arglen - 1), json_type_null); + + for (arridx = 0; arridx < arglen - rest; arridx++) { + /* if the last argument is a rest one (...arg), put all remaining parameter values in an array */ + if (rest && arridx == arglen - 2) { + arr = xjs_new_array(); + + ut_setval(callstack.scope->scope, + json_object_array_get_idx(fn->args, arridx), + arr); + + for (; argvals && arridx < json_object_array_length(argvals); arridx++) + json_object_array_add(arr, json_object_get(json_object_array_get_idx(argvals, arridx))); + + break; + } + ut_setval(callstack.scope->scope, json_object_array_get_idx(fn->args, arridx), argvals ? json_object_array_get_idx(argvals, arridx) : NULL); + } + } rv = ut_execute_op_sequence(state, fn->entry); tag = json_object_get_userdata(rv); @@ -68,6 +68,7 @@ static const struct token tokens[] = { { T_RSTM, "-%}", 3 }, { T_EQS, "===", 3 }, { T_NES, "!==", 3 }, + { T_ELLIP, "...", 3 }, { T_AND, "&&", 2 }, { T_ASADD, "+=", 2 }, { T_ASBAND, "&=", 2 }, @@ -19,7 +19,7 @@ #include "ast.h" -#define __T_MAX 80 +#define __T_MAX 81 #define T_EXCEPTION (__T_MAX + 0) #define T_CFUNC (__T_MAX + 1) #define T_RESSOURCE (__T_MAX + 2) @@ -209,8 +209,12 @@ switch_case(A) ::= T_CASE(B) exp(C) T_COLON stmts(D). { A = wrap_op(B, C, D); } switch_case(A) ::= T_CASE(B) exp(C) T_COLON. { A = wrap_op(B, C); } switch_case(A) ::= T_DEFAULT(B) T_COLON stmts(C). { A = wrap_op(B, C); } -args(A) ::= args(B) T_COMMA T_LABEL(C). { A = append_op(B, C); } -args(A) ::= T_LABEL(B). { A = B; } +args(A) ::= sargs(B) T_COMMA T_ELLIP T_LABEL(C). { A = append_op(B, C); ut_get_op(s, C)->is_ellip = 1; } +args(A) ::= T_ELLIP T_LABEL(B). { A = B; ut_get_op(s, B)->is_ellip = 1; } +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); } diff --git a/tests/00_syntax/15_function_declarations b/tests/00_syntax/15_function_declarations index cb391a4..1ed6f83 100644 --- a/tests/00_syntax/15_function_declarations +++ b/tests/00_syntax/15_function_declarations @@ -55,3 +55,44 @@ The function was called with arguments {{ a }} and {{ b }}. {% endfunction %} {{ test3_fn(123, 456) }} -- End -- + + +Additionally, utpl implements ES6-like "rest" argument syntax to declare +variadic functions. + +-- Expect stdout -- +function non_variadic(a, b, c, d, e) { ... } +[ 1, 2, 3, 4, 5 ] +function variadic_1(a, b, ...args) { ... } +[ 1, 2, [ 3, 4, 5 ] ] +function variadic_2(...args) { ... } +[ [ 1, 2, 3, 4, 5 ] ] +-- End -- + +-- Testcase -- +{% + // ordinary, non-variadic function + function non_variadic(a, b, c, d, e) { + return [ a, b, c, d, e ]; + } + + // fixed amount of arguments with variable remainder + function variadic_1(a, b, ...args) { + return [ a, b, args ]; + } + + // only variable arguments + function variadic_2(...args) { + return [ args ]; + } + + print(join("\n", [ + non_variadic, + non_variadic(1, 2, 3, 4, 5), + variadic_1, + variadic_1(1, 2, 3, 4, 5), + variadic_2, + variadic_2(1, 2, 3, 4, 5) + ]), "\n"); +%} +-- End -- |