From 90ffe9b40e9660e1fb740aee374dd739004db864 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Thu, 9 Jan 2025 11:03:03 +1100 Subject: [PATCH] Disallow empty bodies in control flow statements, added 'pass' keyword Fixed #170 --- source/toy_lexer.c | 7 +----- source/toy_parser.c | 27 +++++++++++++++--------- source/toy_token_types.h | 2 +- tests/integrations/test_control_flow.toy | 9 ++++++++ 4 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 tests/integrations/test_control_flow.toy diff --git a/source/toy_lexer.c b/source/toy_lexer.c index 9f8ab43..ee0f90b 100644 --- a/source/toy_lexer.c +++ b/source/toy_lexer.c @@ -44,6 +44,7 @@ const Toy_KeywordTypeTuple keywordTuples[] = { {TOY_TOKEN_KEYWORD_IMPORT, "import"}, {TOY_TOKEN_KEYWORD_IN, "in"}, {TOY_TOKEN_KEYWORD_OF, "of"}, + {TOY_TOKEN_KEYWORD_PASS, "pass"}, {TOY_TOKEN_KEYWORD_PRINT, "print"}, {TOY_TOKEN_KEYWORD_RETURN, "return"}, {TOY_TOKEN_KEYWORD_TYPEAS, "typeas"}, @@ -404,12 +405,6 @@ void Toy_private_printToken(Toy_Token* token) { return; } - //read pass token, even though it isn't generated - if (token->type == TOY_TOKEN_PASS) { - printf(TOY_CC_NOTICE "PASS: \t%d\t%.*s\n" TOY_CC_RESET, (int)token->line, (int)token->length, token->lexeme); - return; - } - //print the line number printf("\t%d\t%d\t", token->type, (int)token->line); diff --git a/source/toy_parser.c b/source/toy_parser.c index a39b014..cb29887 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -156,6 +156,7 @@ static ParsingTuple parsingRulesetTable[] = { {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_IMPORT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_IN, {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_OF, + {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_PASS, {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_PRINT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_RETURN, {PREC_NONE,NULL,NULL},// TOY_TOKEN_KEYWORD_TYPEAS, @@ -221,7 +222,6 @@ static ParsingTuple parsingRulesetTable[] = { {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_PIPE, // | //meta tokens - {PREC_NONE,NULL,NULL},// TOY_TOKEN_PASS, {PREC_NONE,NULL,NULL},// TOY_TOKEN_ERROR, {PREC_NONE,NULL,NULL},// TOY_TOKEN_EOF, }; @@ -730,7 +730,7 @@ 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 makeDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle); +static void makeDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle, bool errorOnEmpty); static void makeAssertStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { Toy_Ast* ast = NULL; //assert's emit function is a bit different @@ -764,11 +764,11 @@ static void makeIfThenElseStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, To consume(parser, TOY_TOKEN_OPERATOR_PAREN_RIGHT, "Expected ')' after 'if' condition"); // { thenBranch } - makeDeclarationStmt(bucketHandle, parser, &thenBranch); + makeDeclarationStmt(bucketHandle, parser, &thenBranch, true); //else { elseBranch } if (match(parser, TOY_TOKEN_KEYWORD_ELSE)) { - makeDeclarationStmt(bucketHandle, parser, &elseBranch); + makeDeclarationStmt(bucketHandle, parser, &elseBranch, true); } Toy_private_emitAstIfThenElse(bucketHandle, rootHandle, condBranch, thenBranch, elseBranch); @@ -784,7 +784,7 @@ static void makeWhileStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast consume(parser, TOY_TOKEN_OPERATOR_PAREN_RIGHT, "Expected ')' after 'while' condition"); // { thenBranch } - makeDeclarationStmt(bucketHandle, parser, &thenBranch); + makeDeclarationStmt(bucketHandle, parser, &thenBranch, true); Toy_private_emitAstWhileThen(bucketHandle, rootHandle, condBranch, thenBranch); } @@ -899,8 +899,8 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro return; } - //empty lines - else if (match(parser, TOY_TOKEN_OPERATOR_SEMICOLON)) { + //empty lines, or pass statements + else if (match(parser, TOY_TOKEN_OPERATOR_SEMICOLON) || match(parser, TOY_TOKEN_KEYWORD_PASS)) { Toy_private_emitAstPass(bucketHandle, rootHandle); return; } @@ -913,9 +913,16 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro } } -static void makeDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { +static void makeDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle, bool errorOnEmpty) { + //disallow empty control flow bodies + if (errorOnEmpty && match(parser, TOY_TOKEN_OPERATOR_SEMICOLON)) { + printError(parser, parser->previous, "Empty control flow bodies are disallowed, use the 'pass' keyword"); + Toy_private_emitAstError(bucketHandle, rootHandle); + return; + } + //variable declarations - if (match(parser, TOY_TOKEN_KEYWORD_VAR)) { + else if (match(parser, TOY_TOKEN_KEYWORD_VAR)) { makeVariableDeclarationStmt(bucketHandle, parser, rootHandle); } @@ -938,7 +945,7 @@ static void makeBlockStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast 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); + makeDeclarationStmt(bucketHandle, parser, &stmt, false); //if something went wrong if (parser->panic) { diff --git a/source/toy_token_types.h b/source/toy_token_types.h index cf31b15..21be274 100644 --- a/source/toy_token_types.h +++ b/source/toy_token_types.h @@ -37,6 +37,7 @@ typedef enum Toy_TokenType { TOY_TOKEN_KEYWORD_IMPORT, TOY_TOKEN_KEYWORD_IN, TOY_TOKEN_KEYWORD_OF, + TOY_TOKEN_KEYWORD_PASS, TOY_TOKEN_KEYWORD_PRINT, TOY_TOKEN_KEYWORD_RETURN, TOY_TOKEN_KEYWORD_TYPEAS, @@ -102,7 +103,6 @@ typedef enum Toy_TokenType { TOY_TOKEN_OPERATOR_PIPE, // | //meta tokens - TOY_TOKEN_PASS, TOY_TOKEN_ERROR, TOY_TOKEN_EOF, } Toy_TokenType; diff --git a/tests/integrations/test_control_flow.toy b/tests/integrations/test_control_flow.toy new file mode 100644 index 0000000..2b98bd7 --- /dev/null +++ b/tests/integrations/test_control_flow.toy @@ -0,0 +1,9 @@ +//these are allowed +/* EMPTY */; +if (true) { } +if (true) pass; + + +//these are not allowed +// if (true) /* EMPTY */; +