summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-11-14 22:10:12 +0100
committerJo-Philipp Wich <jo@mein.io>2022-11-15 01:21:32 +0100
commitfdc9b6a3841d7346dab6f4aeea06322be0d3ee95 (patch)
tree39537641aa77efc0202d60fd270a592d4b660070
parent5fd5e8c3d785da27b90320f6ac93853ecf87eeae (diff)
compiler: fix `??=`, `||=` and `&&=` logical assignment semantics
When compiling logical assignment expressions, ensure that the right hand side of the assignment is not evaluated when the assignment condition is unfulfilled. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
-rw-r--r--compiler.c153
-rw-r--r--tests/custom/00_syntax/25_and_or_assignment30
-rw-r--r--vm.c28
3 files changed, 146 insertions, 65 deletions
diff --git a/compiler.c b/compiler.c
index a4f220f..35b07a1 100644
--- a/compiler.c
+++ b/compiler.c
@@ -1092,10 +1092,7 @@ uc_compiler_emit_variable_rw(uc_compiler_t *compiler, uc_value_t *varname, uc_to
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;
}
@@ -1153,6 +1150,100 @@ uc_compiler_emit_variable_rw(uc_compiler_t *compiler, uc_value_t *varname, uc_to
}
static void
+uc_compiler_emit_variable_copy(uc_compiler_t *compiler, uc_value_t *var)
+{
+ if (!var) {
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 1);
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 1);
+ }
+
+ uc_compiler_emit_variable_rw(compiler, var, 0);
+}
+
+static void
+uc_compiler_compile_and_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var)
+{
+ uc_chunk_t *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off;
+
+ if (assignment)
+ uc_compiler_emit_variable_copy(compiler, var);
+
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 0);
+ jmpz_off = uc_compiler_emit_jmpz(compiler, 0);
+ uc_compiler_emit_insn(compiler, 0, I_POP);
+
+ if (assignment) {
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN);
+ }
+ else {
+ uc_compiler_parse_precedence(compiler, P_AND);
+ }
+
+ uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+}
+
+static void
+uc_compiler_compile_or_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var)
+{
+ uc_chunk_t *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off, jmp_off;
+
+ if (assignment)
+ uc_compiler_emit_variable_copy(compiler, var);
+
+ uc_compiler_emit_insn(compiler, 0, I_COPY);
+ uc_compiler_emit_u8(compiler, 0, 0);
+ 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);
+
+ if (assignment) {
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN);
+ }
+ else {
+ uc_compiler_parse_precedence(compiler, P_OR);
+ }
+
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+}
+
+static void
+uc_compiler_compile_nullish_common(uc_compiler_t *compiler, bool assignment, uc_value_t *var)
+{
+ uc_chunk_t *chunk = uc_compiler_current_chunk(compiler);
+ size_t jmpz_off, jmp_off;
+
+ if (assignment)
+ uc_compiler_emit_variable_copy(compiler, var);
+
+ 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);
+
+ if (assignment) {
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ uc_compiler_emit_variable_rw(compiler, var, TK_ASSIGN);
+ }
+ else {
+ uc_compiler_parse_precedence(compiler, P_OR);
+ }
+
+ uc_compiler_set_jmpaddr(compiler, jmp_off, chunk->count);
+}
+
+static void
uc_compiler_compile_expression(uc_compiler_t *compiler)
{
uc_compiler_parse_precedence(compiler, P_COMMA);
@@ -1163,7 +1254,25 @@ uc_compiler_compile_assignment(uc_compiler_t *compiler, uc_value_t *var)
{
uc_tokentype_t type = compiler->parser->curr.type;
- if (uc_compiler_parse_at_assignment_op(compiler)) {
+ if (type == TK_ASNULLISH) {
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_compile_nullish_common(compiler, true, var);
+
+ return true;
+ }
+ else if (type == TK_ASOR) {
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_compile_or_common(compiler, true, var);
+
+ return true;
+ }
+ else if (type == TK_ASAND) {
+ uc_compiler_parse_advance(compiler);
+ uc_compiler_compile_and_common(compiler, true, var);
+
+ return true;
+ }
+ else if (uc_compiler_parse_at_assignment_op(compiler)) {
uc_compiler_parse_advance(compiler);
uc_compiler_parse_precedence(compiler, P_ASSIGN);
uc_compiler_emit_variable_rw(compiler, var, type);
@@ -1750,49 +1859,19 @@ uc_compiler_compile_funcdecl(uc_compiler_t *compiler)
static void
uc_compiler_compile_and(uc_compiler_t *compiler)
{
- uc_chunk_t *chunk = uc_compiler_current_chunk(compiler);
- size_t jmpz_off;
-
- uc_compiler_emit_insn(compiler, 0, I_COPY);
- uc_compiler_emit_u8(compiler, 0, 0);
- jmpz_off = uc_compiler_emit_jmpz(compiler, 0);
- uc_compiler_emit_insn(compiler, 0, I_POP);
- uc_compiler_parse_precedence(compiler, P_AND);
- uc_compiler_set_jmpaddr(compiler, jmpz_off, chunk->count);
+ return uc_compiler_compile_and_common(compiler, false, NULL);
}
static void
uc_compiler_compile_or(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);
- 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);
+ return uc_compiler_compile_or_common(compiler, false, NULL);
}
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);
+ return uc_compiler_compile_nullish_common(compiler, false, NULL);
}
static void
diff --git a/tests/custom/00_syntax/25_and_or_assignment b/tests/custom/00_syntax/25_and_or_assignment
index 4dbc5f3..d6a9415 100644
--- a/tests/custom/00_syntax/25_and_or_assignment
+++ b/tests/custom/00_syntax/25_and_or_assignment
@@ -53,3 +53,33 @@ expression result if the lhs is falsy.
printf("%.J\n", [ x, y, z ]);
%}
-- End --
+
+
+3. Ensure that the assignment value expression is not evaluated if the
+assignment condition is false.
+
+-- Expect stdout --
+[
+ 0,
+ 0,
+ 0
+]
+-- End --
+
+-- Testcase --
+{%
+ a = 0;
+ b = 0;
+ c = 0;
+
+ x = false;
+ y = false;
+ z = true;
+
+ x ??= a++;
+ y &&= b++;
+ z ||= c++;
+
+ printf("%.J\n", [ a, b, c ]);
+%}
+-- End --
diff --git a/vm.c b/vm.c
index 3bf9836..5bc071b 100644
--- a/vm.c
+++ b/vm.c
@@ -1409,31 +1409,6 @@ 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;
@@ -1492,9 +1467,6 @@ 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);