diff options
author | Jo-Philipp Wich <jo@mein.io> | 2020-11-03 23:33:30 +0100 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2020-11-05 21:53:23 +0100 |
commit | feb815bc1a058b91eed9dea3c5cad8ea52d51806 (patch) | |
tree | a2fa20ccf392b82e7704545229f614f4026799cd | |
parent | 940a89bd5cc1a04dbff4379b02d559982b4febd4 (diff) |
syntax: implement ES6-like arrow function syntax
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r-- | ast.c | 16 | ||||
-rw-r--r-- | ast.h | 1 | ||||
-rw-r--r-- | eval.c | 8 | ||||
-rw-r--r-- | lexer.c | 1 | ||||
-rw-r--r-- | lexer.h | 2 | ||||
-rw-r--r-- | parser.y | 61 | ||||
-rw-r--r-- | tests/00_syntax/19_arrow_functions | 124 |
7 files changed, 192 insertions, 21 deletions
@@ -297,10 +297,13 @@ func_to_string(struct json_object *v, struct printbuf *pb, int level, int flags) struct ut_function *fn = op->tag.data; size_t i, len; - sprintbuf(pb, "%sfunction%s%s(", - strict ? "\"" : "", - fn->name ? " " : "", - fn->name ? fn->name : ""); + if (op->is_arrow) + sprintbuf(pb, "%s(", strict ? "\"" : ""); + else + sprintbuf(pb, "%sfunction%s%s(", + strict ? "\"" : "", + fn->name ? " " : "", + fn->name ? fn->name : ""); if (fn->args) { len = json_object_array_length(fn->args); @@ -314,7 +317,9 @@ func_to_string(struct json_object *v, struct printbuf *pb, int level, int flags) } } - return sprintbuf(pb, ") { ... }%s", strict ? "\"" : ""); + return sprintbuf(pb, ") %s{ ... }%s", + op->is_arrow ? "=> " : "", + strict ? "\"" : ""); } struct json_object * @@ -360,6 +365,7 @@ ut_new_func(struct ut_state *s, struct ut_op *decl, struct ut_scope *scope) op->val = val; op->type = T_FUNC; + op->is_arrow = (decl->type == T_ARROW); op->tag.data = fn; json_object_set_serializer(val, func_to_string, op, func_free); @@ -59,6 +59,7 @@ struct ut_op { uint16_t is_reg_newline:1; uint16_t is_reg_global:1; uint16_t is_ellip:1; + uint16_t is_arrow:1; uint32_t off; struct json_object *val; union { @@ -1046,7 +1046,12 @@ ut_invoke(struct ut_state *state, uint32_t off, struct json_object *this, callstack.next = state->callstack; callstack.function = state->function; callstack.off = op ? op->off : 0; - callstack.ctx = json_object_get(this ? this : state->ctx); + + if (tag->is_arrow) + callstack.ctx = state->callstack ? json_object_get(state->callstack->ctx) : NULL; + else + callstack.ctx = json_object_get(this ? this : state->ctx); + state->callstack = &callstack; state->calldepth++; @@ -1630,6 +1635,7 @@ static struct json_object *(*fns[__T_MAX])(struct ut_state *, uint32_t) = { [T_NULL] = ut_execute_atom, [T_THIS] = ut_execute_this, [T_FUNC] = ut_execute_function, + [T_ARROW] = ut_execute_function, [T_TEXT] = ut_execute_text, [T_ASSIGN] = ut_execute_assign, [T_LOCAL] = ut_execute_local, @@ -93,6 +93,7 @@ static const struct token tokens[] = { { T_REXP, "}}", 2 }, { T_LSTM, "{%", 2 }, { T_RSTM, "%}", 2 }, + { T_ARROW, "=>", 2 }, { T_ADD, "+", 1 }, { T_ASSIGN, "=", 1 }, { T_BAND, "&", 1 }, @@ -19,7 +19,7 @@ #include "ast.h" -#define __T_MAX 81 +#define __T_MAX 82 #define T_EXCEPTION (__T_MAX + 0) #define T_CFUNC (__T_MAX + 1) #define T_RESSOURCE (__T_MAX + 2) @@ -91,6 +91,26 @@ ut_add_else(struct ut_state *s, uint32_t off, uint32_t add) return off; } +static inline uint32_t +ut_check_arglist(struct ut_state *s, uint32_t off) +{ + uint64_t tokens[(__T_MAX + 63) & -64] = {}; + 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); + + return 0; + } + + arg = ut_get_op(s, arg->tree.next); + } + + return off; +} + } %syntax_error { @@ -233,26 +253,39 @@ decl_stmt(A) ::= T_LOCAL(B) decls(C) T_SCOL. { A = wrap_op(B, C); } 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) ternary_exp(D). { A = wrap_op(C, B, D); } +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); } +arrowfn_body(A) ::= cpd_stmt(B). { A = B; } +arrowfn_body(A) ::= assign_exp(B). { A = no_empty_obj(B); } + exp(A) ::= exp(B) T_COMMA assign_exp(C). { A = append_op(B, C); } exp(A) ::= assign_exp(B). { A = B; } -assign_exp(A) ::= unary_exp(B) T_ASSIGN(C) ternary_exp(D). +assign_exp(A) ::= unary_exp(B) T_ASSIGN(C) arrow_exp(D). { A = wrap_op(C, B, D); } -assign_exp(A) ::= unary_exp(B) T_ASADD ternary_exp(C). { A = new_op(T_ADD, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASSUB ternary_exp(C). { A = new_op(T_SUB, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASMUL ternary_exp(C). { A = new_op(T_MUL, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASDIV ternary_exp(C). { A = new_op(T_DIV, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASMOD ternary_exp(C). { A = new_op(T_MOD, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASLEFT ternary_exp(C). { A = new_op(T_LSHIFT, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASRIGHT ternary_exp(C). +assign_exp(A) ::= unary_exp(B) T_ASADD arrow_exp(C). { A = new_op(T_ADD, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASSUB arrow_exp(C). { A = new_op(T_SUB, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASMUL arrow_exp(C). { A = new_op(T_MUL, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASDIV arrow_exp(C). { A = new_op(T_DIV, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASMOD arrow_exp(C). { A = new_op(T_MOD, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASLEFT arrow_exp(C). { A = new_op(T_LSHIFT, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASRIGHT arrow_exp(C). { A = new_op(T_RSHIFT, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASBAND ternary_exp(C). { A = new_op(T_BAND, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASBXOR ternary_exp(C). { A = new_op(T_BXOR, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= unary_exp(B) T_ASBOR ternary_exp(C). { A = new_op(T_BOR, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } -assign_exp(A) ::= ternary_exp(B). { A = B; } +assign_exp(A) ::= unary_exp(B) T_ASBAND arrow_exp(C). { A = new_op(T_BAND, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASBXOR arrow_exp(C). { A = new_op(T_BXOR, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +assign_exp(A) ::= unary_exp(B) T_ASBOR arrow_exp(C). { A = new_op(T_BOR, NULL, B, C); A = new_op(T_ASSIGN, NULL, B, A); } +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); } +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; } +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). { A = wrap_op(C, B, D, E); } @@ -327,7 +360,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 assign_exp(B) T_RPAREN. { A = B; } +primary_exp(A) ::= T_LPAREN exp(B) T_RPAREN. { A = 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. diff --git a/tests/00_syntax/19_arrow_functions b/tests/00_syntax/19_arrow_functions new file mode 100644 index 0000000..0d7aa7a --- /dev/null +++ b/tests/00_syntax/19_arrow_functions @@ -0,0 +1,124 @@ +Besides the ordinary ES5-like function declarations, utpl supports ES6 inspired +arrow function syntax as well. Such arrow functions are useful for callbacks to functions such as replace(), map() or filter(). + +-- Expect stdout -- +() => { ... } +test +(a, b) => { ... } +3 +(...args) => { ... } +15 +(a) => { ... } +10 +(a) => { ... } +36 +-- End -- + +-- Testcase -- +{% + + // assign arrow function to variable + test1_fn = () => { + return "test"; + }; + + // assign arrow function with parameters + test2_fn = (a, b) => { + return a + b; + }; + + // nesting functions is legal + test3_fn = (...args) => { + nested_fn = (a, b) => { + return a * b; + }; + + return args[0] + nested_fn(args[0], args[1]); + }; + + // parentheses may be omitted if arrow function takes only one argument + test4_fn = a => { + a * 2; + }; + + // curly braces may be omitted if function body is a single expression + test5_fn = a => a * a; + + print(join("\n", [ + test1_fn, + test1_fn(), + test2_fn, + test2_fn(1, 2), + test3_fn, + test3_fn(3, 4), + test4_fn, + test4_fn(5), + test5_fn, + test5_fn(6) + ]), "\n"); +%} +-- End -- + + +While the main advantage of arrow functions is the compact syntax, another +important difference to normal functions is the "this" context behaviour - +arrow functions do not have an own "this" context and simply inherit it from +the outer calling scope. + +-- Expect stdout -- +this is set to obj: true +arrow function uses outher this: true +normal function has own this: true +arrow function as method has no this: true +-- End -- + +-- Testcase -- +{% + obj = { + method: function() { + local that = this; + local arr = () => { + print("arrow function uses outher this: ", that == this, "\n"); + }; + local fn = function() { + print("normal function has own this: ", that != this, "\n"); + }; + + print("this is set to obj: ", this == obj, "\n"); + + arr(); + fn(); + }, + + arrowfn: () => { + print("arrow function as method has no this: ", this == null, "\n"); + } + }; + + obj.method(); + obj.arrowfn(); +%} +-- End -- + + +Due to the difficulty of recognizing arrow function expressions with an LR(1) +grammar the parser has to use a generic expression rule on the lhs argument list +and verify that it does not contain non-label nodes while building the ast. The +subsequent testcase asserts that case. + +-- Expect stderr -- +Syntax error: Unexpected token +Expecting Label +In line 2, byte 5: + + ` (a + 1) => { print("test\n") }` + ^-- Near here + + +-- End -- + +-- Testcase -- +{% + (a + 1) => { print("test\n") } +%} +-- End -- |