/* * Outputs C statements in functions. * * Copyright © 2020-2025 Samuel Lidén Borell * * SPDX-License-Identifier: EUPL-1.2+ */ #include #include "compiler.h" #include "out.h" int stmt_id = 0; static void emit_statements(struct Stmt *stmt); static void emit_statements_end_goto(struct Stmt *stmt, const char *goto_prefix, int goto_id); static void emit_compare(struct TypeRef *tr, const char *res_name, int res_id, const char *a_name, int a_id, const char *b_name, int b_id) { (void)tr; /* TODO string/struct/array comparison */ indentf("%s%d = (%s%d == %s%d);\n", res_name, res_id, a_name, a_id, b_name, b_id); } static void emit_bool(struct Stmt *stmt, struct Expr *expr) { static const struct TypeRef booltr = { TR_BOOL, 0, { NULL } }; emit_expr(&booltr, "b", stmt->id, expr); } static void emit_ifblock(struct Expr *cond, struct Stmt *body, int goto_id) { beginf("{\n"); emit_bool(body, cond); indentf("if (b%d) ", body->id); emit_statements_end_goto(body, "end", goto_id); outc('\n'); endf("}\n"); } void emit_statements_inside(struct Stmt *stmt) { for (; stmt; stmt = stmt->next) { if (stmt->kind == S_VARDECL && !stmt->u.var->initval) { continue; } beginf("{/* line %d */\n", stmt->line); stmt_id = stmt->id; switch (stmt->kind) { case S_ASSERT: /* TODO include filename and source (could take the line minus the "assert" kw). both of these need escaping! */ emit_bool(stmt, stmt->u.expr); indentf("SLUL_ASSERT(b%d, \"TODO expr\", " "\"TODO filename\", %d);\n", stmt->id, stmt->line); break; case S_BREAK: indentf("goto loopbreak%d;\n", stmt->u.break_.loop->id); break; case S_CONTINUE: indentf("continue;"); break; case S_EXPR: emit_expr(NULL, NULL, -1, stmt->u.expr); break; case S_FOR: case S_WHILE: /* TODO use some special character in outf for indent? maybe ^ for normal, > for +, < for -? or maybe an enum as the first parameter? (START, INDENT, OUTDENT, NONE)? */ /* TODO initialization for `for` loops */ if (stmt->u.loop.loopempty) { indentf("bool loopempty%d = true;\n", stmt->id); } beginf("for (;;) {\n"); if (stmt->kind == S_FOR) { /* TODO `for` loops */ } else { beginf("{ /* loop condition */\n"); emit_bool(stmt, stmt->u.loop.cond); indentf("if (!b%d) { break; }\n", stmt->id); endf("} /* end loop condition */\n"); } if (stmt->u.loop.loopempty) { indentf("loopempty%d = false;\n", stmt->id); } /* Loop body */ emit_statements_inside(stmt->u.loop.body); /* TODO - different loop types (while, range, array, iterator, iterable) */ endf("}\n"); if (stmt->u.loop.loopempty) { indentf("if (loopempty%d) ", stmt->id); emit_statements(stmt->u.loop.loopempty); outc('\n'); } if (stmt->u.loop.loopend) { indentf("/* loopend block */\n"); emit_statements_inside(stmt->u.loop.loopend); } indentf("loopbreak%d: ;\n", stmt->id); break; case S_IF: emit_bool(stmt, stmt->u.if_.cond); if (!stmt->u.if_.elifs) { /* No elif. Emit simple if-else */ indentf("if (b%d) ", stmt->id); emit_statements(stmt->u.if_.true_); if (stmt->u.if_.false_) { indentf(" else "); emit_statements(stmt->u.if_.false_); } outc('\n'); } else { struct StmtElif *elif; /* Emit if-elseif-else using goto, This is necessary for two reasons: 1. to avoid very deeply indented else-if chains 2. to avoid having more than 15 nesting levels, which is the minimum that C89 compilers must support */ emit_ifblock(stmt->u.if_.cond, stmt->u.if_.true_, stmt->id); /* After each "ifblock", if it's false, the execution falls through into the next block, and eventually the else-block if nothing matches. */ for (elif = stmt->u.if_.elifs; elif; elif = elif->next) { emit_ifblock(stmt->u.if_.cond, stmt->u.if_.true_, stmt->id); } if (stmt->u.if_.false_) { emit_statements_inside(stmt->u.if_.false_); } indentf("end%d: ;\n", stmt->id); } break; case S_RETURN_NOVALUE: indentf("return;\n"); break; case S_RETURN_VALUE: /* FIXME handle multiple return values */ /* FIXME typeref of return value */ emit_expr(NULL, "rv", stmt->id, stmt->u.expr); indentf("return rv%d;\n", stmt->id); break; case S_SWITCH: { struct Expr *cond = stmt->u.switch_.cond; struct Case *cas; beginf("{\n"); /* Value being switched on */ emit_expr(cond->typeref, "switch", stmt->id, cond); for (cas = stmt->u.switch_.cases; cas != NULL; cas = cas->next) { /* Case value */ struct Expr *value = cas->value; beginf("{\n"); indentf("bool b%d;\n", stmt->id); emit_expr(value->typeref, "case", stmt->id, value); /* Comparison */ emit_compare(cond->typeref, "b", stmt->id, "switch", stmt->id, "case", stmt->id); /* Block */ beginf("if (b%d) {\n", stmt->id); emit_statements_inside(cas->block); indentf("goto switch_end_%d;\n", stmt->id); endf("}\n"); endf("}\n"); } emit_statements_inside(stmt->u.switch_.default_); indentf("switch_end_%d: ;\n", stmt->id); endf("}\n"); break; } case S_VARDECL: { struct Var *var = stmt->u.var; assert(var->initval != NULL); /* skipped otherwise */ emit_expr(var->typeref, var->ident.node.name, -1, var->initval); break; } } endf("}/* end line %d */\n", stmt->line); } } static void emit_statements_end_goto(struct Stmt *stmt, const char *goto_prefix, int goto_id) { outf("{\n"); indentlevel++; emit_statements_inside(stmt); indentf("goto %s%d;\n", goto_prefix, goto_id); endf("}"); } static void emit_statements(struct Stmt *stmt) { outf("{\n"); indentlevel++; emit_statements_inside(stmt); endf("}"); }