summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2020-11-03 11:29:10 +0100
committerJo-Philipp Wich <jo@mein.io>2020-11-03 11:29:10 +0100
commite8e7692f169ae00434f3e0959f104d95284d2891 (patch)
tree8e4507bd6abe5a31c3538e45f0f6729028856826
parent3cf36bbefa1c659861ef3376f3c4d97f1c9db844 (diff)
syntax: implement ES6-like rest parameters for variadic functions
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--ast.c21
-rw-r--r--ast.h1
-rw-r--r--eval.c28
-rw-r--r--lexer.c1
-rw-r--r--lexer.h2
-rw-r--r--parser.y8
-rw-r--r--tests/00_syntax/15_function_declarations41
7 files changed, 90 insertions, 12 deletions
diff --git a/ast.c b/ast.c
index 1a7355a..0c9abe8 100644
--- a/ast.c
+++ b/ast.c
@@ -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;
diff --git a/ast.h b/ast.h
index e9c8c0c..da304c7 100644
--- a/ast.h
+++ b/ast.h
@@ -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 {
diff --git a/eval.c b/eval.c
index 8931aec..91a1b6c 100644
--- a/eval.c
+++ b/eval.c
@@ -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);
diff --git a/lexer.c b/lexer.c
index f14a944..b59d031 100644
--- a/lexer.c
+++ b/lexer.c
@@ -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 },
diff --git a/lexer.h b/lexer.h
index 46cb9d5..6500722 100644
--- a/lexer.h
+++ b/lexer.h
@@ -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)
diff --git a/parser.y b/parser.y
index 25edf2f..289fdfa 100644
--- a/parser.y
+++ b/parser.y
@@ -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 --