diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-09-28 15:37:52 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-10-04 15:31:34 +0900 |
| commit | 7e11675136edf8136f812c85cd45bc88ba405533 (patch) | |
| tree | 61dcda3287f409b8c54f61e29adc9afc32a772d1 | |
| parent | 5a16856f8ff4dbf801b4a622ca7053b77e8a0214 (diff) | |
| download | ducc-7e11675136edf8136f812c85cd45bc88ba405533.tar.gz ducc-7e11675136edf8136f812c85cd45bc88ba405533.tar.zst ducc-7e11675136edf8136f812c85cd45bc88ba405533.zip | |
feat: implement goto statement
| -rw-r--r-- | src/ast.h | 2 | ||||
| -rw-r--r-- | src/codegen.c | 13 | ||||
| -rw-r--r-- | src/parse.c | 18 | ||||
| -rw-r--r-- | tests/helpers.sh | 1 | ||||
| -rw-r--r-- | tests/test_goto.sh | 168 |
5 files changed, 202 insertions, 0 deletions
@@ -106,10 +106,12 @@ typedef enum { AstNodeKind_func_call, AstNodeKind_func_decl, AstNodeKind_func_def, + AstNodeKind_goto_stmt, AstNodeKind_gvar, AstNodeKind_gvar_decl, AstNodeKind_if_stmt, AstNodeKind_int_expr, + AstNodeKind_label_stmt, AstNodeKind_list, AstNodeKind_logical_expr, AstNodeKind_lvar, diff --git a/src/codegen.c b/src/codegen.c index f02a38c..4387449 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -578,6 +578,15 @@ static void codegen_continue_stmt(CodeGen* g, AstNode* ast) { fprintf(g->out, " jmp .Lcontinue%d\n", label); } +static void codegen_goto_stmt(CodeGen* g, AstNode* ast) { + fprintf(g->out, " jmp .L%s__%s\n", g->current_func->name, ast->name); +} + +static void codegen_label_stmt(CodeGen* g, AstNode* ast) { + fprintf(g->out, ".L%s__%s:\n", g->current_func->name, ast->name); + codegen_stmt(g, ast->node_body); +} + // Helper to collect case values from the switch body static void collect_cases(AstNode* stmt, int* case_values, int* case_labels, int* n_cases) { if (!stmt) @@ -694,6 +703,10 @@ static void codegen_stmt(CodeGen* g, AstNode* ast) { codegen_break_stmt(g, ast); } else if (ast->kind == AstNodeKind_continue_stmt) { codegen_continue_stmt(g, ast); + } else if (ast->kind == AstNodeKind_goto_stmt) { + codegen_goto_stmt(g, ast); + } else if (ast->kind == AstNodeKind_label_stmt) { + codegen_label_stmt(g, ast); } else if (ast->kind == AstNodeKind_expr_stmt) { codegen_expr_stmt(g, ast); } else if (ast->kind == AstNodeKind_lvar_decl) { diff --git a/src/parse.c b/src/parse.c index b82c45d..1ad11bd 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1331,12 +1331,30 @@ static AstNode* parse_stmt(Parser* p) { return parse_break_stmt(p); } else if (t->kind == TokenKind_keyword_continue) { return parse_continue_stmt(p); + } else if (t->kind == TokenKind_keyword_goto) { + expect(p, TokenKind_keyword_goto); + Token* label_token = expect(p, TokenKind_ident); + expect(p, TokenKind_semicolon); + + AstNode* goto_stmt = ast_new(AstNodeKind_goto_stmt); + goto_stmt->name = label_token->value.string; + return goto_stmt; } else if (t->kind == TokenKind_brace_l) { return parse_block_stmt(p); } else if (t->kind == TokenKind_semicolon) { return parse_empty_stmt(p); } else if (is_type_token(p, t)) { return parse_var_decl(p); + } else if (t->kind == TokenKind_ident && peek_token2(p)->kind == TokenKind_colon) { + // Label statement + Token* label_token = expect(p, TokenKind_ident); + expect(p, TokenKind_colon); + AstNode* stmt = parse_stmt(p); + + AstNode* label_stmt = ast_new(AstNodeKind_label_stmt); + label_stmt->name = label_token->value.string; + label_stmt->node_body = stmt; + return label_stmt; } else { return parse_expr_stmt(p); } diff --git a/tests/helpers.sh b/tests/helpers.sh index adc9511..6e0c51c 100644 --- a/tests/helpers.sh +++ b/tests/helpers.sh @@ -43,6 +43,7 @@ function test_compile_error() { if [[ $exit_code -eq 0 ]]; then echo "expected to fail" >&2 + cat expected >&2 exit 1 fi diff --git a/tests/test_goto.sh b/tests/test_goto.sh new file mode 100644 index 0000000..1e28a51 --- /dev/null +++ b/tests/test_goto.sh @@ -0,0 +1,168 @@ +touch expected +test_diff <<'EOF' +int main() { + goto end; + return 1; +end: + return 0; +} +EOF + +cat <<'EOF' > expected +1 +2 +3 +EOF +test_diff <<'EOF' +int printf(const char*, ...); + +int main() { + int i = 0; +loop: + i++; + printf("%d\n", i); + if (i < 3) + goto loop; + return 0; +} +EOF + +cat < /dev/null > expected +test_diff <<'EOF' +int main() { + goto skip; + int x = 5; +skip: + return 0; +} +EOF + +cat <<'EOF' > expected +start +middle +end +EOF +test_diff <<'EOF' +int printf(const char*, ...); + +int main() { + printf("start\n"); + goto middle; +first: + printf("first\n"); + goto end; +middle: + printf("middle\n"); + goto end; +last: + printf("last\n"); +end: + printf("end\n"); + return 0; +} +EOF + +cat <<'EOF' > expected +before +after +EOF +test_diff <<'EOF' +int printf(const char*, ...); + +int main() { + printf("before\n"); + { + { + goto out; + printf("inside\n"); + } + printf("middle\n"); + } +out: + printf("after\n"); + return 0; +} +EOF + +cat <<'EOF' > expected +x is 5 +x is 10 +EOF +test_diff <<'EOF' +int printf(const char*, ...); + +int main() { + int x = 5; + if (x == 5) { + printf("x is 5\n"); + goto next; + } + printf("x is not 5\n"); +next: + x = 10; + printf("x is %d\n", x); + return 0; +} +EOF + +cat <<'EOF' > expected +case 2 +done +EOF +test_diff <<'EOF' +int printf(const char*, ...); + +int main() { + int x = 2; + switch (x) { + case 1: + printf("case 1\n"); + break; + case 2: + printf("case 2\n"); + goto done; + case 3: + printf("case 3\n"); + break; + } + printf("after switch\n"); +done: + printf("done\n"); + return 0; +} +EOF + +# cat <<'EOF' > expected +# error: use of undeclared label 'undefined' +# EOF +# test_compile_error <<'EOF' +# int main() { +# goto undefined; +# return 0; +# } +# EOF + +# cat <<'EOF' > expected +# error: redefinition of label 'duplicate' +# EOF +# test_compile_error <<'EOF' +# int main() { +# duplicate: +# ; +# duplicate: +# return 0; +# } +# EOF + +# cat <<'EOF' > expected +# error: label at end of compound statement +# EOF +# test_compile_error <<'EOF' +# int main() { +# { +# goto end; +# end: +# } +# return 0; +# } +# EOF |
