code / tomo

Lines41.3K C23.7K Markdown9.7K YAML5.0K Tomo2.3K
7 others 763
Python231 Shell230 make212 INI47 Text21 SVG16 Lua6
(256 lines)
1 // Logic for parsing control flow
3 #include <stdbool.h>
4 #include <string.h>
6 #include "../ast.h"
7 #include "../stdlib/util.h"
8 #include "context.h"
9 #include "controlflow.h"
10 #include "errors.h"
11 #include "expressions.h"
12 #include "statements.h"
13 #include "suffixes.h"
14 #include "utils.h"
16 ast_t *parse_block(parse_ctx_t *ctx, const char *pos) {
17 const char *start = pos;
18 spaces(&pos);
20 ast_list_t *statements = NULL;
21 if (!indent(ctx, &pos)) {
22 // Inline block
23 spaces(&pos);
24 while (*pos) {
25 spaces(&pos);
26 ast_t *stmt = optional(ctx, &pos, parse_statement);
27 if (!stmt) break;
28 statements = new (ast_list_t, .ast = stmt, .next = statements);
29 spaces(&pos);
30 if (!match(&pos, ";")) break;
32 } else {
33 goto indented;
36 if (indent(ctx, &pos)) {
37 indented:;
38 int64_t block_indent = get_indent(ctx, pos);
39 whitespace(ctx, &pos);
40 while (*pos) {
41 ast_t *stmt = optional(ctx, &pos, parse_statement);
42 if (!stmt) {
43 const char *line_start = pos;
44 if (match_word(&pos, "struct"))
45 parser_err(ctx, line_start, eol(pos), "Struct definitions are only allowed at the top level");
46 else if (match_word(&pos, "enum"))
47 parser_err(ctx, line_start, eol(pos), "Enum definitions are only allowed at the top level");
48 else if (match_word(&pos, "func"))
49 parser_err(ctx, line_start, eol(pos), "Function definitions are only allowed at the top level");
50 else if (match_word(&pos, "use"))
51 parser_err(ctx, line_start, eol(pos), "'use' statements are only allowed at the top level");
53 spaces(&pos);
54 if (*pos && *pos != '\r' && *pos != '\n') parser_err(ctx, pos, eol(pos), "I couldn't parse this line");
55 break;
57 statements = new (ast_list_t, .ast = stmt, .next = statements);
58 whitespace(ctx, &pos);
60 // Guard against having two valid statements on the same line, separated by spaces (but no newlines):
61 if (!memchr(stmt->end, '\n', (size_t)(pos - stmt->end))) {
62 if (*pos) parser_err(ctx, pos, eol(pos), "I don't know how to parse the rest of this line");
63 pos = stmt->end;
64 break;
67 if (get_indent(ctx, pos) != block_indent) {
68 pos = stmt->end; // backtrack
69 break;
73 REVERSE_LIST(statements);
74 return NewAST(ctx->file, start, pos, Block, .statements = statements);
77 ast_t *parse_pass(parse_ctx_t *ctx, const char *pos) {
78 const char *start = pos;
79 return match_word(&pos, "pass") ? NewAST(ctx->file, start, pos, Pass) : NULL;
82 ast_t *parse_defer(parse_ctx_t *ctx, const char *pos) {
83 const char *start = pos;
84 if (!match_word(&pos, "defer")) return NULL;
85 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a block to be deferred here");
86 return NewAST(ctx->file, start, pos, Defer, .body = body);
89 ast_t *parse_skip(parse_ctx_t *ctx, const char *pos) {
90 const char *start = pos;
91 if (!match_word(&pos, "continue") && !match_word(&pos, "skip")) return NULL;
92 const char *target;
93 if (match_word(&pos, "for")) target = "for";
94 else if (match_word(&pos, "while")) target = "while";
95 else target = get_id(&pos);
96 ast_t *skip = NewAST(ctx->file, start, pos, Skip, .target = target);
97 skip = parse_optional_conditional_suffix(ctx, skip);
98 return skip;
101 ast_t *parse_stop(parse_ctx_t *ctx, const char *pos) {
102 const char *start = pos;
103 if (!match_word(&pos, "stop") && !match_word(&pos, "break")) return NULL;
104 const char *target;
105 if (match_word(&pos, "for")) target = "for";
106 else if (match_word(&pos, "while")) target = "while";
107 else target = get_id(&pos);
108 ast_t *stop = NewAST(ctx->file, start, pos, Stop, .target = target);
109 stop = parse_optional_conditional_suffix(ctx, stop);
110 return stop;
113 ast_t *parse_return(parse_ctx_t *ctx, const char *pos) {
114 const char *start = pos;
115 if (!match_word(&pos, "return")) return NULL;
116 ast_t *value = optional(ctx, &pos, parse_expr);
117 ast_t *ret = NewAST(ctx->file, start, pos, Return, .value = value);
118 ret = parse_optional_conditional_suffix(ctx, ret);
119 return ret;
122 ast_t *parse_do(parse_ctx_t *ctx, const char *pos) {
123 // do [<indent>] body
124 const char *start = pos;
125 if (!match_word(&pos, "do")) return NULL;
126 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'do'");
127 return NewAST(ctx->file, start, pos, Block, .statements = Match(body, Block)->statements);
130 ast_t *parse_while(parse_ctx_t *ctx, const char *pos) {
131 // while condition ["do"] [<indent>] body
132 const char *start = pos;
133 if (!match_word(&pos, "while")) return NULL;
134 ast_t *condition = expect(ctx, start, &pos, parse_expr, "I don't see a viable condition for this 'while'");
135 (void)match_word(&pos, "do"); // Optional 'do'
136 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'while'");
137 return NewAST(ctx->file, start, pos, While, .condition = condition, .body = body);
140 ast_t *parse_repeat(parse_ctx_t *ctx, const char *pos) {
141 // repeat [<indent>] body
142 const char *start = pos;
143 if (!match_word(&pos, "repeat")) return NULL;
144 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'repeat'");
145 return NewAST(ctx->file, start, pos, Repeat, .body = body);
148 ast_t *parse_if(parse_ctx_t *ctx, const char *pos) {
149 // "if" <condition> ["then"] <body> ["else" <body>] | "unless" <condition> <body> ["else" <body>]
150 const char *start = pos;
151 int64_t starting_indent = get_indent(ctx, pos);
153 bool unless;
154 if (match_word(&pos, "if")) unless = false;
155 else if (match_word(&pos, "unless")) unless = true;
156 else return NULL;
158 ast_t *condition = unless ? NULL : optional(ctx, &pos, parse_declaration);
159 if (!condition) condition = expect(ctx, start, &pos, parse_expr, "I expected to find a condition for this 'if'");
161 if (unless) condition = WrapAST(condition, Not, condition);
163 (void)match_word(&pos, "then"); // Optional 'then'
164 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'if' statement");
166 const char *tmp = pos;
167 whitespace(ctx, &tmp);
168 ast_t *else_body = NULL;
169 const char *else_start = pos;
170 if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) {
171 pos = tmp;
172 spaces(&pos);
173 else_body = optional(ctx, &pos, parse_if);
174 if (!else_body) else_body = expect(ctx, else_start, &pos, parse_block, "I expected a body for this 'else'");
176 return NewAST(ctx->file, start, pos, If, .condition = condition, .body = body, .else_body = else_body);
179 ast_t *parse_when(parse_ctx_t *ctx, const char *pos) {
180 // when <expr> (is var : Tag <body>)* [else <body>]
181 const char *start = pos;
182 int64_t starting_indent = get_indent(ctx, pos);
184 if (!match_word(&pos, "when")) return NULL;
186 ast_t *subject = optional(ctx, &pos, parse_declaration);
187 if (!subject) subject = expect(ctx, start, &pos, parse_expr, "I expected to find an expression for this 'when'");
189 when_clause_t *clauses = NULL;
190 const char *tmp = pos;
191 whitespace(ctx, &tmp);
192 while (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "is")) {
193 pos = tmp;
194 spaces(&pos);
195 ast_t *pattern = expect(ctx, start, &pos, parse_expr, "I expected a pattern to match here");
196 spaces(&pos);
197 when_clause_t *new_clauses = new (when_clause_t, .pattern = pattern, .next = clauses);
198 while (match(&pos, ",")) {
199 pattern = expect(ctx, start, &pos, parse_expr, "I expected a pattern to match here");
200 new_clauses = new (when_clause_t, .pattern = pattern, .next = new_clauses);
201 spaces(&pos);
203 (void)match_word(&pos, "then"); // Optional 'then'
204 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'when' clause");
205 for (when_clause_t *c = new_clauses; c && c != clauses; c = c->next) {
206 c->body = body;
208 clauses = new_clauses;
209 tmp = pos;
210 whitespace(ctx, &tmp);
212 REVERSE_LIST(clauses);
214 ast_t *else_body = NULL;
215 const char *else_start = pos;
216 if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) {
217 pos = tmp;
218 else_body = expect(ctx, else_start, &pos, parse_block, "I expected a body for this 'else'");
220 return NewAST(ctx->file, start, pos, When, .subject = subject, .clauses = clauses, .else_body = else_body);
223 ast_t *parse_for(parse_ctx_t *ctx, const char *pos) {
224 // for [k,] v in iter [<indent>] body
225 const char *start = pos;
226 if (!match_word(&pos, "for")) return NULL;
227 int64_t starting_indent = get_indent(ctx, pos);
228 spaces(&pos);
229 ast_list_t *vars = NULL;
230 for (;;) {
231 ast_t *var = optional(ctx, &pos, parse_var);
232 if (var) vars = new (ast_list_t, .ast = var, .next = vars);
234 spaces(&pos);
235 if (!match(&pos, ",")) break;
238 spaces(&pos);
239 expect_str(ctx, start, &pos, "in", "I expected an 'in' for this 'for'");
241 ast_t *iter = expect(ctx, start, &pos, parse_expr, "I expected an iterable value for this 'for'");
243 (void)match_word(&pos, "do"); // Optional 'do'
245 ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'for'");
247 const char *else_start = pos;
248 whitespace(ctx, &else_start);
249 ast_t *empty = NULL;
250 if (match_word(&else_start, "else") && get_indent(ctx, else_start) == starting_indent) {
251 pos = else_start;
252 empty = expect(ctx, pos, &pos, parse_block, "I expected a body for this 'else'");
254 REVERSE_LIST(vars);
255 return NewAST(ctx->file, start, pos, For, .vars = vars, .iter = iter, .body = body, .empty = empty);