Implemented tests for Toy_Parser

The parser now correctly produces a workable AST. I think I'll skip
over the optimizer, and begin on the compiler next session. The
optimizer will act directly on the AST, but it isn't totally necessary.

Other tools can also operate on the AST, such as for debugging - I'll
have to ask what kinds are out there.
This commit is contained in:
2024-09-13 18:08:52 +10:00
parent b00a6838be
commit 898b8efc04
6 changed files with 691 additions and 71 deletions

View File

@@ -82,3 +82,9 @@ void Toy_private_emitAstError(Toy_Bucket** bucket, Toy_Ast** handle) {
(*handle)->error.type = TOY_AST_ERROR; (*handle)->error.type = TOY_AST_ERROR;
} }
void Toy_private_emitAstEnd(Toy_Bucket** bucket, Toy_Ast** handle) {
(*handle) = (Toy_Ast*)Toy_partBucket(bucket, sizeof(Toy_Ast));
(*handle)->error.type = TOY_AST_END;
}

View File

@@ -16,6 +16,7 @@ typedef enum Toy_AstType {
TOY_AST_PASS, TOY_AST_PASS,
TOY_AST_ERROR, TOY_AST_ERROR,
TOY_AST_END,
} Toy_AstType; } Toy_AstType;
//flags are handled differently by different types //flags are handled differently by different types
@@ -64,6 +65,7 @@ void Toy_private_emitAstGroup(Toy_Bucket** bucket, Toy_Ast** handle);
void Toy_private_emitAstPass(Toy_Bucket** bucket, Toy_Ast** handle); void Toy_private_emitAstPass(Toy_Bucket** bucket, Toy_Ast** handle);
void Toy_private_emitAstError(Toy_Bucket** bucket, Toy_Ast** handle); void Toy_private_emitAstError(Toy_Bucket** bucket, Toy_Ast** handle);
void Toy_private_emitAstEnd(Toy_Bucket** bucket, Toy_Ast** handle);
typedef struct Toy_AstBlock { typedef struct Toy_AstBlock {
Toy_AstType type; Toy_AstType type;
@@ -85,8 +87,8 @@ typedef struct Toy_AstUnary {
typedef struct Toy_AstBinary { typedef struct Toy_AstBinary {
Toy_AstType type; Toy_AstType type;
Toy_Ast* left;
Toy_AstFlag flag; Toy_AstFlag flag;
Toy_Ast* left;
Toy_Ast* right; Toy_Ast* right;
} Toy_AstBinary; } Toy_AstBinary;
@@ -104,6 +106,10 @@ typedef struct Toy_AstError {
//TODO: more data regarding the error //TODO: more data regarding the error
} Toy_AstError; } Toy_AstError;
typedef struct Toy_AstEnd {
Toy_AstType type;
} Toy_AstEnd;
union Toy_Ast { union Toy_Ast {
Toy_AstType type; //4 Toy_AstType type; //4
Toy_AstBlock block; //12 Toy_AstBlock block; //12
@@ -113,4 +119,5 @@ union Toy_Ast {
Toy_AstGroup group; //8 Toy_AstGroup group; //8
Toy_AstPass pass; //4 Toy_AstPass pass; //4
Toy_AstError error; //4 Toy_AstError error; //4
Toy_AstEnd end; //4
}; //16 }; //16

View File

@@ -24,8 +24,8 @@
TOY_API void* Toy_reallocate(void* pointer, size_t oldSize, size_t newSize); TOY_API void* Toy_reallocate(void* pointer, size_t oldSize, size_t newSize);
//immobile "bucket" memory structure for custom allocators //immobile "bucket" memory structure for custom allocators
#define TOY_BUCKET_INIT(type, bucket, capacity) \ #define TOY_BUCKET_INIT(type, bucket, count) \
Toy_initBucket(&(bucket), sizeof(type)*(capacity)) Toy_initBucket(&(bucket), sizeof(type)*(count))
#define TOY_BUCKET_PART(type, bucket) \ #define TOY_BUCKET_PART(type, bucket) \
(type*)Toy_partBucket(&(bucket), sizeof(type)) (type*)Toy_partBucket(&(bucket), sizeof(type))

View File

@@ -235,7 +235,8 @@ static Toy_AstFlag atomic(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** roo
do { do {
buffer[i] = parser->previous.lexeme[o]; buffer[i] = parser->previous.lexeme[o];
if (buffer[i] != '_') i++; if (buffer[i] != '_') i++;
} while (parser->previous.lexeme[o++]); } while (parser->previous.lexeme[o++] && i < parser->previous.length);
buffer[i] = '\0'; //BUGFIX
int value = 0; int value = 0;
sscanf(buffer, "%d", &value); sscanf(buffer, "%d", &value);
@@ -251,7 +252,8 @@ static Toy_AstFlag atomic(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** roo
do { do {
buffer[i] = parser->previous.lexeme[o]; buffer[i] = parser->previous.lexeme[o];
if (buffer[i] != '_') i++; if (buffer[i] != '_') i++;
} while (parser->previous.lexeme[o++]); } while (parser->previous.lexeme[o++] && i < parser->previous.length);
buffer[i] = '\0'; //BUGFIX
float value = 0; float value = 0;
sscanf(buffer, "%f", &value); sscanf(buffer, "%f", &value);
@@ -267,13 +269,37 @@ static Toy_AstFlag atomic(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** roo
} }
static Toy_AstFlag unary(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** root) { static Toy_AstFlag unary(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** root) {
if (parser->previous.type == TOY_TOKEN_OPERATOR_SUBTRACT || parser->previous.type == TOY_TOKEN_OPERATOR_NEGATE) { //'subtract' can only be applied to numbers and groups, while 'negate' can only be applied to booleans and groups
//read what to negate //this function takes the libery of peeking into the uppermost node, to see if it can apply this to it
if (parser->previous.type == TOY_TOKEN_OPERATOR_SUBTRACT) {
parsePrecedence(bucket, parser, root, PREC_UNARY); parsePrecedence(bucket, parser, root, PREC_UNARY);
//negative numbers
if ((*root)->type == TOY_AST_VALUE && TOY_VALUE_IS_INTEGER((*root)->value.value)) {
(*root)->value.value = TOY_VALUE_TO_INTEGER( -TOY_VALUE_AS_INTEGER((*root)->value.value) );
}
else if ((*root)->type == TOY_AST_VALUE && TOY_VALUE_IS_FLOAT((*root)->value.value)) {
(*root)->value.value = TOY_VALUE_TO_FLOAT( -TOY_VALUE_AS_FLOAT((*root)->value.value) );
}
else {
//actually emit the negation node //actually emit the negation node
Toy_private_emitAstUnary(bucket, root, TOY_AST_FLAG_NEGATE); Toy_private_emitAstUnary(bucket, root, TOY_AST_FLAG_NEGATE);
} }
}
else if (parser->previous.type == TOY_TOKEN_OPERATOR_NEGATE) {
parsePrecedence(bucket, parser, root, PREC_UNARY);
//inverted booleans
if ((*root)->type == TOY_AST_VALUE && TOY_VALUE_IS_BOOLEAN((*root)->value.value)) {
(*root)->value.value = TOY_VALUE_TO_BOOLEAN( !TOY_VALUE_AS_BOOLEAN((*root)->value.value) );
}
else {
//actually emit the negation node
Toy_private_emitAstUnary(bucket, root, TOY_AST_FLAG_NEGATE);
}
}
else { else {
printError(parser, parser->previous, "Unexpected token passed to unary precedence rule"); printError(parser, parser->previous, "Unexpected token passed to unary precedence rule");
@@ -300,17 +326,17 @@ static Toy_AstFlag binary(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** roo
} }
case TOY_TOKEN_OPERATOR_MULTIPLY: { case TOY_TOKEN_OPERATOR_MULTIPLY: {
parsePrecedence(bucket, parser, root, PREC_TERM + 1); parsePrecedence(bucket, parser, root, PREC_FACTOR + 1);
return TOY_AST_FLAG_MULTIPLY; return TOY_AST_FLAG_MULTIPLY;
} }
case TOY_TOKEN_OPERATOR_DIVIDE: { case TOY_TOKEN_OPERATOR_DIVIDE: {
parsePrecedence(bucket, parser, root, PREC_TERM + 1); parsePrecedence(bucket, parser, root, PREC_FACTOR + 1);
return TOY_AST_FLAG_DIVIDE; return TOY_AST_FLAG_DIVIDE;
} }
case TOY_TOKEN_OPERATOR_MODULO: { case TOY_TOKEN_OPERATOR_MODULO: {
parsePrecedence(bucket, parser, root, PREC_TERM + 1); parsePrecedence(bucket, parser, root, PREC_FACTOR + 1);
return TOY_AST_FLAG_MODULO; return TOY_AST_FLAG_MODULO;
} }
@@ -524,17 +550,20 @@ static void makeBlockStmt(Toy_Bucket** bucket, Toy_Parser* parser, Toy_Ast** roo
void Toy_bindParser(Toy_Parser* parser, Toy_Lexer* lexer) { void Toy_bindParser(Toy_Parser* parser, Toy_Lexer* lexer) {
Toy_resetParser(parser); Toy_resetParser(parser);
parser->lexer = lexer; parser->lexer = lexer;
advance(parser);
} }
Toy_Ast* Toy_scanParser(Toy_Bucket** bucket, Toy_Parser* parser) { Toy_Ast* Toy_scanParser(Toy_Bucket** bucket, Toy_Parser* parser) {
Toy_Ast* root = NULL;
//check for EOF //check for EOF
if (match(parser, TOY_TOKEN_EOF)) { if (match(parser, TOY_TOKEN_EOF)) {
return NULL; Toy_private_emitAstEnd(bucket, &root);
return root;
} }
//TODO: better errors, check for unbound parser, etc. //TODO: better errors, check for unbound parser, etc.
Toy_Ast* root = NULL;
makeBlockStmt(bucket, parser, &root); makeBlockStmt(bucket, parser, &root);
return root; return root;

View File

@@ -29,13 +29,9 @@ int test_sizeof_ast_32bit() {
return -err; return -err;
} }
int test_type_emission() { int test_type_emission(Toy_Bucket* bucket) {
//emit value //emit value
{ {
//bucket setup
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 8);
//emit to an AST //emit to an AST
Toy_Ast* ast = NULL; Toy_Ast* ast = NULL;
Toy_private_emitAstValue(&bucket, &ast, TOY_VALUE_TO_INTEGER(42)); Toy_private_emitAstValue(&bucket, &ast, TOY_VALUE_TO_INTEGER(42));
@@ -49,17 +45,10 @@ int test_type_emission() {
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a value as 'Toy_Ast', state unknown\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a value as 'Toy_Ast', state unknown\n" TOY_CC_RESET);
return -1; return -1;
} }
//bucket free
TOY_BUCKET_FREE(bucket);
} }
//emit unary //emit unary
{ {
//bucket setup
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 8);
//build the AST //build the AST
Toy_Ast* ast = NULL; Toy_Ast* ast = NULL;
Toy_private_emitAstValue(&bucket, &ast, TOY_VALUE_TO_INTEGER(42)); Toy_private_emitAstValue(&bucket, &ast, TOY_VALUE_TO_INTEGER(42));
@@ -76,17 +65,10 @@ int test_type_emission() {
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a unary as 'Toy_Ast', state unknown\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a unary as 'Toy_Ast', state unknown\n" TOY_CC_RESET);
return -1; return -1;
} }
//bucket free
TOY_BUCKET_FREE(bucket);
} }
//emit binary //emit binary
{ {
//bucket setup
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 8);
//build the AST //build the AST
Toy_Ast* ast = NULL; Toy_Ast* ast = NULL;
Toy_Ast* right = NULL; Toy_Ast* right = NULL;
@@ -107,17 +89,10 @@ int test_type_emission() {
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a binary as 'Toy_Ast', state unknown\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a binary as 'Toy_Ast', state unknown\n" TOY_CC_RESET);
return -1; return -1;
} }
//bucket free
TOY_BUCKET_FREE(bucket);
} }
//emit group //emit group
{ {
//bucket setup
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 8);
//build the AST //build the AST
Toy_Ast* ast = NULL; Toy_Ast* ast = NULL;
Toy_Ast* right = NULL; Toy_Ast* right = NULL;
@@ -141,17 +116,10 @@ int test_type_emission() {
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a group as 'Toy_Ast', state unknown\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_ERROR "ERROR: failed to emit a group as 'Toy_Ast', state unknown\n" TOY_CC_RESET);
return -1; return -1;
} }
//bucket free
TOY_BUCKET_FREE(bucket);
} }
//emit and append blocks of code //emit and append blocks of code
{ {
//bucket setup
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 8);
//initialize the root block //initialize the root block
Toy_Ast* block = NULL; Toy_Ast* block = NULL;
Toy_private_initAstBlock(&bucket, &block); Toy_private_initAstBlock(&bucket, &block);
@@ -191,23 +159,6 @@ int test_type_emission() {
iter = iter->block.next; iter = iter->block.next;
} }
//additional check: count the bucket's total allocations
Toy_Bucket* biter = bucket;
int total = 0;
while(biter != NULL) {
total += biter->count;
biter = biter->next;
}
if (total != 25 * (int)sizeof(Toy_Ast)) {
fprintf(stderr, TOY_CC_ERROR "ERROR: unexpected number of allocations found in a bucket, expected %d, found %d\n" TOY_CC_RESET, 25 * (int)sizeof(Toy_Ast), total);
return -1;
}
//bucket free
TOY_BUCKET_FREE(bucket);
} }
return 0; return 0;
@@ -228,12 +179,16 @@ int main() {
fprintf(stderr, TOY_CC_WARN "WARNING: Skipping test_sizeof_ast_32bit(); Can't determine the 'bitness' of this platform\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_WARN "WARNING: Skipping test_sizeof_ast_32bit(); Can't determine the 'bitness' of this platform\n" TOY_CC_RESET);
#endif #endif
res = test_type_emission(); {
total += res; Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 32);
res = test_type_emission(bucket);
TOY_BUCKET_FREE(bucket);
if (res == 0) { if (res == 0) {
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
} }
total += res;
}
return total; return total;
} }

