From 1608a13b43873a1b85a25fe7b10343e55b96c2bf Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Sat, 9 Nov 2024 17:41:29 +1100 Subject: [PATCH] Implemented assert keyword, read more The assert keyword works, but I want to add a cmd option to suppress or disable the errors. The tests need some serious TLC. I know that, but I'm kicking it down the road for now. --- source/toy_ast.c | 10 +++++ source/toy_ast.h | 9 ++++ source/toy_opcodes.h | 1 + source/toy_parser.c | 29 ++++++++++--- source/toy_routine.c | 20 +++++++++ source/toy_value.c | 84 ++++++++++++++++++++++++++++++------ source/toy_value.h | 4 ++ source/toy_vm.c | 97 ++++++++++++++++++++---------------------- tests/cases/test_ast.c | 2 + 9 files changed, 187 insertions(+), 69 deletions(-) diff --git a/source/toy_ast.c b/source/toy_ast.c index 540463f..521cda1 100644 --- a/source/toy_ast.c +++ b/source/toy_ast.c @@ -95,6 +95,16 @@ void Toy_private_emitAstCompound(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, (*astHandle) = tmp; } +void Toy_private_emitAstAssert(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* child, Toy_Ast* msg) { + Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); + + tmp->type = TOY_AST_ASSERT; + tmp->assert.child = child; + tmp->assert.message = msg; + + (*astHandle) = tmp; +} + void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) { Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); diff --git a/source/toy_ast.h b/source/toy_ast.h index 296ad9a..990a57b 100644 --- a/source/toy_ast.h +++ b/source/toy_ast.h @@ -17,6 +17,7 @@ typedef enum Toy_AstType { TOY_AST_GROUP, TOY_AST_COMPOUND, + TOY_AST_ASSERT, TOY_AST_PRINT, TOY_AST_VAR_DECLARE, @@ -116,6 +117,12 @@ typedef struct Toy_AstCompound { Toy_Ast* right; } Toy_AstCompound; +typedef struct Toy_AstAssert { + Toy_AstType type; + Toy_Ast* child; + Toy_Ast* message; +} Toy_AstAssert; + typedef struct Toy_AstPrint { Toy_AstType type; Toy_Ast* child; @@ -160,6 +167,7 @@ union Toy_Ast { //32 | 64 BITNESS Toy_AstCompare compare; //16 | 24 Toy_AstGroup group; //8 | 16 Toy_AstCompound compound; //16 | 24 + Toy_AstAssert assert; //16 | 24 Toy_AstPrint print; //8 | 16 Toy_AstVarDeclare varDeclare; //16 | 24 Toy_AstVarAssign varAssign; //16 | 24 @@ -179,6 +187,7 @@ void Toy_private_emitAstCompare(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,T void Toy_private_emitAstGroup(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstCompound(Toy_Bucket** bucketHandle, Toy_Ast** astHandle,Toy_AstFlag flag, Toy_Ast* right); +void Toy_private_emitAstAssert(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* child, Toy_Ast* msg); void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstVariableDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_Ast* expr); diff --git a/source/toy_opcodes.h b/source/toy_opcodes.h index 66f9f69..9f19cf3 100644 --- a/source/toy_opcodes.h +++ b/source/toy_opcodes.h @@ -37,6 +37,7 @@ typedef enum Toy_OpcodeType { TOY_OPCODE_SCOPE_POP, //various action instructions + TOY_OPCODE_ASSERT, TOY_OPCODE_PRINT, TOY_OPCODE_CONCAT, TOY_OPCODE_INDEX, diff --git a/source/toy_parser.c b/source/toy_parser.c index 5c1f959..daeb123 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -195,7 +195,7 @@ static ParsingTuple parsingRulesetTable[] = { //structural operators {PREC_NONE,group,NULL},// TOY_TOKEN_OPERATOR_PAREN_LEFT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_PAREN_RIGHT, - {PREC_CALL,NULL,compound},// TOY_TOKEN_OPERATOR_BRACKET_LEFT, + {PREC_GROUP,NULL,compound},// TOY_TOKEN_OPERATOR_BRACKET_LEFT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACKET_RIGHT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACE_LEFT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACE_RIGHT, @@ -208,7 +208,7 @@ static ParsingTuple parsingRulesetTable[] = { {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_COLON, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_SEMICOLON, // ; - {PREC_CALL,NULL,compound},// TOY_TOKEN_OPERATOR_COMMA, // , + {PREC_GROUP,NULL,compound},// TOY_TOKEN_OPERATOR_COMMA, // , {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_DOT, // . {PREC_CALL,NULL,binary},// TOY_TOKEN_OPERATOR_CONCAT, // .. @@ -563,11 +563,11 @@ static Toy_AstFlag compound(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_A advance(parser); if (parser->previous.type == TOY_TOKEN_OPERATOR_COMMA) { - parsePrecedence(bucketHandle, parser, rootHandle, PREC_ASSIGNMENT + 1); + parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); return TOY_AST_FLAG_COMPOUND_COLLECTION; } else if (parser->previous.type == TOY_TOKEN_OPERATOR_BRACKET_LEFT) { - parsePrecedence(bucketHandle, parser, rootHandle, PREC_ASSIGNMENT + 1); + parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); consume(parser, TOY_TOKEN_OPERATOR_BRACKET_RIGHT, "Expected ']' at the end of index expression"); return TOY_AST_FLAG_COMPOUND_INDEX; } @@ -655,6 +655,21 @@ static void makeExpr(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro //forward declarations static void makeBlockStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle); +static void makeAssertStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { + Toy_Ast* ast = NULL; //assert's emit function is a bit different + makeExpr(bucketHandle, parser, &ast); + + //NOTE: if it's a compound, then it's got a second arg + if (ast->type == TOY_AST_COMPOUND) { + Toy_private_emitAstAssert(bucketHandle, rootHandle, ast->compound.left, ast->compound.right); + } + else { + Toy_private_emitAstAssert(bucketHandle, rootHandle, ast, NULL); + } + + consume(parser, TOY_TOKEN_OPERATOR_SEMICOLON, "Expected ';' at the end of assert statement"); +} + static void makePrintStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { makeExpr(bucketHandle, parser, rootHandle); Toy_private_emitAstPrint(bucketHandle, rootHandle); @@ -715,7 +730,11 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro return; } - //assert + else if (match(parser, TOY_TOKEN_KEYWORD_ASSERT)) { + makeAssertStmt(bucketHandle, parser, rootHandle); + return; + } + //if-then-else //while-then //for-pre-clause-post-then diff --git a/source/toy_routine.c b/source/toy_routine.c index 9ff1297..a72efb0 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -280,6 +280,22 @@ static unsigned int writeInstructionCompound(Toy_Routine** rt, Toy_AstCompound a } } +static unsigned int writeInstructionAssert(Toy_Routine** rt, Toy_AstAssert ast) { + //the thing to print + writeRoutineCode(rt, ast.child); + writeRoutineCode(rt, ast.message); + + //output the print opcode + EMIT_BYTE(rt, code, TOY_OPCODE_ASSERT); + + //4-byte alignment + EMIT_BYTE(rt, code, ast.message != NULL ? 2 : 1); //arg count + EMIT_BYTE(rt, code,0); + EMIT_BYTE(rt, code,0); + + return 0; +} + static unsigned int writeInstructionPrint(Toy_Routine** rt, Toy_AstPrint ast) { //the thing to print writeRoutineCode(rt, ast.child); @@ -504,6 +520,10 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { result += writeInstructionCompound(rt, ast->compound); break; + case TOY_AST_ASSERT: + result += writeInstructionAssert(rt, ast->assert); + break; + case TOY_AST_PRINT: result += writeInstructionPrint(rt, ast->print); break; diff --git a/source/toy_value.c b/source/toy_value.c index 1d43ee2..33c9f7d 100644 --- a/source/toy_value.c +++ b/source/toy_value.c @@ -2,7 +2,9 @@ #include "toy_console_colors.h" #include "toy_string.h" -#include "toy_print.h" + +#include +#include //utils static unsigned int hashUInt(unsigned int x) { @@ -37,10 +39,10 @@ unsigned int Toy_hashValue(Toy_Value value) { case TOY_VALUE_TYPE: case TOY_VALUE_ANY: case TOY_VALUE_UNKNOWN: - break; + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't hash an unknown value type, exiting\n" TOY_CC_RESET); + exit(-1); } - Toy_error(TOY_CC_ERROR "ERROR: Can't hash an unknown value type\n" TOY_CC_RESET); return 0; } @@ -64,11 +66,11 @@ Toy_Value Toy_copyValue(Toy_Value value) { case TOY_VALUE_TYPE: case TOY_VALUE_ANY: case TOY_VALUE_UNKNOWN: - break; + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't copy an unknown value type, exiting\n" TOY_CC_RESET); + exit(-1); } //dummy return - Toy_error(TOY_CC_ERROR "ERROR: Can't copy an unknown value type\n" TOY_CC_RESET); return TOY_VALUE_FROM_NULL(); } @@ -93,14 +95,15 @@ void Toy_freeValue(Toy_Value value) { case TOY_VALUE_TYPE: case TOY_VALUE_ANY: case TOY_VALUE_UNKNOWN: - Toy_error(TOY_CC_ERROR "ERROR: Can't free an unknown type\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't free an unknown value type, exiting\n" TOY_CC_RESET); + exit(-1); } } bool Toy_checkValueIsTruthy(Toy_Value value) { //null is an error if (TOY_VALUE_IS_NULL(value)) { - Toy_error(TOY_CC_ERROR "ERROR: 'null' is neither true nor false\n" TOY_CC_RESET); + Toy_error("'null' is neither true nor false"); return false; } @@ -158,10 +161,10 @@ bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right) { case TOY_VALUE_TYPE: case TOY_VALUE_ANY: case TOY_VALUE_UNKNOWN: - break; + fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown types in value equality, exiting\n" TOY_CC_RESET); + exit(-1); } - Toy_error(TOY_CC_ERROR "ERROR: Unknown types in value equality\n" TOY_CC_RESET); return false; } @@ -187,10 +190,10 @@ bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right) { case TOY_VALUE_TYPE: case TOY_VALUE_ANY: case TOY_VALUE_UNKNOWN: - break; + fprintf(stderr, TOY_CC_ERROR "Unknown types in value comparison check, exiting\n" TOY_CC_RESET); + exit(-1); } - Toy_error(TOY_CC_ERROR "ERROR: Unknown types in value comparison check\n" TOY_CC_RESET); return false; } @@ -235,9 +238,64 @@ int Toy_compareValues(Toy_Value left, Toy_Value right) { case TOY_VALUE_TYPE: case TOY_VALUE_ANY: case TOY_VALUE_UNKNOWN: - break; + fprintf(stderr, TOY_CC_ERROR "Unknown types in value comparison, exiting\n" TOY_CC_RESET); + exit(-1); } - Toy_error(TOY_CC_ERROR "ERROR: Unknown types in value comparison\n" TOY_CC_RESET); return -1; } + +void Toy_stringifyValue(Toy_Value value, Toy_callbackType callback) { + //NOTE: don't append a newline + switch(value.type) { + case TOY_VALUE_NULL: + callback("null"); + break; + + case TOY_VALUE_BOOLEAN: + callback(TOY_VALUE_AS_BOOLEAN(value) ? "true" : "false"); + break; + + case TOY_VALUE_INTEGER: { + char buffer[16]; + sprintf(buffer, "%d", TOY_VALUE_AS_INTEGER(value)); + callback(buffer); + break; + } + + case TOY_VALUE_FLOAT: { + char buffer[16]; + sprintf(buffer, "%f", TOY_VALUE_AS_FLOAT(value)); + callback(buffer); + break; + } + + case TOY_VALUE_STRING: { + Toy_String* str = TOY_VALUE_AS_STRING(value); + + //TODO: decide on how long strings, etc. live for in memory + if (str->type == TOY_STRING_NODE) { + char* buffer = Toy_getStringRawBuffer(str); + callback(buffer); + free(buffer); + } + else if (str->type == TOY_STRING_LEAF) { + callback(str->as.leaf.data); + } + else if (str->type == TOY_STRING_NAME) { + callback(str->as.name.data); //should this be a thing? + } + break; + } + + case TOY_VALUE_ARRAY: + case TOY_VALUE_TABLE: + case TOY_VALUE_FUNCTION: + case TOY_VALUE_OPAQUE: + case TOY_VALUE_TYPE: + case TOY_VALUE_ANY: + case TOY_VALUE_UNKNOWN: + fprintf(stderr, TOY_CC_ERROR "Unknown types in value stringify, exiting\n" TOY_CC_RESET); + exit(-1); + } +} diff --git a/source/toy_value.h b/source/toy_value.h index 2606ca8..61569d3 100644 --- a/source/toy_value.h +++ b/source/toy_value.h @@ -1,6 +1,7 @@ #pragma once #include "toy_common.h" +#include "toy_print.h" //forward declarations struct Toy_String; @@ -67,3 +68,6 @@ TOY_API bool Toy_checkValueIsTruthy(Toy_Value value); TOY_API bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right); TOY_API bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right); TOY_API int Toy_compareValues(Toy_Value left, Toy_Value right); + +//convert the value to a string, then forward it to a callback +TOY_API void Toy_stringifyValue(Toy_Value value, Toy_callbackType callback); diff --git a/source/toy_vm.c b/source/toy_vm.c index a0d5f44..d8c25ce 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -177,6 +177,7 @@ static void processAssign(Toy_VM* vm) { //cleanup Toy_freeValue(name); } + static void processAccess(Toy_VM* vm) { Toy_Value name = Toy_popStack(&vm->stack); @@ -348,62 +349,42 @@ static void processLogical(Toy_VM* vm, Toy_OpcodeType opcode) { } } -static void processPrint(Toy_VM* vm) { - //print the value on top of the stack, popping it - Toy_Value value = Toy_popStack(&vm->stack); - - //NOTE: don't append a newline - leave that choice to the host - switch(value.type) { - case TOY_VALUE_NULL: - Toy_print("null"); - break; +static void processAssert(Toy_VM* vm) { + unsigned int count = READ_BYTE(vm); - case TOY_VALUE_BOOLEAN: - Toy_print(TOY_VALUE_AS_BOOLEAN(value) ? "true" : "false"); - break; - - case TOY_VALUE_INTEGER: { - char buffer[16]; - sprintf(buffer, "%d", TOY_VALUE_AS_INTEGER(value)); - Toy_print(buffer); - break; - } + Toy_Value value = TOY_VALUE_FROM_NULL(); + Toy_Value message = TOY_VALUE_FROM_NULL(); - case TOY_VALUE_FLOAT: { - char buffer[16]; - sprintf(buffer, "%f", TOY_VALUE_AS_FLOAT(value)); - Toy_print(buffer); - break; - } + //determine the args + if (count == 1) { + message = TOY_VALUE_FROM_STRING(Toy_createString(&vm->stringBucket, "assertion failed")); + value = Toy_popStack(&vm->stack); + } + else if (count == 2) { + message = Toy_popStack(&vm->stack); + value = Toy_popStack(&vm->stack); + } + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid assert argument count %d found, exiting\n" TOY_CC_RESET, (int)count); + exit(-1); + } - case TOY_VALUE_STRING: { - Toy_String* str = TOY_VALUE_AS_STRING(value); + //do the check + if (TOY_VALUE_IS_NULL(value) || Toy_checkValueIsTruthy(value) == false) { + //on a failure, print the message + Toy_stringifyValue(message, Toy_error); + } - //TODO: decide on how long strings, etc. live for in memory - if (str->type == TOY_STRING_NODE) { - char* buffer = Toy_getStringRawBuffer(str); - Toy_print(buffer); - free(buffer); - } - else if (str->type == TOY_STRING_LEAF) { - Toy_print(str->as.leaf.data); - } - else if (str->type == TOY_STRING_NAME) { - Toy_print(str->as.name.data); //should this be a thing? - } - break; - } + //cleanup + Toy_freeValue(value); + Toy_freeValue(message); +} - case TOY_VALUE_ARRAY: - case TOY_VALUE_TABLE: - case TOY_VALUE_FUNCTION: - case TOY_VALUE_OPAQUE: - case TOY_VALUE_TYPE: - case TOY_VALUE_ANY: - case TOY_VALUE_UNKNOWN: - fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown value type %d passed to processPrint, exiting\n" TOY_CC_RESET, value.type); - exit(-1); - } +static void processPrint(Toy_VM* vm) { + //print the value on top of the stack, popping it + Toy_Value value = Toy_popStack(&vm->stack); + Toy_stringifyValue(value, Toy_print); + Toy_freeValue(value); } static void processConcat(Toy_VM* vm) { @@ -447,11 +428,17 @@ static void processIndex(Toy_VM* vm) { //type checks if (!TOY_VALUE_IS_INTEGER(index)) { Toy_error("Failed to index a string"); + Toy_freeValue(value); + Toy_freeValue(index); + Toy_freeValue(length); return; } if (!(TOY_VALUE_IS_NULL(length) || TOY_VALUE_IS_INTEGER(length))) { Toy_error("Failed to index-length a string"); + Toy_freeValue(value); + Toy_freeValue(index); + Toy_freeValue(length); return; } @@ -486,6 +473,10 @@ static void processIndex(Toy_VM* vm) { fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown value type %d found in processIndex, exiting\n" TOY_CC_RESET, value.type); exit(-1); } + + Toy_freeValue(value); + Toy_freeValue(index); + Toy_freeValue(length); } static void process(Toy_VM* vm) { @@ -554,6 +545,10 @@ static void process(Toy_VM* vm) { break; //various action instructions + case TOY_OPCODE_ASSERT: + processAssert(vm); + break; + case TOY_OPCODE_PRINT: processPrint(vm); break; diff --git a/tests/cases/test_ast.c b/tests/cases/test_ast.c index 7b76119..cee2bce 100644 --- a/tests/cases/test_ast.c +++ b/tests/cases/test_ast.c @@ -23,6 +23,7 @@ int test_sizeof_ast_64bit() { TEST_SIZEOF(Toy_AstCompare, 24); TEST_SIZEOF(Toy_AstGroup, 16); TEST_SIZEOF(Toy_AstCompound, 24); + TEST_SIZEOF(Toy_AstAssert, 24); TEST_SIZEOF(Toy_AstPrint, 16); TEST_SIZEOF(Toy_AstVarDeclare, 24); TEST_SIZEOF(Toy_AstVarAssign, 24); @@ -56,6 +57,7 @@ int test_sizeof_ast_32bit() { TEST_SIZEOF(Toy_AstCompare, 16); TEST_SIZEOF(Toy_AstGroup, 8); TEST_SIZEOF(Toy_AstCompound, 16); + TEST_SIZEOF(Toy_AstAssert, 12); TEST_SIZEOF(Toy_AstPrint, 8); TEST_SIZEOF(Toy_AstVarDeclare, 12); TEST_SIZEOF(Toy_AstVarAssign, 16);