aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/ast.h2
-rw-r--r--src/codegen.c13
-rw-r--r--src/parse.c18
-rw-r--r--tests/helpers.sh1
-rw-r--r--tests/test_goto.sh168
5 files changed, 202 insertions, 0 deletions
diff --git a/src/ast.h b/src/ast.h
index c5171d7..4ca2e1b 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -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