diff --git a/scripts/valgrind.toy b/scripts/valgrind.toy index e69de29..3342a26 100644 --- a/scripts/valgrind.toy +++ b/scripts/valgrind.toy @@ -0,0 +1,64 @@ +//logical short-circuits and chained assignments + +//logical AND +{ + var a = 1; + var b = 2; + var c = a + 1 && b + 2; + + assert a == 1, "short circuit 1.1"; + assert b == 2, "short circuit 1.2"; + assert c == 4, "short circuit 1.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = (a + 1) && b + 2; + + assert a == 4, "short circuit 2.1"; + assert b == 2, "short circuit 2.2"; + assert c == 4, "short circuit 2.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = a + 1 && b + 2; + + assert a == 4, "short circuit 3.1"; + assert b == 2, "short circuit 3.2"; + assert c == 4, "short circuit 3.3"; +} + + +//logical OR +{ + var a = 1; + var b = 2; + var c = a + 1 || b + 2; + + assert a == 1, "short circuit 4.1"; + assert b == 2, "short circuit 4.2"; + assert c == 2, "short circuit 4.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = (a + 1) || b + 2; + + assert a == 2, "short circuit 5.1"; + assert b == 2, "short circuit 5.2"; + assert c == 2, "short circuit 5.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = a + 1 || b + 2; + + assert a == 2, "short circuit 6.1"; + assert b == 2, "short circuit 6.2"; + assert c == 2, "short circuit 6.3"; +} diff --git a/source/toy_ast.c b/source/toy_ast.c index 19a1544..ef88d71 100644 --- a/source/toy_ast.c +++ b/source/toy_ast.c @@ -64,6 +64,17 @@ void Toy_private_emitAstBinary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, T (*astHandle) = tmp; } +void Toy_private_emitAstBinaryShortCircuit(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* right) { + Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); + + tmp->type = TOY_AST_BINARY_SHORT_CIRCUIT; + tmp->binary.flag = flag; + tmp->binary.left = *astHandle; //left-recursive + tmp->binary.right = right; + + (*astHandle) = tmp; +} + void Toy_private_emitAstCompare(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag, Toy_Ast* right) { Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); diff --git a/source/toy_ast.h b/source/toy_ast.h index 9509fbd..3b371af 100644 --- a/source/toy_ast.h +++ b/source/toy_ast.h @@ -13,6 +13,7 @@ typedef enum Toy_AstType { TOY_AST_VALUE, TOY_AST_UNARY, TOY_AST_BINARY, + TOY_AST_BINARY_SHORT_CIRCUIT, TOY_AST_COMPARE, TOY_AST_GROUP, TOY_AST_COMPOUND, @@ -106,6 +107,13 @@ typedef struct Toy_AstBinary { Toy_Ast* right; } Toy_AstBinary; +typedef struct Toy_AstBinaryShortCircuit { + Toy_AstType type; + Toy_AstFlag flag; + Toy_Ast* left; + Toy_Ast* right; +} Toy_AstBinaryShortCircuit; + typedef struct Toy_AstCompare { Toy_AstType type; Toy_AstFlag flag; @@ -193,29 +201,30 @@ typedef struct Toy_AstEnd { Toy_AstType type; } Toy_AstEnd; -union Toy_Ast { //32 | 64 BITNESS - Toy_AstType type; //4 | 4 - Toy_AstBlock block; //16 | 32 - Toy_AstValue value; //12 | 24 - Toy_AstUnary unary; //12 | 16 - Toy_AstBinary binary; //16 | 24 - Toy_AstCompare compare; //16 | 24 - Toy_AstGroup group; //8 | 16 - Toy_AstCompound compound; //12 | 16 - Toy_AstAggregate aggregate; //16 | 24 - Toy_AstAssert assert; //16 | 24 - Toy_AstIfThenElse ifThenElse; //16 | 32 - Toy_AstWhileThen whileThen; //16 | 24 - Toy_AstBreak breakPoint; //4 | 4 - Toy_AstContinue continuePoint; //4 | 4 - Toy_AstPrint print; //8 | 16 - Toy_AstVarDeclare varDeclare; //16 | 24 - Toy_AstVarAssign varAssign; //16 | 24 - Toy_AstVarAccess varAccess; //8 | 16 - Toy_AstPass pass; //4 | 4 - Toy_AstError error; //4 | 4 - Toy_AstEnd end; //4 | 4 -}; //16 | 32 +union Toy_Ast { //32 | 64 BITNESS + Toy_AstType type; //4 | 4 + Toy_AstBlock block; //16 | 32 + Toy_AstValue value; //12 | 24 + Toy_AstUnary unary; //12 | 16 + Toy_AstBinary binary; //16 | 24 + Toy_AstBinaryShortCircuit binaryShortCircuit; //16 | 24 + Toy_AstCompare compare; //16 | 24 + Toy_AstGroup group; //8 | 16 + Toy_AstCompound compound; //12 | 16 + Toy_AstAggregate aggregate; //16 | 24 + Toy_AstAssert assert; //16 | 24 + Toy_AstIfThenElse ifThenElse; //16 | 32 + Toy_AstWhileThen whileThen; //16 | 24 + Toy_AstBreak breakPoint; //4 | 4 + Toy_AstContinue continuePoint; //4 | 4 + Toy_AstPrint print; //8 | 16 + Toy_AstVarDeclare varDeclare; //16 | 24 + Toy_AstVarAssign varAssign; //16 | 24 + Toy_AstVarAccess varAccess; //8 | 16 + Toy_AstPass pass; //4 | 4 + Toy_AstError error; //4 | 4 + Toy_AstEnd end; //4 | 4 +}; //16 | 32 void Toy_private_initAstBlock(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_appendAstBlock(Toy_Bucket** bucketHandle, Toy_Ast* block, Toy_Ast* child); @@ -223,6 +232,7 @@ void Toy_private_appendAstBlock(Toy_Bucket** bucketHandle, Toy_Ast* block, Toy_A void Toy_private_emitAstValue(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Value value); void Toy_private_emitAstUnary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_AstFlag flag); void Toy_private_emitAstBinary(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right); +void Toy_private_emitAstBinaryShortCircuit(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right); void Toy_private_emitAstCompare(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right); void Toy_private_emitAstGroup(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstCompound(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag); diff --git a/source/toy_opcodes.h b/source/toy_opcodes.h index 99614f7..8f2a830 100644 --- a/source/toy_opcodes.h +++ b/source/toy_opcodes.h @@ -11,6 +11,7 @@ typedef enum Toy_OpcodeType { TOY_OPCODE_ASSIGN_COMPOUND, //assign to a compound's internals TOY_OPCODE_ACCESS, TOY_OPCODE_DUPLICATE, //duplicate the top of the stack + TOY_OPCODE_ELIMINATE, //remove the top of the stack //arithmetic instructions TOY_OPCODE_ADD, diff --git a/source/toy_parser.c b/source/toy_parser.c index 07c1abc..a39b014 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -708,7 +708,13 @@ static void parsePrecedence(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_A Toy_private_emitAstAggregate(bucketHandle, rootHandle, flag, ptr); } else { - Toy_private_emitAstBinary(bucketHandle, rootHandle, flag, ptr); + //BUGFIX: '&&' and '||' are special cases, with short-circuit logic + if (flag == TOY_AST_FLAG_AND || flag == TOY_AST_FLAG_OR) { + Toy_private_emitAstBinaryShortCircuit(bucketHandle, rootHandle, flag, ptr); + } + else { + Toy_private_emitAstBinary(bucketHandle, rootHandle, flag, ptr); + } } } diff --git a/source/toy_routine.c b/source/toy_routine.c index b45307a..3849a1a 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -192,13 +192,6 @@ static unsigned int writeInstructionBinary(Toy_Routine** rt, Toy_AstBinary ast) EMIT_BYTE(rt, code,TOY_OPCODE_MODULO); } - //nowhere to really put these for now - else if (ast.flag == TOY_AST_FLAG_AND) { - EMIT_BYTE(rt, code,TOY_OPCODE_AND); - } - else if (ast.flag == TOY_AST_FLAG_OR) { - EMIT_BYTE(rt, code,TOY_OPCODE_OR); - } else if (ast.flag == TOY_AST_FLAG_CONCAT) { EMIT_BYTE(rt, code, TOY_OPCODE_CONCAT); } @@ -215,6 +208,55 @@ static unsigned int writeInstructionBinary(Toy_Routine** rt, Toy_AstBinary ast) return 1; //leaves only 1 value on the stack } +static unsigned int writeInstructionBinaryShortCircuit(Toy_Routine** rt, Toy_AstBinaryShortCircuit ast) { + //lhs + writeRoutineCode(rt, ast.left); + + //duplicate the top (so the lhs can be 'returned' by this expression, if needed) + EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + + // && return the first falsy operand, or the last operand + if (ast.flag == TOY_AST_FLAG_AND) { + EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); + EMIT_BYTE(rt, code, 0); + } + + // || return the first truthy operand, or the last operand + else if (ast.flag == TOY_AST_FLAG_OR) { + EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_TRUE); + EMIT_BYTE(rt, code, 0); + } + + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST binary short circuit flag found\n" TOY_CC_RESET); + exit(-1); + } + + //parameter address + unsigned int endAddr = SKIP_INT(rt, code); //parameter to be written later + + //if the lhs value isn't needed, pop it + EMIT_BYTE(rt, code,TOY_OPCODE_ELIMINATE); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + + //rhs + writeRoutineCode(rt, ast.right); + + //set the parameter + OVERWRITE_INT(rt, code, endAddr, CURRENT_ADDRESS(rt, code) - (endAddr + 4)); + + return 1; //leaves only 1 value on the stack +} + static unsigned int writeInstructionCompare(Toy_Routine** rt, Toy_AstCompare ast) { //left, then right, then the compare's operation writeRoutineCode(rt, ast.left); @@ -683,6 +725,10 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { result += writeInstructionBinary(rt, ast->binary); break; + case TOY_AST_BINARY_SHORT_CIRCUIT: + result += writeInstructionBinaryShortCircuit(rt, ast->binaryShortCircuit); + break; + case TOY_AST_COMPARE: result += writeInstructionCompare(rt, ast->compare); break; diff --git a/source/toy_stack.c b/source/toy_stack.c index 22d12e2..763deba 100644 --- a/source/toy_stack.c +++ b/source/toy_stack.c @@ -29,6 +29,26 @@ void Toy_freeStack(Toy_Stack* stack) { } } +void Toy_resetStack(Toy_Stack** stackHandle) { + if ((*stackHandle) == NULL) { + return; + } + + //if some values will be removed, free them first + for (unsigned int i = 0; i < (*stackHandle)->count; i++) { + Toy_freeValue((*stackHandle)->data[i]); + } + + //reset to the stack's default state + if ((*stackHandle)->capacity > TOY_STACK_INITIAL_CAPACITY) { + (*stackHandle) = realloc((*stackHandle), TOY_STACK_INITIAL_CAPACITY * sizeof(Toy_Value) + sizeof(Toy_Stack)); + + (*stackHandle)->capacity = TOY_STACK_INITIAL_CAPACITY; + } + + (*stackHandle)->count = 0; +} + void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value) { //don't go overboard if ((*stackHandle)->count >= TOY_STACK_OVERFLOW_THRESHOLD) { @@ -58,7 +78,7 @@ void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value) { Toy_Value Toy_peekStack(Toy_Stack** stackHandle) { if ((*stackHandle)->count == 0) { - fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow when peeking\n" TOY_CC_RESET); exit(-1); } @@ -67,7 +87,7 @@ Toy_Value Toy_peekStack(Toy_Stack** stackHandle) { Toy_Value Toy_popStack(Toy_Stack** stackHandle) { if ((*stackHandle)->count == 0) { - fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow when popping\n" TOY_CC_RESET); exit(-1); } diff --git a/source/toy_stack.h b/source/toy_stack.h index 423e28d..ada5a71 100644 --- a/source/toy_stack.h +++ b/source/toy_stack.h @@ -11,6 +11,7 @@ typedef struct Toy_Stack { //32 | 64 BITNESS TOY_API Toy_Stack* Toy_allocateStack(void); TOY_API void Toy_freeStack(Toy_Stack* stack); +TOY_API void Toy_resetStack(Toy_Stack** stackHandle); TOY_API void Toy_pushStack(Toy_Stack** stackHandle, Toy_Value value); TOY_API Toy_Value Toy_peekStack(Toy_Stack** stackHandle); diff --git a/source/toy_vm.c b/source/toy_vm.c index e3f8db7..80440fc 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -229,7 +229,10 @@ static void processAssign(Toy_VM* vm) { } //assign it - Toy_assignScope(vm->scope, TOY_VALUE_AS_STRING(name), value); //scope now owns value, doesn't need to be freed + Toy_assignScope(vm->scope, TOY_VALUE_AS_STRING(name), value); //scope now owns the value, doesn't need to be freed + + //in case of chaining, leave a copy on the stack + Toy_pushStack(&vm->stack, Toy_copyValue(value)); //cleanup Toy_freeValue(name); @@ -276,6 +279,9 @@ static void processAssignCompound(Toy_VM* vm) { //set the value array->data[index] = Toy_copyValue(Toy_unwrapValue(value)); + //in case of chaining, leave a copy on the stack + Toy_pushStack(&vm->stack, Toy_copyValue(value)); + //cleanup Toy_freeValue(value); } @@ -286,6 +292,9 @@ static void processAssignCompound(Toy_VM* vm) { //set the value Toy_insertTable(&table, Toy_copyValue(Toy_unwrapValue(key)), Toy_copyValue(Toy_unwrapValue(value))); + //in case of chaining, leave a copy on the stack + Toy_pushStack(&vm->stack, Toy_copyValue(value)); + //cleanup Toy_freeValue(value); } @@ -343,6 +352,12 @@ static void processDuplicate(Toy_VM* vm) { } } +static void processEliminate(Toy_VM* vm) { + //discard the stack top + Toy_Value value = Toy_popStack(&vm->stack); + Toy_freeValue(value); +} + static void processArithmetic(Toy_VM* vm, Toy_OpcodeType opcode) { Toy_Value right = Toy_popStack(&vm->stack); Toy_Value left = Toy_popStack(&vm->stack); @@ -564,7 +579,7 @@ static void processAssert(Toy_VM* vm) { //determine the args if (count == 1) { - message = TOY_VALUE_FROM_STRING(Toy_createString(&vm->stringBucket, "assertion failed")); + message = TOY_VALUE_FROM_STRING(Toy_createString(&vm->stringBucket, "assertion failed")); //TODO: needs a better default message value = Toy_popStack(&vm->stack); } else if (count == 2) { @@ -827,6 +842,10 @@ static void process(Toy_VM* vm) { processDuplicate(vm); break; + case TOY_OPCODE_ELIMINATE: + processEliminate(vm); + break; + //arithmetic instructions case TOY_OPCODE_ADD: case TOY_OPCODE_SUBTRACT: @@ -999,8 +1018,6 @@ void Toy_freeVM(Toy_VM* vm) { Toy_popScope(vm->scope); Toy_freeBucket(&vm->stringBucket); Toy_freeBucket(&vm->scopeBucket); - - Toy_resetVM(vm); } void Toy_resetVM(Toy_VM* vm) { @@ -1020,5 +1037,7 @@ void Toy_resetVM(Toy_VM* vm) { vm->programCounter = 0; - //NOTE: stack, scope and memory are not altered during resets + Toy_resetStack(&vm->stack); + + //NOTE: scope and memory are not altered during resets } diff --git a/tests/cases/test_ast.c b/tests/cases/test_ast.c index 1cc729e..84bb20f 100644 --- a/tests/cases/test_ast.c +++ b/tests/cases/test_ast.c @@ -21,6 +21,7 @@ int test_sizeof_ast_64bit(void) { TEST_SIZEOF(Toy_AstValue, 24); TEST_SIZEOF(Toy_AstUnary, 16); TEST_SIZEOF(Toy_AstBinary, 24); + TEST_SIZEOF(Toy_AstBinaryShortCircuit, 24); TEST_SIZEOF(Toy_AstCompare, 24); TEST_SIZEOF(Toy_AstGroup, 16); TEST_SIZEOF(Toy_AstCompound, 16); @@ -60,6 +61,7 @@ int test_sizeof_ast_32bit(void) { TEST_SIZEOF(Toy_AstValue, 12); TEST_SIZEOF(Toy_AstUnary, 12); TEST_SIZEOF(Toy_AstBinary, 16); + TEST_SIZEOF(Toy_AstBinaryShortCircuit, 16); TEST_SIZEOF(Toy_AstCompare, 16); TEST_SIZEOF(Toy_AstGroup, 8); TEST_SIZEOF(Toy_AstCompound, 12); diff --git a/tests/integrations/test_variables.toy b/tests/integrations/test_variables.toy index 2938ead..6d5ea30 100644 --- a/tests/integrations/test_variables.toy +++ b/tests/integrations/test_variables.toy @@ -49,8 +49,67 @@ print false || false; //false print !true; //false print !false; //true -//precedence -print true && false || true; //URGENT: a grouping warning is needed for this, see issue #154 +//logical AND short-circuits and chained assignments +{ + var a = 1; + var b = 2; + var c = a + 1 && b + 2; + + assert a == 1, "short circuit 1.1"; + assert b == 2, "short circuit 1.2"; + assert c == 4, "short circuit 1.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = (a + 1) && b + 2; + + assert a == 4, "short circuit 2.1"; + assert b == 2, "short circuit 2.2"; + assert c == 4, "short circuit 2.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = a + 1 && b + 2; + + assert a == 4, "short circuit 3.1"; + assert b == 2, "short circuit 3.2"; + assert c == 4, "short circuit 3.3"; +} + +//logical OR short-circuits and chained assignments +{ + var a = 1; + var b = 2; + var c = a + 1 || b + 2; + + assert a == 1, "short circuit 4.1"; + assert b == 2, "short circuit 4.2"; + assert c == 2, "short circuit 4.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = (a + 1) || b + 2; + + assert a == 2, "short circuit 5.1"; + assert b == 2, "short circuit 5.2"; + assert c == 2, "short circuit 5.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = a + 1 || b + 2; + + assert a == 2, "short circuit 6.1"; + assert b == 2, "short circuit 6.2"; + assert c == 2, "short circuit 6.3"; +} //types var a: int;