View File

@@ -3,7 +3,630 @@
#include <stdio.h> #include <stdio.h>
int main() { //utils
fprintf(stderr, TOY_CC_WARN "WARNING: Test suite for Toy_Parser is not yet implemented\n" TOY_CC_RESET); Toy_Ast* makeAstFromSource(Toy_Bucket** bucket, const char* source) {
Toy_Lexer lexer;
Toy_bindLexer(&lexer, source);
Toy_Parser parser;
Toy_bindParser(&parser, &lexer);
return Toy_scanParser(bucket, &parser);
}
//tests
int test_simple_empty_parsers(Toy_Bucket* bucket) {
//simple parser setup and cleanup
{
//raw source code and lexer
const char* source = "";
Toy_Lexer lexer;
Toy_bindLexer(&lexer, source);
//parser
Toy_Parser parser;
Toy_bindParser(&parser, &lexer);
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_END)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with empty source\n" TOY_CC_RESET);
return -1;
}
}
//repeat above, but with one semicolon
{
//raw source code and lexer
const char* source = ";";
Toy_Lexer lexer;
Toy_bindLexer(&lexer, source);
//parser
Toy_Parser parser;
Toy_bindParser(&parser, &lexer);
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_PASS)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with one semicolon\n" TOY_CC_RESET);
return -1;
}
}
//repeat above, but with multiple semicolons
{
//raw source code and lexer
const char* source = ";;;;;";
Toy_Lexer lexer;
Toy_bindLexer(&lexer, source);
//parser
Toy_Parser parser;
Toy_bindParser(&parser, &lexer);
Toy_Ast* ast = Toy_scanParser(&bucket, &parser);
Toy_Ast* iter = ast;
while(iter != NULL) {
//check each link and child
if (
iter == NULL ||
iter->type != TOY_AST_BLOCK ||
iter->block.child == NULL ||
iter->block.child->type != TOY_AST_PASS)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with multiple semicolons\n" TOY_CC_RESET);
return -1;
}
iter = iter->block.next;
}
}
return 0; return 0;
} }
int test_values(Toy_Bucket* bucket) {
//test boolean true
{
Toy_Ast* ast = makeAstFromSource(&bucket, "true;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_BOOLEAN(ast->block.child->value.value) == false ||
TOY_VALUE_AS_BOOLEAN(ast->block.child->value.value) != true)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with boolean value true\n" TOY_CC_RESET);
return -1;
}
}
//test boolean false (just to be safe)
{
Toy_Ast* ast = makeAstFromSource(&bucket, "false;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_BOOLEAN(ast->block.child->value.value) == false ||
TOY_VALUE_AS_BOOLEAN(ast->block.child->value.value) != false)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with boolean value false\n" TOY_CC_RESET);
return -1;
}
}
//test integer
{
Toy_Ast* ast = makeAstFromSource(&bucket, "42;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->value.value) != 42)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with integer value 42\n" TOY_CC_RESET);
return -1;
}
}
//test float
{
Toy_Ast* ast = makeAstFromSource(&bucket, "3.1415;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_FLOAT(ast->block.child->value.value) == false ||
TOY_VALUE_AS_FLOAT(ast->block.child->value.value) != 3.1415f)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with float value 3.1415\n" TOY_CC_RESET);
return -1;
}
}
//test integer with separators
{
Toy_Ast* ast = makeAstFromSource(&bucket, "1_234_567_890;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->value.value) != 1234567890)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with integer value 42 with separators\n" TOY_CC_RESET);
return -1;
}
}
//test float with separators
{
Toy_Ast* ast = makeAstFromSource(&bucket, "3.141_592_65;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_FLOAT(ast->block.child->value.value) == false ||
TOY_VALUE_AS_FLOAT(ast->block.child->value.value) != 3.14159265f)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with float value 3.1415 with separators\n" TOY_CC_RESET);
return -1;
}
}
return 0;
}
int test_unary(Toy_Bucket* bucket) {
//test unary boolean negation (!true)
{
Toy_Ast* ast = makeAstFromSource(&bucket, "!true;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_BOOLEAN(ast->block.child->value.value) == false ||
TOY_VALUE_AS_BOOLEAN(ast->block.child->value.value) != false)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with boolean value !true (unary negation)\n" TOY_CC_RESET);
return -1;
}
}
//test unary boolean negation (!false, just to be safe)
{
Toy_Ast* ast = makeAstFromSource(&bucket, "!false;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_BOOLEAN(ast->block.child->value.value) == false ||
TOY_VALUE_AS_BOOLEAN(ast->block.child->value.value) != true)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with boolean value !false (unary negation)\n" TOY_CC_RESET);
return -1;
}
}
//test unary integer negation
{
Toy_Ast* ast = makeAstFromSource(&bucket, "-42;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->value.value) != -42)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with integer value -42 (unary negation)\n" TOY_CC_RESET);
return -1;
}
}
//test unary float negation
{
Toy_Ast* ast = makeAstFromSource(&bucket, "-3.1415;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_FLOAT(ast->block.child->value.value) == false ||
TOY_VALUE_AS_FLOAT(ast->block.child->value.value) != -3.1415f)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with float value -3.1415 (unary negation)\n" TOY_CC_RESET);
return -1;
}
}
//ensure unary negation doesn't affect other things (such as group)
{
Toy_Ast* ast = makeAstFromSource(&bucket, "-(42);");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_UNARY ||
ast->block.child->unary.child == NULL ||
ast->block.child->unary.child->type != TOY_AST_GROUP ||
ast->block.child->unary.child->group.child == NULL ||
ast->block.child->unary.child->group.child->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->unary.child->group.child->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->unary.child->group.child->value.value) != 42)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: unexpected successful unary negation in parser with grouped value -(42)\n" TOY_CC_RESET);
return -1;
}
}
return 0;
}
int test_binary(Toy_Bucket* bucket) {
//test binary add (term); also covers subtract
{
Toy_Ast* ast = makeAstFromSource(&bucket, "1 + 2;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_ADD ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->value.value) != 1 ||
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->value.value) != 2)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with binary add '1 + 2' (term)\n" TOY_CC_RESET);
return -1;
}
}
//test binary multiply (factor); also covers divide and modulo
{
Toy_Ast* ast = makeAstFromSource(&bucket, "3 * 5;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_MULTIPLY ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->value.value) != 3 ||
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->value.value) != 5)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with binary multiply '3 * 5' (factor)\n" TOY_CC_RESET);
return -1;
}
}
//test binary assign (using numbers for now, as identifiers aren't coded yet)
{
Toy_Ast* ast = makeAstFromSource(&bucket, "1 = 2;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_ASSIGN ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->value.value) != 1 ||
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->value.value) != 2)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with binary assign '1 = 2'\n" TOY_CC_RESET);
return -1;
}
}
//test binary compare (equality)
{
Toy_Ast* ast = makeAstFromSource(&bucket, "42 == 69;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_COMPARE_EQUAL ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->value.value) != 42 ||
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->value.value) != 69)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser with binary compare '42 == 69'\n" TOY_CC_RESET);
return -1;
}
}
return 0;
}
int test_precedence(Toy_Bucket* bucket) {
//test term-factor precedence
{
Toy_Ast* ast = makeAstFromSource(&bucket, "1 * 2 + 3 * 4;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_ADD ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_BINARY ||
ast->block.child->binary.left->binary.flag != TOY_AST_FLAG_MULTIPLY ||
ast->block.child->binary.left->binary.left == NULL ||
ast->block.child->binary.left->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.left->value.value) != 1 ||
ast->block.child->binary.left->binary.right == NULL ||
ast->block.child->binary.left->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.right->value.value) != 2 ||
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_BINARY ||
ast->block.child->binary.right->binary.flag != TOY_AST_FLAG_MULTIPLY ||
ast->block.child->binary.right->binary.left == NULL ||
ast->block.child->binary.right->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->binary.left->value.value) != 3 ||
ast->block.child->binary.right->binary.right == NULL ||
ast->block.child->binary.right->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->binary.right->value.value) != 4)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser precedence '1 * 2 + 3 * 4' (term-factor)\n" TOY_CC_RESET);
return -1;
}
}
//test left-recrusive precedence
{
Toy_Ast* ast = makeAstFromSource(&bucket, "1 + 2 + 3 + 4 + 5 + 6;");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_ADD ||
// start from the right and work backwards
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->value.value) != 6 ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_BINARY ||
ast->block.child->binary.left->binary.right == NULL ||
ast->block.child->binary.left->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.right->value.value) != 5 ||
ast->block.child->binary.left->binary.left == NULL ||
ast->block.child->binary.left->binary.left->type != TOY_AST_BINARY ||
ast->block.child->binary.left->binary.left->binary.right == NULL ||
ast->block.child->binary.left->binary.left->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.left->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.left->binary.right->value.value) != 4 ||
ast->block.child->binary.left->binary.left->binary.left == NULL ||
ast->block.child->binary.left->binary.left->binary.left->type != TOY_AST_BINARY ||
ast->block.child->binary.left->binary.left->binary.left->binary.right == NULL ||
ast->block.child->binary.left->binary.left->binary.left->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.left->binary.left->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.left->binary.left->binary.right->value.value) != 3 ||
ast->block.child->binary.left->binary.left->binary.left->binary.left == NULL ||
ast->block.child->binary.left->binary.left->binary.left->binary.left->type != TOY_AST_BINARY ||
ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.right == NULL ||
ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.right->value.value) != 2 ||
ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.left == NULL ||
ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->binary.left->binary.left->binary.left->binary.left->value.value) != 1)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser precedence '1 + 2 + 3 + 4 + 5 + 6' (left-recursive)\n" TOY_CC_RESET);
return -1;
}
}
//test group precedence
{
Toy_Ast* ast = makeAstFromSource(&bucket, "(1 + 2) * (3 + 4);");
//check if it worked
if (
ast == NULL ||
ast->type != TOY_AST_BLOCK ||
ast->block.child == NULL ||
ast->block.child->type != TOY_AST_BINARY ||
ast->block.child->binary.flag != TOY_AST_FLAG_MULTIPLY ||
ast->block.child->binary.left == NULL ||
ast->block.child->binary.left->type != TOY_AST_GROUP ||
ast->block.child->binary.left->group.child == NULL ||
ast->block.child->binary.left->group.child->type != TOY_AST_BINARY ||
ast->block.child->binary.left->group.child->binary.flag != TOY_AST_FLAG_ADD ||
ast->block.child->binary.left->group.child->binary.left == NULL ||
ast->block.child->binary.left->group.child->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->group.child->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->group.child->binary.left->value.value) != 1 ||
ast->block.child->binary.left->group.child->binary.right == NULL ||
ast->block.child->binary.left->group.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.left->group.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.left->group.child->binary.right->value.value) != 2 ||
ast->block.child->binary.right == NULL ||
ast->block.child->binary.right->type != TOY_AST_GROUP ||
ast->block.child->binary.right->group.child == NULL ||
ast->block.child->binary.right->group.child->type != TOY_AST_BINARY ||
ast->block.child->binary.right->group.child->binary.flag != TOY_AST_FLAG_ADD ||
ast->block.child->binary.right->group.child->binary.left == NULL ||
ast->block.child->binary.right->group.child->binary.left->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->group.child->binary.left->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->group.child->binary.left->value.value) != 3 ||
ast->block.child->binary.right->group.child->binary.right == NULL ||
ast->block.child->binary.right->group.child->binary.right->type != TOY_AST_VALUE ||
TOY_VALUE_IS_INTEGER(ast->block.child->binary.right->group.child->binary.right->value.value) == false ||
TOY_VALUE_AS_INTEGER(ast->block.child->binary.right->group.child->binary.right->value.value) != 4)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to run the parser precedence '(1 + 2) * (3 + 4)' (group)\n" TOY_CC_RESET);
return -1;
}
}
return 0;
}
int main() {
//run each test set, returning the total errors given
int total = 0, res = 0;
{
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 32);
res = test_simple_empty_parsers(bucket);
TOY_BUCKET_FREE(bucket);
if (res == 0) {
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
}
total += res;
}
{
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 32);
res = test_values(bucket);
TOY_BUCKET_FREE(bucket);
if (res == 0) {
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
}
total += res;
}
{
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 32);
res = test_unary(bucket);
TOY_BUCKET_FREE(bucket);
if (res == 0) {
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
}
total += res;
}
{
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 32);
res = test_binary(bucket);
TOY_BUCKET_FREE(bucket);
if (res == 0) {
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
}
total += res;
}
{
Toy_Bucket* bucket = NULL;
TOY_BUCKET_INIT(Toy_Ast, bucket, 32);
res = test_precedence(bucket);
TOY_BUCKET_FREE(bucket);
if (res == 0) {
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
}
total += res;
}
return total;
}