summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2020-11-03 23:33:30 +0100
committerJo-Philipp Wich <jo@mein.io>2020-11-05 21:53:23 +0100
commitfeb815bc1a058b91eed9dea3c5cad8ea52d51806 (patch)
treea2fa20ccf392b82e7704545229f614f4026799cd
parent940a89bd5cc1a04dbff4379b02d559982b4febd4 (diff)
syntax: implement ES6-like arrow function syntax
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--ast.c16
-rw-r--r--ast.h1
-rw-r--r--eval.c8
-rw-r--r--lexer.c1
-rw-r--r--lexer.h2
-rw-r--r--parser.y61
-rw-r--r--tests/00_syntax/19_arrow_functions124
7 files changed, 192 insertions, 21 deletions
diff --git a/ast.c b/ast.c
index 0c9abe8..823b2a7 100644
--- a/ast.c
+++ b/ast.c
@@ -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);
diff --git a/ast.h b/ast.h
index da304c7..864e5f9 100644
--- a/ast.h
+++ b/ast.h
@@ -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 {
diff --git a/eval.c b/eval.c
index 481efc1..b7aece0 100644
--- a/eval.c
+++ b/eval.c
@@ -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,
diff --git a/lexer.c b/lexer.c
index b59d031..3555927 100644
--- a/lexer.c
+++ b/lexer.c
@@ -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 },
diff --git a/lexer.h b/lexer.h
index 6500722..08984fc 100644
--- a/lexer.h
+++ b/lexer.h
@@ -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)
diff --git a/parser.y b/parser.y
index 360a1be..87a6934 100644
--- a/parser.y
+++ b/parser.y
@@ -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 --