diff options
-rw-r--r-- | compiler.c | 51 | ||||
-rw-r--r-- | include/ucode/compiler.h | 6 | ||||
-rw-r--r-- | include/ucode/lexer.h | 6 | ||||
-rw-r--r-- | include/ucode/vm.h | 1 | ||||
-rw-r--r-- | lexer.c | 6 | ||||
-rw-r--r-- | tests/custom/00_syntax/24_null_coalesce | 50 | ||||
-rw-r--r-- | tests/custom/00_syntax/25_and_or_assignment | 55 | ||||
-rw-r--r-- | tests/custom/00_syntax/26_exponentiation | 56 | ||||
-rw-r--r-- | vm.c | 67 |
9 files changed, 285 insertions, 13 deletions
@@ -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) \ @@ -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 -- @@ -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; |