code / tomo

Lines41.3K C23.7K Markdown9.7K YAML5.0K Tomo2.3K
7 others 763
Python231 Shell230 make212 INI47 Text21 SVG16 Lua6
(168 lines)
1 // This file defines how to compile 'when' statements/expressions
3 #include "../ast.h"
4 #include "../config.h"
5 #include "../environment.h"
6 #include "../naming.h"
7 #include "../stdlib/datatypes.h"
8 #include "../stdlib/text.h"
9 #include "../stdlib/util.h"
10 #include "../typecheck.h"
11 #include "compilation.h"
13 public
14 Text_t compile_when_statement(env_t *env, ast_t *ast) {
15 // Typecheck to verify exhaustiveness:
16 type_t *result_t = get_type(env, ast);
17 (void)result_t;
19 DeclareMatch(when, ast, When);
20 type_t *subject_t = get_type(env, when->subject);
22 if (subject_t->tag != EnumType) {
23 Text_t prefix = EMPTY_TEXT, suffix = EMPTY_TEXT;
24 ast_t *subject = when->subject;
25 if (!is_idempotent(when->subject)) {
26 prefix = Texts("{\n", compile_declaration(subject_t, Text("_when_subject")), " = ", compile(env, subject),
27 ";\n");
28 suffix = Text("}\n");
29 subject = LiteralCode(Text("_when_subject"), .type = subject_t);
32 Text_t code = EMPTY_TEXT;
33 for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
34 ast_t *comparison = WrapAST(clause->pattern, Equals, .lhs = subject, .rhs = clause->pattern);
35 (void)get_type(env, comparison);
36 if (code.length > 0) code = Texts(code, "else ");
37 code = Texts(code, "if (", compile(env, comparison), ")", compile_statement(env, clause->body));
39 if (when->else_body) code = Texts(code, "else ", compile_statement(env, when->else_body));
40 code = Texts(prefix, code, suffix);
41 return code;
44 DeclareMatch(enum_t, subject_t, EnumType);
46 Text_t code = Texts("WHEN(", compile_type(subject_t), ", ", compile(env, when->subject), ", _when_subject, {\n");
47 for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
48 if (clause->pattern->tag == Var) {
49 const char *clause_tag_name = Match(clause->pattern, Var)->name;
50 type_t *clause_type = clause->body ? get_type(env, clause->body) : Type(VoidType);
51 code = Texts(
52 code, "case ", namespace_name(enum_t->env, enum_t->env->namespace, Texts("tag$", clause_tag_name)),
53 ": {\n", compile_inline_block(env, clause->body),
54 (clause_type->tag == ReturnType || clause_type->tag == AbortType) ? EMPTY_TEXT : Text("break;\n"),
55 "}\n");
56 continue;
59 if (clause->pattern->tag != FunctionCall || Match(clause->pattern, FunctionCall)->fn->tag != Var)
60 code_err(clause->pattern, "This is not a valid pattern for a ", type_to_text(subject_t), " enum type");
62 const char *clause_tag_name = Match(Match(clause->pattern, FunctionCall)->fn, Var)->name;
63 code = Texts(code, "case ", namespace_name(enum_t->env, enum_t->env->namespace, Texts("tag$", clause_tag_name)),
64 ": {\n");
65 type_t *tag_type = NULL;
66 for (tag_t *tag = enum_t->tags; tag; tag = tag->next) {
67 if (streq(tag->name, clause_tag_name)) {
68 tag_type = tag->type;
69 break;
72 assert(tag_type);
73 env_t *scope = env;
75 DeclareMatch(tag_struct, tag_type, StructType);
76 arg_ast_t *args = Match(clause->pattern, FunctionCall)->args;
77 if (args && !args->next && tag_struct->fields && tag_struct->fields->next) {
78 if (args->value->tag != Var) code_err(args->value, "This is not a valid variable to bind to");
79 const char *var_name = Match(args->value, Var)->name;
80 if (!streq(var_name, "_")) {
81 Text_t var = Texts("_$", var_name);
82 ast_t *member =
83 WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type);
84 code = Texts(code, compile_declaration(tag_type, var), " = ",
85 compile_maybe_incref(env, member, tag_type), ";\n");
86 scope = fresh_scope(scope);
87 set_binding(scope, Match(args->value, Var)->name, tag_type, EMPTY_TEXT);
89 } else if (args) {
90 scope = fresh_scope(scope);
91 arg_t *field = tag_struct->fields;
92 for (arg_ast_t *arg = args; arg || field; arg = arg->next) {
93 if (!arg)
94 code_err(ast, "The field ", type_to_text(subject_t), ".", clause_tag_name, ".", field->name,
95 " wasn't accounted for");
96 if (!field) code_err(arg->value, "This is one more field than ", type_to_text(subject_t), " has");
97 if (arg->name) code_err(arg->value, "Named arguments are not currently supported");
99 const char *var_name = Match(arg->value, Var)->name;
100 if (!streq(var_name, "_")) {
101 Text_t var = Texts("_$", var_name);
102 ast_t *member =
103 WrapLiteralCode(ast, Texts("_when_subject.", valid_c_name(clause_tag_name)), .type = tag_type);
104 code = Texts(code, compile_declaration(field->type, var), " = ",
105 compile_maybe_incref(env, member, tag_type), ".", valid_c_name(field->name), ";\n");
106 set_binding(scope, Match(arg->value, Var)->name, field->type, var);
108 field = field->next;
111 if (clause->body->tag == Block) {
112 ast_list_t *statements = Match(clause->body, Block)->statements;
113 if (!statements || (statements->ast->tag == Pass && !statements->next)) code = Texts(code, "break;\n}\n");
114 else code = Texts(code, compile_inline_block(scope, clause->body), "\nbreak;\n}\n");
115 } else {
116 code = Texts(code, compile_statement(scope, clause->body), "\nbreak;\n}\n");
119 if (when->else_body) {
120 if (when->else_body->tag == Block) {
121 ast_list_t *statements = Match(when->else_body, Block)->statements;
122 if (!statements || (statements->ast->tag == Pass && !statements->next))
123 code = Texts(code, "default: break;");
124 else code = Texts(code, "default: {\n", compile_inline_block(env, when->else_body), "\nbreak;\n}\n");
125 } else {
126 code = Texts(code, "default: {\n", compile_statement(env, when->else_body), "\nbreak;\n}\n");
128 } else {
129 code = Texts(code, "default: errx(1, \"Invalid tag!\");\n");
131 code = Texts(code, "\n}", Text(")"), "\n");
132 return code;
135 public
136 Text_t compile_when_expression(env_t *env, ast_t *ast) {
137 DeclareMatch(original, ast, When);
138 ast_t *when_var = WrapAST(ast, Var, .name = "when");
139 when_clause_t *new_clauses = NULL;
140 type_t *subject_t = get_type(env, original->subject);
141 for (when_clause_t *clause = original->clauses; clause; clause = clause->next) {
142 type_t *clause_type = get_clause_type(env, subject_t, clause);
143 if (clause_type->tag == AbortType || clause_type->tag == ReturnType) {
144 new_clauses = new (when_clause_t, .pattern = clause->pattern, .body = clause->body, .next = new_clauses);
145 } else {
146 ast_t *assign = WrapAST(clause->body, Assign, .targets = new (ast_list_t, .ast = when_var),
147 .values = new (ast_list_t, .ast = clause->body));
148 new_clauses = new (when_clause_t, .pattern = clause->pattern, .body = assign, .next = new_clauses);
151 REVERSE_LIST(new_clauses);
152 ast_t *else_body = original->else_body;
153 if (else_body) {
154 type_t *clause_type = get_type(env, else_body);
155 if (clause_type->tag != AbortType && clause_type->tag != ReturnType) {
156 else_body = WrapAST(else_body, Assign, .targets = new (ast_list_t, .ast = when_var),
157 .values = new (ast_list_t, .ast = else_body));
161 type_t *t = get_type(env, ast);
162 env_t *when_env = fresh_scope(env);
163 set_binding(when_env, "when", t, Text("when"));
164 return Texts("({ ", compile_declaration(t, Text("when")), ";\n",
165 compile_statement(when_env, WrapAST(ast, When, .subject = original->subject, .clauses = new_clauses,
166 .else_body = else_body)),
167 "when; })");