summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-03-06 23:56:41 +0100
committerJo-Philipp Wich <jo@mein.io>2022-03-07 23:17:51 +0100
commitefe8a0233bea283765443226340fa732cffc9ca6 (patch)
tree3532b18c9ada2e4bab1ec7d4e89e960067168c3e
parent05bd7edd7a101aa09a54371aa34bc22646b75bee (diff)
syntax: support add new operators
- Support ES2016 exponentiation (**) and exponentiation assignment (**=) - Support ES2020 nullish coalescing (??) and logical nullish assignment (??=) - Support ES2021 logical and assignment (&&=) and logical or assignment (||=) Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--compiler.c51
-rw-r--r--include/ucode/compiler.h6
-rw-r--r--include/ucode/lexer.h6
-rw-r--r--include/ucode/vm.h1
-rw-r--r--lexer.c6
-rw-r--r--tests/custom/00_syntax/24_null_coalesce50
-rw-r--r--tests/custom/00_syntax/25_and_or_assignment55
-rw-r--r--tests/custom/00_syntax/26_exponentiation56
-rw-r--r--vm.c67
9 files changed, 285 insertions, 13 deletions
diff --git a/compiler.c b/compiler.c
index 00f9f11..80b873d 100644
--- a/compiler.c
+++ b/compiler.c
@@ -38,6 +38,7 @@ static void uc_compiler_compile_labelexpr(uc_compiler_t *compiler);
static void uc_compiler_compile_function(uc_compiler_t *compiler);
static void uc_compiler_compile_and(uc_compiler_t *compiler);
static void uc_compiler_compile_or(uc_compiler_t *compiler);
+static void uc_compiler_compile_nullish(uc_compiler_t *compiler);
static void uc_compiler_compile_dot(uc_compiler_t *compiler);
static void uc_compiler_compile_subscript(uc_compiler_t *compiler);
static void uc_compiler_compile_ternary(uc_compiler_t *compiler);
@@ -62,6 +63,7 @@ uc_compiler_parse_rules[TK_ERROR + 1] = {
[TK_DIV] = { NULL, uc_compiler_compile_binary, P_MUL },
[TK_MUL] = { NULL, uc_compiler_compile_binary, P_MUL },
[TK_MOD] = { NULL, uc_compiler_compile_binary, P_MUL },
+ [TK_EXP] = { NULL, uc_compiler_compile_binary, P_EXP },
[TK_NUMBER] = { uc_compiler_compile_constant, NULL, P_NONE },
[TK_DOUBLE] = { uc_compiler_compile_constant, NULL, P_NONE },
[TK_STRING] = { uc_compiler_compile_constant, NULL, P_NONE },
@@ -75,6 +77,7 @@ uc_compiler_parse_rules[TK_ERROR + 1] = {
[TK_FUNC] = { uc_compiler_compile_function, NULL, P_NONE },
[TK_AND] = { NULL, uc_compiler_compile_and, P_AND },
[TK_OR] = { NULL, uc_compiler_compile_or, P_OR },
+ [TK_NULLISH] = { NULL, uc_compiler_compile_nullish, P_OR },
[TK_BOR] = { NULL, uc_compiler_compile_binary, P_BOR },
[TK_BXOR] = { NULL, uc_compiler_compile_binary, P_BXOR },
[TK_BAND] = { NULL, uc_compiler_compile_binary, P_BAND },
@@ -317,6 +320,10 @@ uc_compiler_parse_at_assignment_op(uc_compiler_t *compiler)
case TK_ASMOD:
case TK_ASADD:
case TK_ASSUB:
+ case TK_ASAND:
+ case TK_ASOR:
+ case TK_ASEXP:
+ case TK_ASNULLISH:
case TK_ASSIGN:
return true;
@@ -992,17 +999,21 @@ uc_compiler_emit_variable_rw(uc_compiler_t *compiler, uc_value_t *varname, uc_to
ssize_t idx;
switch (type) {
- case TK_ASADD: sub_insn = I_ADD; break;
- case TK_ASSUB: sub_insn = I_SUB; break;
- case TK_ASMUL: sub_insn = I_MUL; break;
- case TK_ASDIV: sub_insn = I_DIV; break;
- case TK_ASMOD: sub_insn = I_MOD; break;
- case TK_ASBAND: sub_insn = I_BAND; break;
- case TK_ASBXOR: sub_insn = I_BXOR; break;
- case TK_ASBOR: sub_insn = I_BOR; break;
- case TK_ASLEFT: sub_insn = I_LSHIFT; break;
- case TK_ASRIGHT: sub_insn = I_RSHIFT; break;
- default: sub_insn = 0; break;
+ case TK_ASADD: sub_insn = I_ADD; break;
+ case TK_ASSUB: sub_insn = I_SUB; break;
+ case TK_ASMUL: sub_insn = I_MUL; break;
+ case TK_ASDIV: sub_insn = I_DIV; break;
+ case TK_ASMOD: sub_insn = I_MOD; break;
+ case TK_ASBAND: sub_insn = I_BAND; break;
+ case TK_ASBXOR: sub_insn = I_BXOR; break;
+ case TK_ASBOR: sub_insn = I_BOR; break;
+ case TK_ASLEFT: sub_insn = I_LSHIFT; break;
+ case TK_ASRIGHT: sub_insn = I_RSHIFT; break;
+ case TK_ASAND: sub_insn = I_LTRUE; break;
+ case TK_ASOR: sub_insn = I_LFALSE; break;
+ case TK_ASEXP: sub_insn = I_EXP; break;
+ case TK_ASNULLISH: sub_insn = I_LNULL; break;
+ default: sub_insn = 0; break;
}
if (!varname) {
@@ -1637,6 +1648,24 @@ uc_compiler_compile_or(uc_compiler_t *compiler)
}
static void
+uc_compiler_compile_nullish(uc_compiler_t *compiler)
+{
+ uc_chunk_t *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off, jmp_off;
+
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 0);
+ uc_compiler_emit_insn(compiler, 0, I_LNULL);
+ uc_compiler_emit_insn(compiler, 0, I_NES);
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0);
+ jmp_off = uc_compiler_emit_jmp(compiler, 0);
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+ uc_compiler_parse_precedence(compiler, P_OR);
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+}
+
+static void
uc_compiler_compile_dot(uc_compiler_t *compiler)
{
bool optional_chaining = (compiler->parser->prev.type == TK_QDOT);
diff --git a/include/ucode/compiler.h b/include/ucode/compiler.h
index c112027..ffe7caf 100644
--- a/include/ucode/compiler.h
+++ b/include/ucode/compiler.h
@@ -37,11 +37,11 @@ typedef enum {
P_COMMA, /* , */
- P_ASSIGN, /* = += -= *= /= %= <<= >>= &= ^= |= */
+ P_ASSIGN, /* = += -= *= /= %= <<= >>= &= ^= |= ||= &&= **= ??= */
P_TERNARY, /* ?: */
- P_OR, /* || */
+ P_OR, /* || ?? */
P_AND, /* && */
P_BOR, /* | */
P_BXOR, /* ^ */
@@ -55,6 +55,8 @@ typedef enum {
P_ADD, /* + - */
P_MUL, /* * / % */
+ P_EXP, /* ** */
+
P_UNARY, /* ! ~ +… -… ++… --… */
P_INC, /* …++ …-- */
diff --git a/include/ucode/lexer.h b/include/ucode/lexer.h
index 30849e0..aa5d78b 100644
--- a/include/ucode/lexer.h
+++ b/include/ucode/lexer.h
@@ -63,6 +63,7 @@ typedef enum {
TK_MUL,
TK_DIV,
TK_MOD,
+ TK_EXP,
TK_NOT,
TK_COMPL,
TK_INC,
@@ -109,6 +110,11 @@ typedef enum {
TK_QLBRACK,
TK_QLPAREN,
TK_QDOT,
+ TK_ASEXP,
+ TK_ASAND,
+ TK_ASOR,
+ TK_ASNULLISH,
+ TK_NULLISH,
TK_EOF,
TK_ERROR
diff --git a/include/ucode/vm.h b/include/ucode/vm.h
index 4ba4627..24818a1 100644
--- a/include/ucode/vm.h
+++ b/include/ucode/vm.h
@@ -77,6 +77,7 @@ __insn(SUB) \
__insn(MUL) \
__insn(DIV) \
__insn(MOD) \
+__insn(EXP) \
__insn(NOT) \
__insn(COMPL) \
__insn(PLUS) \
diff --git a/lexer.c b/lexer.c
index b41e680..5fe7f6b 100644
--- a/lexer.c
+++ b/lexer.c
@@ -75,6 +75,10 @@ static const struct token tokens[] = {
{ TK_ELLIP, { .pat = "..." }, 3, NULL },
{ TK_QLBRACK, { .pat = "?.[" }, 3, NULL },
{ TK_QLPAREN, { .pat = "?.(" }, 3, NULL },
+ { TK_ASEXP, { .pat = "**=" }, 3, NULL },
+ { TK_ASAND, { .pat = "&&=" }, 3, NULL },
+ { TK_ASOR, { .pat = "||=" }, 3, NULL },
+ { TK_ASNULLISH, { .pat = "\?\?=" }, 3, NULL },
{ TK_AND, { .pat = "&&" }, 2, NULL },
{ TK_ASADD, { .pat = "+=" }, 2, NULL },
{ TK_ASBAND, { .pat = "&=" }, 2, NULL },
@@ -84,6 +88,7 @@ static const struct token tokens[] = {
{ TK_ASMOD, { .pat = "%=" }, 2, NULL },
{ TK_ASMUL, { .pat = "*=" }, 2, NULL },
{ TK_ASSUB, { .pat = "-=" }, 2, NULL },
+ { TK_EXP, { .pat = "**" }, 2, NULL },
{ TK_DEC, { .pat = "--" }, 2, NULL },
{ TK_INC, { .pat = "++" }, 2, NULL },
{ TK_EQ, { .pat = "==" }, 2, NULL },
@@ -100,6 +105,7 @@ static const struct token tokens[] = {
{ TK_LSTM, { .pat = "{%" }, 2, NULL },
{ TK_RSTM, { .pat = "%}" }, 2, NULL },
{ TK_ARROW, { .pat = "=>" }, 2, NULL },
+ { TK_NULLISH, { .pat = "??" }, 2, NULL },
{ TK_QDOT, { .pat = "?." }, 2, NULL },
{ TK_ADD, { .pat = "+" }, 1, NULL },
{ TK_ASSIGN, { .pat = "=" }, 1, NULL },
diff --git a/tests/custom/00_syntax/24_null_coalesce b/tests/custom/00_syntax/24_null_coalesce
new file mode 100644
index 0000000..26b89f1
--- /dev/null
+++ b/tests/custom/00_syntax/24_null_coalesce
@@ -0,0 +1,50 @@
+Null coalescing operators return the right hand side of an expression of
+the left hand side is null.
+
+
+1. The `??` operator returns the right hand side of the expression if the
+left hand side evaluates to `null`.
+
+-- Expect stdout --
+is null
+false
+0
+-- End --
+
+-- Testcase --
+{%
+ x = null;
+ y = false;
+ z = 0;
+
+ print(x ?? "is null", "\n");
+ print(y ?? "is null", "\n");
+ print(z ?? "is null", "\n");
+%}
+-- End --
+
+
+2. The `??=` nullish assignment operator sets the left hand side variable
+or value to the right hand side expression if the existing value is null.
+
+-- Expect stdout --
+is null
+false
+0
+-- End --
+
+-- Testcase --
+{%
+ x = null;
+ y = false;
+ z = 0;
+
+ x ??= "is null";
+ y ??= "is null";
+ z ??= "is null";
+
+ print(x, "\n");
+ print(y, "\n");
+ print(z, "\n");
+%}
+-- End --
diff --git a/tests/custom/00_syntax/25_and_or_assignment b/tests/custom/00_syntax/25_and_or_assignment
new file mode 100644
index 0000000..4dbc5f3
--- /dev/null
+++ b/tests/custom/00_syntax/25_and_or_assignment
@@ -0,0 +1,55 @@
+The logical AND and logical OR assignment operators set the left hand side
+variable or value to the right hand side expression result depending on
+whether the lhs value is truish.
+
+
+1. The `&&=` operator overwrites the lhs variable or field with the rhs
+expression result if the lhs is truish.
+
+-- Expect stdout --
+[
+ null,
+ false,
+ "is truish"
+]
+-- End --
+
+-- Testcase --
+{%
+ x = null;
+ y = false;
+ z = true;
+
+ x &&= "is truish";
+ y &&= "is truish";
+ z &&= "is truish";
+
+ printf("%.J\n", [ x, y, z ]);
+%}
+-- End --
+
+
+2. The `||=` operator overwrites the lhs variable or field with the rhs
+expression result if the lhs is falsy.
+
+-- Expect stdout --
+[
+ "is falsy",
+ "is falsy",
+ true
+]
+-- End --
+
+-- Testcase --
+{%
+ x = null;
+ y = false;
+ z = true;
+
+ x ||= "is falsy";
+ y ||= "is falsy";
+ z ||= "is falsy";
+
+ printf("%.J\n", [ x, y, z ]);
+%}
+-- End --
diff --git a/tests/custom/00_syntax/26_exponentiation b/tests/custom/00_syntax/26_exponentiation
new file mode 100644
index 0000000..1aab257
--- /dev/null
+++ b/tests/custom/00_syntax/26_exponentiation
@@ -0,0 +1,56 @@
+The exponentiation and exponentiation assignment operands allow raising
+the base operand value to the given power.
+
+
+1. The `**` operator returns the result of raising the first operand to
+the power of the second operand.
+
+-- Expect stdout --
+[
+ 1,
+ 4,
+ 9223372036854775808,
+ -9223372036854775808,
+ -0.25,
+ 2.75568
+]
+-- End --
+
+-- Testcase --
+{%
+ printf("%.J\n", [
+ 2 ** 0,
+ 2 ** 2,
+ 2 ** 63,
+ -2 ** 63,
+ -2 ** -2,
+ 1.5 ** 2.5
+ ]);
+%}
+-- End --
+
+
+2. The `**=` operator raises the lhs variable or field value to the
+power value in the rhs expression.
+
+-- Expect stdout --
+[
+ 4,
+ -0.25,
+ 2.75568
+]
+-- End --
+
+-- Testcase --
+{%
+ x = 2;
+ y = -2;
+ z = 1.5;
+
+ x **= 2;
+ y **= -2;
+ z **= 2.5;
+
+ printf("%.J\n", [ x, y, z ]);
+%}
+-- End --
diff --git a/vm.c b/vm.c
index e29a6ed..b6a0a26 100644
--- a/vm.c
+++ b/vm.c
@@ -1355,6 +1355,31 @@ uc_vm_value_bitop(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_val
}
static uc_value_t *
+uc_vm_value_logical(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand)
+{
+ uc_value_t *rv = NULL;
+
+ switch (operation) {
+ case I_LTRUE:
+ rv = ucv_get(ucv_is_truish(value) ? operand : value);
+ break;
+
+ case I_LFALSE:
+ rv = ucv_get(ucv_is_truish(value) ? value : operand);
+ break;
+
+ case I_LNULL:
+ rv = ucv_get(value == NULL ? operand : value);
+ break;
+
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+static uc_value_t *
uc_vm_string_concat(uc_vm_t *vm, uc_value_t *v1, uc_value_t *v2)
{
char buf[sizeof(void *)], *s1, *s2;
@@ -1385,6 +1410,22 @@ uc_vm_string_concat(uc_vm_t *vm, uc_value_t *v1, uc_value_t *v2)
return ucv_stringbuf_finish(sbuf);
}
+static uint64_t
+upow64(uint64_t base, uint64_t exponent)
+{
+ uint64_t result = 1;
+
+ while (exponent) {
+ if (exponent & 1)
+ result *= base;
+
+ exponent >>= 1;
+ base *= base;
+ }
+
+ return result;
+}
+
static uc_value_t *
uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand)
{
@@ -1397,6 +1438,9 @@ uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_val
operation == I_BAND || operation == I_BXOR || operation == I_BOR)
return uc_vm_value_bitop(vm, operation, value, operand);
+ if (operation == I_LTRUE || operation == I_LFALSE || operation == I_LNULL)
+ return uc_vm_value_logical(vm, operation, value, operand);
+
if (operation == I_ADD && (ucv_type(value) == UC_STRING || ucv_type(operand) == UC_STRING))
return uc_vm_string_concat(vm, value, operand);
@@ -1445,6 +1489,10 @@ uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_val
rv = ucv_double_new(fmod(d1, d2));
break;
+ case I_EXP:
+ rv = ucv_double_new(pow(d1, d2));
+ break;
+
default:
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
"undefined arithmetic operation %d",
@@ -1549,6 +1597,24 @@ uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_val
break;
+ case I_EXP:
+ if (n1 < 0 || n2 < 0) {
+ if (n1 < 0 && n2 < 0)
+ rv = ucv_double_new(-(1.0 / (double)upow64(abs64(n1), abs64(n2))));
+ else if (n2 < 0)
+ rv = ucv_double_new(1.0 / (double)upow64(abs64(n1), abs64(n2)));
+ else
+ rv = ucv_int64_new(-upow64(abs64(n1), abs64(n2)));
+ }
+ else {
+ if (!u1) u1 = (uint64_t)n1;
+ if (!u2) u2 = (uint64_t)n2;
+
+ rv = ucv_uint64_new(upow64(u1, u2));
+ }
+
+ break;
+
default:
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
"undefined arithmetic operation %d",
@@ -2415,6 +2481,7 @@ uc_vm_execute_chunk(uc_vm_t *vm)
case I_MUL:
case I_DIV:
case I_MOD:
+ case I_EXP:
uc_vm_insn_arith(vm, insn);
break;