Fixed logical AND and OR operators, read more

The definition of '&&':
  Return the first falsy value, or the last value, skipping the evaluation of other operands.

The definition of '||':
  Return the first truthy value, or the last value, skipping the evaluation of other operands.

Toy now follows these definitions.

Fixed #154
This commit is contained in:
2024-12-26 16:09:16 +11:00
parent 153ab54be6
commit cc4ff3f6d8
11 changed files with 279 additions and 40 deletions

View File

@@ -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";
}

View File

@@ -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));

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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;