diff --git a/scripts/testificate.toy b/scripts/testificate.toy new file mode 100644 index 0000000..811b052 --- /dev/null +++ b/scripts/testificate.toy @@ -0,0 +1,22 @@ + +//normal scope stuff +var answer = 42; +print answer; + +{ + var answer = 7; + print answer; +} + +print answer; + +//I wonder if... +var question = 42; +print question; + +{ + var question = question; + print question; +} + +print question; diff --git a/source/toy_ast.c b/source/toy_ast.c index 83462a6..8eb3b69 100644 --- a/source/toy_ast.c +++ b/source/toy_ast.c @@ -4,6 +4,7 @@ void Toy_private_initAstBlock(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) { Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); tmp->type = TOY_AST_BLOCK; + tmp->block.innerScope = false; tmp->block.child = NULL; tmp->block.next = NULL; tmp->block.tail = NULL; diff --git a/source/toy_ast.h b/source/toy_ast.h index 69f3d5d..91cc055 100644 --- a/source/toy_ast.h +++ b/source/toy_ast.h @@ -69,6 +69,7 @@ typedef union Toy_Ast Toy_Ast; typedef struct Toy_AstBlock { Toy_AstType type; + bool innerScope; Toy_Ast* child; //begin encoding the line Toy_Ast* next; //'next' is either an AstBlock or null Toy_Ast* tail; //'tail' - either points to the tail of the current list, or null; only used by the head of a list as an optimisation diff --git a/source/toy_opcodes.h b/source/toy_opcodes.h index 849b79f..9e9c205 100644 --- a/source/toy_opcodes.h +++ b/source/toy_opcodes.h @@ -33,6 +33,9 @@ typedef enum Toy_OpcodeType { //control instructions TOY_OPCODE_RETURN, + TOY_OPCODE_SCOPE_PUSH, + TOY_OPCODE_SCOPE_POP, + //various action instructions TOY_OPCODE_PRINT, TOY_OPCODE_CONCAT, diff --git a/source/toy_parser.c b/source/toy_parser.c index af392db..af75685 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -565,6 +565,9 @@ static void makeExpr(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro parsePrecedence(bucketHandle, parser, rootHandle, PREC_ASSIGNMENT); } +//forward declarations +static void makeBlockStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle); + static void makePrintStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { makeExpr(bucketHandle, parser, rootHandle); Toy_private_emitAstPrint(bucketHandle, rootHandle); @@ -608,7 +611,14 @@ static void makeVariableDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* p } static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { - //block + //inner scope + if (match(parser, TOY_TOKEN_OPERATOR_BRACE_LEFT)) { + makeBlockStmt(bucketHandle, parser, rootHandle); + consume(parser, TOY_TOKEN_OPERATOR_BRACE_RIGHT, "Expected '}' at the end of block scope"); + (*rootHandle)->block.innerScope = true; + return; + } + //assert //if-then-else //while-then @@ -619,7 +629,7 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro //import //check for empty lines - if (match(parser, TOY_TOKEN_OPERATOR_SEMICOLON)) { + else if (match(parser, TOY_TOKEN_OPERATOR_SEMICOLON)) { Toy_private_emitAstPass(bucketHandle, rootHandle); return; } @@ -658,7 +668,7 @@ static void makeBlockStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast Toy_private_initAstBlock(bucketHandle, rootHandle); //read a series of statements into the block - while (!match(parser, TOY_TOKEN_EOF)) { + while (parser->current.type != TOY_TOKEN_OPERATOR_BRACE_RIGHT && !match(parser, TOY_TOKEN_EOF)) { //process the grammar rules Toy_Ast* stmt = NULL; makeDeclarationStmt(bucketHandle, parser, &stmt); @@ -695,6 +705,11 @@ Toy_Ast* Toy_scanParser(Toy_Bucket** bucketHandle, Toy_Parser* parser) { makeBlockStmt(bucketHandle, parser, &rootHandle); + //don't emit this error if we're already panicking + if (parser->panic != true && parser->previous.type != TOY_TOKEN_EOF) { + printError(parser, parser->previous, "Expected 'EOF' and the end of the parser scan (possibly an extra '}' was found)"); + } + return rootHandle; } diff --git a/source/toy_routine.c b/source/toy_routine.c index 98283e5..cfa476d 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -410,8 +410,22 @@ static void writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { //determine how to write each instruction based on the Ast switch(ast->type) { case TOY_AST_BLOCK: + if (ast->block.innerScope) { + EMIT_BYTE(rt, code, TOY_OPCODE_SCOPE_PUSH); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + } + writeRoutineCode(rt, ast->block.child); writeRoutineCode(rt, ast->block.next); + + if (ast->block.innerScope) { + EMIT_BYTE(rt, code, TOY_OPCODE_SCOPE_POP); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + EMIT_BYTE(rt, code, 0); + } break; case TOY_AST_VALUE: diff --git a/source/toy_vm.c b/source/toy_vm.c index 8e0bb2a..81728db 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -482,6 +482,14 @@ static void process(Toy_VM* vm) { //temp terminator return; + case TOY_OPCODE_SCOPE_PUSH: + vm->scope = Toy_pushScope(&vm->scopeBucket, vm->scope); + break; + + case TOY_OPCODE_SCOPE_POP: + vm->scope = Toy_popScope(vm->scope); + break; + //various action instructions case TOY_OPCODE_PRINT: processPrint(vm); diff --git a/tests/cases/test_ast.c b/tests/cases/test_ast.c index d9c4846..967d31f 100644 --- a/tests/cases/test_ast.c +++ b/tests/cases/test_ast.c @@ -48,7 +48,7 @@ int test_sizeof_ast_32bit() { //run for each type TEST_SIZEOF(Toy_AstType, 4); - TEST_SIZEOF(Toy_AstBlock, 16); + TEST_SIZEOF(Toy_AstBlock, 20); TEST_SIZEOF(Toy_AstValue, 12); TEST_SIZEOF(Toy_AstUnary, 12); TEST_SIZEOF(Toy_AstBinary, 16); @@ -61,7 +61,7 @@ int test_sizeof_ast_32bit() { TEST_SIZEOF(Toy_AstPass, 4); TEST_SIZEOF(Toy_AstError, 4); TEST_SIZEOF(Toy_AstEnd, 4); - TEST_SIZEOF(Toy_Ast, 16); + TEST_SIZEOF(Toy_Ast, 20); #undef TEST_SIZEOF diff --git a/tests/integrations/test_scopes.toy b/tests/integrations/test_scopes.toy new file mode 100644 index 0000000..1525a1f --- /dev/null +++ b/tests/integrations/test_scopes.toy @@ -0,0 +1,19 @@ +//shadowing +var answer = 42; +print answer; //42 +{ + var answer = 7; + print answer; //7 +} +print answer; //42 + +//rebinding +var question = 42; +print question; //42 +{ + var question = question; + print question; //42 +} +print question; //42 + +//TODO: scope test case diff --git a/tests/integrations/test_variables_and_scopes.toy b/tests/integrations/test_variables.toy similarity index 86% rename from tests/integrations/test_variables_and_scopes.toy rename to tests/integrations/test_variables.toy index 76bfddb..43af2ef 100644 --- a/tests/integrations/test_variables_and_scopes.toy +++ b/tests/integrations/test_variables.toy @@ -7,15 +7,13 @@ var empty; //assign a previously existing variable answer = 6 * 9; +//access a variable answer = answer + 1; +//compound assignments answer += 5; - answer -= 5; - answer *= 9; - answer /= 2; - answer %= 10;