code / tomo

Lines41.3K C23.7K Markdown9.7K YAML5.0K Tomo2.3K
7 others 763
Python231 Shell230 make212 INI47 Text21 SVG16 Lua6
(851 lines)
1 // This code defines functions for transforming ASTs back into Tomo source text
3 #include <assert.h>
4 #include <setjmp.h>
5 #include <stdbool.h>
6 #include <stdint.h>
7 #include <unictype.h>
9 #include "../ast.h"
10 #include "../parse/context.h"
11 #include "../parse/files.h"
12 #include "../parse/utils.h"
13 #include "../stdlib/datatypes.h"
14 #include "../stdlib/integers.h"
15 #include "../stdlib/optionals.h"
16 #include "../stdlib/stdlib.h"
17 #include "../stdlib/text.h"
18 #include "args.h"
19 #include "enums.h"
20 #include "formatter.h"
21 #include "types.h"
22 #include "utils.h"
24 #define fmt_inline(...) must(format_inline_code(__VA_ARGS__))
25 #define fmt(...) format_code(__VA_ARGS__)
27 Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) {
28 if (unwrap_block(namespace) == NULL) return EMPTY_TEXT;
29 return Texts("\n", indent, single_indent, fmt(namespace, comments, Texts(indent, single_indent)));
32 typedef struct {
33 Text_t quote, unquote, interp;
34 } text_opts_t;
36 PUREFUNC text_opts_t choose_text_options(ast_list_t *chunks) {
37 int double_quotes = 0, single_quotes = 0, backticks = 0;
38 for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
39 if (chunk->ast->tag == TextLiteral) {
40 Text_t literal = Match(chunk->ast, TextLiteral)->text;
41 if (Text$has(literal, Text("\""))) double_quotes += 1;
42 if (Text$has(literal, Text("'"))) single_quotes += 1;
43 if (Text$has(literal, Text("`"))) backticks += 1;
46 Text_t quote;
47 if (double_quotes == 0) quote = Text("\"");
48 else if (single_quotes == 0) quote = Text("'");
49 else if (backticks == 0) quote = Text("`");
50 else quote = Text("\"");
52 text_opts_t opts = {.quote = quote, .unquote = quote, .interp = Text("$")};
53 return opts;
56 static bool starts_with_id(Text_t text) {
57 if (text.length <= 0) return false;
58 List_t codepoints = Text$utf32(Text$slice(text, I_small(1), I_small(1)));
59 if (codepoints.length <= 0 || codepoints.data == NULL) return false;
60 return uc_is_property_xid_continue(*(ucs4_t *)codepoints.data);
63 static OptionalText_t format_inline_text(text_opts_t opts, ast_list_t *chunks, Table_t comments) {
64 Text_t code = opts.quote;
65 for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
66 if (chunk->ast->tag == TextLiteral) {
67 Text_t literal = Match(chunk->ast, TextLiteral)->text;
68 Text_t segment = Text$escaped(literal, false, Texts(opts.unquote, opts.interp));
69 code = Texts(code, segment);
70 } else {
71 if (chunk->ast->tag == Var
72 && (!chunk->next || chunk->next->ast->tag != TextLiteral
73 || !starts_with_id(Match(chunk->next->ast, TextLiteral)->text))) {
74 code = Texts(code, opts.interp, fmt_inline(chunk->ast, comments));
75 } else {
76 code = Texts(code, opts.interp, "(", fmt_inline(chunk->ast, comments), ")");
80 return Texts(code, opts.unquote);
83 static Text_t format_text(text_opts_t opts, ast_list_t *chunks, Table_t comments, Text_t indent) {
84 Text_t code = EMPTY_TEXT;
85 Text_t current_line = EMPTY_TEXT;
86 for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) {
87 if (chunk->ast->tag == TextLiteral) {
88 Text_t literal = Match(chunk->ast, TextLiteral)->text;
89 List_t lines = Text$lines(literal);
90 if (lines.length == 0) continue;
91 current_line = Texts(current_line, Text$escaped(*(Text_t *)lines.data, false, opts.interp));
92 for (int64_t i = 1; i < (int64_t)lines.length; i += 1) {
93 add_line(&code, current_line, Texts(indent, single_indent));
94 current_line = Text$escaped(*(Text_t *)(lines.data + i * lines.stride), false, opts.interp);
96 } else {
97 current_line = Texts(current_line, opts.interp, "(", fmt(chunk->ast, comments, indent), ")");
100 add_line(&code, current_line, Texts(indent, single_indent));
101 code = Texts(opts.quote, "\n", indent, single_indent, code, "\n", indent, opts.unquote);
102 return code;
105 OptionalText_t format_inline_code(ast_t *ast, Table_t comments) {
106 if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT;
107 switch (ast->tag) {
108 /*inline*/ case Unknown:
109 fail("Invalid AST");
110 /*inline*/ case Block: {
111 ast_list_t *statements = Match(ast, Block)->statements;
112 if (statements == NULL) return Text("pass");
113 else if (statements->next == NULL) return fmt_inline(statements->ast, comments);
114 else return NONE_TEXT;
116 /*inline*/ case StructDef:
117 /*inline*/ case EnumDef:
118 /*inline*/ case LangDef:
119 /*inline*/ case FunctionDef:
120 /*inline*/ case ConvertDef:
121 /*inline*/ case DebugLog:
122 return NONE_TEXT;
123 /*inline*/ case Assert: {
124 DeclareMatch(assert, ast, Assert);
125 Text_t expr = fmt_inline(assert->expr, comments);
126 if (!assert->message) return Texts("assert ", expr);
127 Text_t message = fmt_inline(assert->message, comments);
128 return Texts("assert ", expr, ", ", message);
130 /*inline*/ case Defer:
131 return Texts("defer ", fmt_inline(Match(ast, Defer)->body, comments));
132 /*inline*/ case Lambda: {
133 DeclareMatch(lambda, ast, Lambda);
134 Text_t code = Texts("func(", format_inline_args(lambda->args, comments));
135 if (lambda->ret_type)
136 code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type));
137 code = Texts(code, ") ", fmt_inline(lambda->body, comments));
138 return Texts(code);
140 /*inline*/ case If: {
141 DeclareMatch(if_, ast, If);
143 Text_t if_condition = if_->condition->tag == Not
144 ? Texts("unless ", fmt_inline(Match(if_->condition, Not)->value, comments))
145 : Texts("if ", fmt_inline(if_->condition, comments));
147 if (if_->else_body == NULL && if_->condition->tag != Declare) {
148 ast_t *stmt = unwrap_block(if_->body);
149 if (!stmt) return Texts("pass ", if_condition);
150 switch (stmt->tag) {
151 case Return:
152 case Skip:
153 case Stop: return Texts(fmt_inline(stmt, comments), " ", if_condition);
154 default: break;
158 Text_t code = Texts(if_condition, " then ", fmt_inline(if_->body, comments));
159 if (if_->else_body) code = Texts(code, " else ", fmt_inline(if_->else_body, comments));
160 return code;
162 /*inline*/ case When: {
163 DeclareMatch(when, ast, When);
164 Text_t code = Texts("when ", fmt_inline(when->subject, comments));
165 for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
166 code = Texts(code, " is ", fmt_inline(clause->pattern, comments));
167 while (clause->next && clause->next->body == clause->body) {
168 clause = clause->next;
169 code = Texts(code, ", ", fmt_inline(clause->pattern, comments));
171 code = Texts(code, " then ", fmt_inline(clause->body, comments));
173 if (when->else_body) code = Texts(code, " else ", fmt_inline(when->else_body, comments));
174 return code;
176 /*inline*/ case Repeat:
177 return Texts("repeat ", fmt_inline(Match(ast, Repeat)->body, comments));
178 /*inline*/ case While: {
179 DeclareMatch(loop, ast, While);
180 return Texts("while ", fmt_inline(loop->condition, comments), " do ", fmt_inline(loop->body, comments));
182 /*inline*/ case For: {
183 DeclareMatch(loop, ast, For);
184 Text_t code = Text("for ");
185 for (ast_list_t *var = loop->vars; var; var = var->next) {
186 code = Texts(code, fmt_inline(var->ast, comments));
187 if (var->next) code = Texts(code, ", ");
189 code = Texts(code, " in ", fmt_inline(loop->iter, comments), " do ", fmt_inline(loop->body, comments));
190 if (loop->empty) code = Texts(code, " else ", fmt_inline(loop->empty, comments));
191 return code;
193 /*inline*/ case Comprehension: {
194 DeclareMatch(comp, ast, Comprehension);
195 Text_t code = Texts(fmt_inline(comp->expr, comments), " for ");
196 for (ast_list_t *var = comp->vars; var; var = var->next) {
197 code = Texts(code, fmt_inline(var->ast, comments));
198 if (var->next) code = Texts(code, ", ");
200 code = Texts(code, " in ", fmt_inline(comp->iter, comments));
201 if (comp->filter) code = Texts(code, " if ", fmt_inline(comp->filter, comments));
202 return code;
204 /*inline*/ case List: {
205 ast_list_t *items = Match(ast, List)->items;
206 Text_t code = EMPTY_TEXT;
207 for (ast_list_t *item = items; item; item = item->next) {
208 code = Texts(code, fmt_inline(item->ast, comments));
209 if (item->next) code = Texts(code, ", ");
211 return Texts("[", code, "]");
213 /*inline*/ case Table: {
214 DeclareMatch(table, ast, Table);
215 Text_t code = EMPTY_TEXT;
216 for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
217 code = Texts(code, fmt_inline(entry->ast, comments));
218 if (entry->next) code = Texts(code, ", ");
220 if (table->fallback) code = Texts(code, "; fallback=", fmt_inline(table->fallback, comments));
221 if (table->default_value) code = Texts(code, "; default=", fmt_inline(table->default_value, comments));
222 return Texts("{", code, "}");
224 /*inline*/ case TableEntry: {
225 DeclareMatch(entry, ast, TableEntry);
226 if (entry->value) return Texts(fmt_inline(entry->key, comments), ": ", fmt_inline(entry->value, comments));
227 else return Texts(fmt_inline(entry->key, comments));
229 /*inline*/ case Declare: {
230 DeclareMatch(decl, ast, Declare);
231 Text_t code = fmt_inline(decl->var, comments);
232 if (decl->type) code = Texts(code, " : ", format_type(decl->type));
233 if (decl->value) code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt_inline(decl->value, comments));
234 return code;
236 /*inline*/ case Assign: {
237 DeclareMatch(assign, ast, Assign);
238 Text_t code = EMPTY_TEXT;
239 for (ast_list_t *target = assign->targets; target; target = target->next) {
240 code = Texts(code, fmt_inline(target->ast, comments));
241 if (target->next) code = Texts(code, ", ");
243 code = Texts(code, " = ");
244 for (ast_list_t *value = assign->values; value; value = value->next) {
245 code = Texts(code, fmt_inline(value->ast, comments));
246 if (value->next) code = Texts(code, ", ");
248 return code;
250 /*inline*/ case Pass:
251 return Text("pass");
252 /*inline*/ case Return: {
253 ast_t *value = Match(ast, Return)->value;
254 return value ? Texts("return ", fmt_inline(value, comments)) : Text("return");
256 /*inline*/ case Not: {
257 ast_t *val = Match(ast, Not)->value;
258 return Texts("not ", must(termify_inline(val, comments)));
260 /*inline*/ case Negative: {
261 ast_t *val = Match(ast, Negative)->value;
262 return Texts("-", must(termify_inline(val, comments)));
264 /*inline*/ case HeapAllocate: {
265 ast_t *val = Match(ast, HeapAllocate)->value;
266 return Texts("@", must(termify_inline(val, comments)));
268 /*inline*/ case StackReference: {
269 ast_t *val = Match(ast, StackReference)->value;
270 return Texts("&", must(termify_inline(val, comments)));
272 /*inline*/ case NonOptional: {
273 ast_t *val = Match(ast, NonOptional)->value;
274 return Texts(must(termify_inline(val, comments)), "!");
276 /*inline*/ case FieldAccess: {
277 DeclareMatch(access, ast, FieldAccess);
278 return Texts(must(termify_inline(access->fielded, comments)), ".", Text$from_str(access->field));
280 /*inline*/ case Index: {
281 DeclareMatch(index, ast, Index);
282 Text_t indexed = must(termify_inline(index->indexed, comments));
283 if (index->index) return Texts(indexed, "[", fmt_inline(index->index, comments), "]");
284 else return Texts(indexed, "[]");
286 /*inline*/ case TextJoin: {
287 text_opts_t opts = choose_text_options(Match(ast, TextJoin)->children);
288 Text_t ret = must(format_inline_text(opts, Match(ast, TextJoin)->children, comments));
289 const char *lang = Match(ast, TextJoin)->lang;
290 return lang ? Texts("$", Text$from_str(lang), ret) : ret;
292 /*inline*/ case InlineCCode: {
293 DeclareMatch(c_code, ast, InlineCCode);
294 Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast)) : Text("C_code");
295 text_opts_t opts = {.quote = Text("`"), .unquote = Text("`"), .interp = Text("@")};
296 return Texts(code, must(format_inline_text(opts, Match(ast, InlineCCode)->chunks, comments)));
298 /*inline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); }
299 /*inline*/ case Path: {
300 return Texts("(", Text$escaped(Text$from_str(Match(ast, Path)->path), false, Text("()")), ")");
302 /*inline*/ case Stop: {
303 const char *target = Match(ast, Stop)->target;
304 return target ? Texts("stop ", Text$from_str(target)) : Text("stop");
306 /*inline*/ case Skip: {
307 const char *target = Match(ast, Skip)->target;
308 return target ? Texts("skip ", Text$from_str(target)) : Text("skip");
310 /*inline*/ case Min:
311 /*inline*/ case Max: {
312 Text_t lhs = fmt_inline(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments);
313 Text_t rhs = fmt_inline(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments);
314 ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key;
315 return Texts(lhs, key ? fmt_inline(key, comments) : (ast->tag == Min ? Text(" _min_ ") : Text(" _max_ ")), rhs);
317 /*inline*/ case Reduction: {
318 DeclareMatch(reduction, ast, Reduction);
319 if (reduction->key) {
320 return Texts("(", fmt_inline(reduction->key, comments), ": ", fmt_inline(reduction->iter, comments));
321 } else {
322 return Texts("(", Text$from_str(binop_info[reduction->op].operator), ": ",
323 fmt_inline(reduction->iter, comments));
326 /*inline*/ case None:
327 return Text("none");
328 /*inline*/ case Bool:
329 return Match(ast, Bool)->b ? Text("yes") : Text("no");
330 /*inline*/ case Int: {
331 OptionalText_t source = ast_source(ast);
332 return source.length > 0 ? source : Text$from_str(Match(ast, Int)->str);
334 /*inline*/ case Num: {
335 OptionalText_t source = ast_source(ast);
336 return source.length > 0 ? source : Text$from_str(String(Match(ast, Num)->n));
338 /*inline*/ case Var:
339 return Text$from_str(Match(ast, Var)->name);
340 /*inline*/ case FunctionCall: {
341 DeclareMatch(call, ast, FunctionCall);
342 return Texts(fmt_inline(call->fn, comments), "(", must(format_inline_args(call->args, comments)), ")");
344 /*inline*/ case MethodCall: {
345 DeclareMatch(call, ast, MethodCall);
346 Text_t self = fmt_inline(call->self, comments);
347 if (is_binary_operation(call->self) || call->self->tag == Negative || call->self->tag == Not)
348 self = parenthesize(self, EMPTY_TEXT);
349 return Texts(self, ".", Text$from_str(call->name), "(", must(format_inline_args(call->args, comments)), ")");
351 /*inline*/ case BINOP_CASES: {
352 binary_operands_t operands = BINARY_OPERANDS(ast);
353 const char *op = binop_info[ast->tag].operator;
355 Text_t lhs = fmt_inline(operands.lhs, comments);
356 Text_t rhs = fmt_inline(operands.rhs, comments);
358 if (is_update_assignment(ast)) {
359 return Texts(lhs, " ", Text$from_str(op), " ", rhs);
362 if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])
363 lhs = parenthesize(lhs, EMPTY_TEXT);
364 if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])
365 rhs = parenthesize(rhs, EMPTY_TEXT);
367 Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" ");
368 return Texts(lhs, space, Text$from_str(binop_info[ast->tag].operator), space, rhs);
370 /*inline*/ case Use: {
371 DeclareMatch(use, ast, Use);
372 return Texts("use ", use->path);
374 /*inline*/ case ExplicitlyTyped:
375 fail("Explicitly typed AST nodes are only meant to be used internally.");
376 default: {
377 fail("Formatting not implemented for: ", ast_to_sexp(ast));
382 PUREFUNC static int64_t trailing_line_len(Text_t text) {
383 TextIter_t state = NEW_TEXT_ITER_STATE(text);
384 int64_t len = 0;
385 for (int64_t i = text.length - 1; i >= 0; i--) {
386 int32_t g = Text$get_grapheme_fast(&state, i);
387 if (g == '\n' || g == '\r') break;
388 len += 1;
390 return len;
393 Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) {
394 OptionalText_t inlined = format_inline_code(ast, comments);
395 bool inlined_fits = (inlined.tag != TEXT_NONE && indent.length + inlined.length <= MAX_WIDTH);
397 switch (ast->tag) {
398 /*multiline*/ case Unknown:
399 fail("Invalid AST");
400 /*multiline*/ case Block: {
401 Text_t code = EMPTY_TEXT;
402 bool gap_before_comment = false;
403 const char *comment_pos = ast->start;
404 for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) {
405 for (OptionalText_t comment;
406 (comment = next_comment(comments, &comment_pos, stmt->ast->start)).length > 0;) {
407 if (gap_before_comment) {
408 add_line(&code, Text(""), indent);
409 gap_before_comment = false;
411 add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent);
414 if (stmt->ast->tag == Block) {
415 add_line(&code,
416 Texts("do\n", indent, single_indent, fmt(stmt->ast, comments, Texts(indent, single_indent))),
417 indent);
418 } else {
419 add_line(&code, fmt(stmt->ast, comments, indent), indent);
421 comment_pos = stmt->ast->end;
423 if (stmt->next) {
424 int suggested_blanks = suggested_blank_lines(stmt->ast, stmt->next->ast);
425 for (int blanks = suggested_blanks; blanks > 0; blanks--)
426 add_line(&code, Text(""), indent);
427 gap_before_comment = (suggested_blanks == 0);
428 } else gap_before_comment = true;
431 for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) {
432 if (gap_before_comment) {
433 add_line(&code, Text(""), indent);
434 gap_before_comment = false;
436 add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent);
438 return code;
440 /*multiline*/ case If: {
441 DeclareMatch(if_, ast, If);
442 Text_t code = if_->condition->tag == Not
443 ? Texts("unless ", fmt(Match(if_->condition, Not)->value, comments, indent))
444 : Texts("if ", fmt(if_->condition, comments, indent));
446 code = Texts(code, "\n", indent, single_indent, fmt(if_->body, comments, Texts(indent, single_indent)));
447 if (if_->else_body) {
448 if (if_->else_body->tag != If) {
449 code = Texts(code, "\n", indent, "else\n", indent, single_indent,
450 fmt(if_->else_body, comments, Texts(indent, single_indent)));
451 } else {
452 code = Texts(code, "\n", indent, "else ", fmt(if_->else_body, comments, indent));
455 return code;
457 /*multiline*/ case When: {
458 DeclareMatch(when, ast, When);
459 Text_t code = Texts("when ", fmt(when->subject, comments, indent));
460 for (when_clause_t *clause = when->clauses; clause; clause = clause->next) {
461 code = Texts(code, "\n", indent, "is ", fmt(clause->pattern, comments, indent));
462 while (clause->next && clause->next->body == clause->body) {
463 clause = clause->next;
464 code = Texts(code, ", ", fmt(clause->pattern, comments, indent));
466 code = Texts(code, format_namespace(clause->body, comments, indent));
468 if (when->else_body)
469 code = Texts(code, "\n", indent, "else", format_namespace(when->else_body, comments, indent));
470 return code;
472 /*multiline*/ case Repeat: {
473 return Texts("repeat\n", indent, single_indent,
474 fmt(Match(ast, Repeat)->body, comments, Texts(indent, single_indent)));
476 /*multiline*/ case While: {
477 DeclareMatch(loop, ast, While);
478 return Texts("while ", fmt(loop->condition, comments, indent), "\n", indent, single_indent,
479 fmt(loop->body, comments, Texts(indent, single_indent)));
481 /*multiline*/ case For: {
482 DeclareMatch(loop, ast, For);
483 Text_t code = Text("for ");
484 for (ast_list_t *var = loop->vars; var; var = var->next) {
485 code = Texts(code, fmt(var->ast, comments, indent));
486 if (var->next) code = Texts(code, ", ");
488 code = Texts(code, " in ", fmt(loop->iter, comments, indent), format_namespace(loop->body, comments, indent));
489 if (loop->empty) code = Texts(code, "\n", indent, "else", format_namespace(loop->empty, comments, indent));
490 return code;
492 /*multiline*/ case Comprehension: {
493 if (inlined_fits) return inlined;
494 DeclareMatch(comp, ast, Comprehension);
495 Text_t code = Texts("(", fmt(comp->expr, comments, indent));
496 if (code.length >= MAX_WIDTH) code = Texts(code, "\n", indent, "for ");
497 else code = Texts(code, " for ");
499 for (ast_list_t *var = comp->vars; var; var = var->next) {
500 code = Texts(code, fmt(var->ast, comments, indent));
501 if (var->next) code = Texts(code, ", ");
504 code = Texts(code, " in ", fmt(comp->iter, comments, indent));
506 if (comp->filter) {
507 if (code.length >= MAX_WIDTH) code = Texts(code, "\n", indent, "if ");
508 else code = Texts(code, " if ");
509 code = Texts(code, fmt(comp->filter, comments, indent));
511 return code;
513 /*multiline*/ case FunctionDef: {
514 DeclareMatch(func, ast, FunctionDef);
515 Text_t code = Texts("func ", fmt(func->name, comments, indent), "(", format_args(func->args, comments, indent));
516 if (func->ret_type) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type));
517 if (func->cache) code = Texts(code, "; cache=", fmt(func->cache, comments, indent));
518 if (func->is_inline) code = Texts(code, "; inline");
519 code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent,
520 single_indent, fmt(func->body, comments, Texts(indent, single_indent)));
521 return Texts(code);
523 /*multiline*/ case Lambda: {
524 if (inlined_fits) return inlined;
525 DeclareMatch(lambda, ast, Lambda);
526 Text_t code = Texts("func(", format_args(lambda->args, comments, indent));
527 if (lambda->ret_type)
528 code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type));
529 code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent,
530 single_indent, fmt(lambda->body, comments, Texts(indent, single_indent)));
531 return Texts(code);
533 /*multiline*/ case ConvertDef: {
534 DeclareMatch(convert, ast, ConvertDef);
535 Text_t code = Texts("convert (", format_args(convert->args, comments, indent));
536 if (convert->ret_type)
537 code = Texts(code, convert->args ? Text(" -> ") : Text("-> "), format_type(convert->ret_type));
538 if (convert->cache) code = Texts(code, "; cache=", fmt(convert->cache, comments, indent));
539 if (convert->is_inline) code = Texts(code, "; inline");
540 code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent,
541 single_indent, fmt(convert->body, comments, Texts(indent, single_indent)));
542 return Texts(code);
544 /*multiline*/ case StructDef: {
545 DeclareMatch(def, ast, StructDef);
546 Text_t args = format_args(def->fields, comments, indent);
547 Text_t code = Texts("struct ", Text$from_str(def->name), "(", args);
548 if (def->secret) code = Texts(code, "; secret");
549 if (def->external) code = Texts(code, "; external");
550 if (def->opaque) code = Texts(code, "; opaque");
551 code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"));
552 return Texts(code, format_namespace(def->namespace, comments, indent));
554 /*multiline*/ case EnumDef: {
555 DeclareMatch(def, ast, EnumDef);
556 Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent));
557 return Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"),
558 format_namespace(def->namespace, comments, indent));
560 /*multiline*/ case LangDef: {
561 DeclareMatch(def, ast, LangDef);
562 return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent));
564 /*multiline*/ case Defer:
565 return Texts("defer ", format_namespace(Match(ast, Defer)->body, comments, indent));
566 /*multiline*/ case List: {
567 if (inlined_fits) return inlined;
568 ast_list_t *items = Match(ast, List)->items;
569 Text_t code = Text("[");
570 const char *comment_pos = ast->start;
571 for (ast_list_t *item = items; item; item = item->next) {
572 for (OptionalText_t comment;
573 (comment = next_comment(comments, &comment_pos, item->ast->start)).length > 0;) {
574 add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
576 Text_t item_text = fmt(item->ast, comments, Texts(indent, single_indent));
577 if (Text$ends_with(code, Text(","), NULL)) {
578 if (!Text$has(item_text, Text("\n")) && trailing_line_len(code) + 1 + item_text.length + 1 <= MAX_WIDTH)
579 code = Texts(code, " ", item_text, ",");
580 else code = Texts(code, "\n", indent, single_indent, item_text, ",");
581 } else {
582 add_line(&code, Texts(item_text, ","), Texts(indent, single_indent));
585 for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) {
586 add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
588 return Texts(code, "\n", indent, "]");
590 /*multiline*/ case Table: {
591 if (inlined_fits) return inlined;
592 DeclareMatch(table, ast, Table);
593 Text_t code = Texts("{");
594 const char *comment_pos = ast->start;
595 for (ast_list_t *entry = table->entries; entry; entry = entry->next) {
596 for (OptionalText_t comment;
597 (comment = next_comment(comments, &comment_pos, entry->ast->start)).length > 0;) {
598 add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
601 Text_t entry_text = fmt(entry->ast, comments, Texts(indent, single_indent));
602 if (Text$ends_with(code, Text(","), NULL)) {
603 if (!Text$has(entry_text, Text("\n"))
604 && trailing_line_len(code) + 1 + entry_text.length + 1 <= MAX_WIDTH)
605 code = Texts(code, " ", entry_text, ",");
606 else code = Texts(code, "\n", indent, single_indent, entry_text, ",");
607 } else {
608 add_line(&code, Texts(entry_text, ","), Texts(indent, single_indent));
611 add_line(&code, Texts(entry_text, ","), Texts(indent, single_indent));
613 for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) {
614 add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent));
617 if (table->fallback)
618 code = Texts(code, ";\n", indent, single_indent, "fallback=", fmt(table->fallback, comments, indent));
620 if (table->default_value)
621 code = Texts(code, ";\n", indent, single_indent, "default=", fmt(table->default_value, comments, indent));
623 return Texts(code, "\n", indent, "}");
625 /*multiline*/ case TableEntry: {
626 if (inlined_fits) return inlined;
627 DeclareMatch(entry, ast, TableEntry);
628 if (entry->value) return Texts(fmt(entry->key, comments, indent), ": ", fmt(entry->value, comments, indent));
629 else return Texts(fmt(entry->key, comments, indent));
631 /*multiline*/ case Declare: {
632 if (inlined_fits) return inlined;
633 DeclareMatch(decl, ast, Declare);
634 Text_t code = fmt(decl->var, comments, indent);
635 if (decl->type) code = Texts(code, " : ", format_type(decl->type));
636 if (decl->value)
637 code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt(decl->value, comments, indent));
638 return code;
640 /*multiline*/ case Assign: {
641 if (inlined_fits) return inlined;
642 DeclareMatch(assign, ast, Assign);
643 Text_t code = EMPTY_TEXT;
644 for (ast_list_t *target = assign->targets; target; target = target->next) {
645 code = Texts(code, fmt(target->ast, comments, indent));
646 if (target->next) code = Texts(code, ", ");
648 code = Texts(code, " = ");
649 for (ast_list_t *value = assign->values; value; value = value->next) {
650 code = Texts(code, fmt(value->ast, comments, indent));
651 if (value->next) code = Texts(code, ", ");
653 return code;
655 /*multiline*/ case Pass:
656 return Text("pass");
657 /*multiline*/ case Return: {
658 if (inlined_fits) return inlined;
659 ast_t *value = Match(ast, Return)->value;
660 return value ? Texts("return ", fmt(value, comments, indent)) : Text("return");
662 /*inline*/ case Not: {
663 if (inlined_fits) return inlined;
664 ast_t *val = Match(ast, Not)->value;
665 if (is_binary_operation(val)) return Texts("not ", termify(val, comments, indent));
666 else return Texts("not ", fmt(val, comments, indent));
668 /*inline*/ case Negative: {
669 if (inlined_fits) return inlined;
670 ast_t *val = Match(ast, Negative)->value;
671 if (is_binary_operation(val)) return Texts("-", termify(val, comments, indent));
672 else return Texts("-", fmt(val, comments, indent));
674 /*multiline*/ case HeapAllocate: {
675 if (inlined_fits) return inlined;
676 ast_t *val = Match(ast, HeapAllocate)->value;
677 return Texts("@", termify(val, comments, indent), "");
679 /*multiline*/ case StackReference: {
680 if (inlined_fits) return inlined;
681 ast_t *val = Match(ast, StackReference)->value;
682 return Texts("&(", termify(val, comments, indent), ")");
684 /*multiline*/ case NonOptional: {
685 if (inlined_fits) return inlined;
686 ast_t *val = Match(ast, NonOptional)->value;
687 return Texts(termify(val, comments, indent), "!");
689 /*multiline*/ case FieldAccess: {
690 if (inlined_fits) return inlined;
691 DeclareMatch(access, ast, FieldAccess);
692 return Texts(termify(access->fielded, comments, indent), ".", Text$from_str(access->field));
694 /*multiline*/ case Index: {
695 if (inlined_fits) return inlined;
696 DeclareMatch(index, ast, Index);
697 if (index->index)
698 return Texts(termify(index->indexed, comments, indent), "[", fmt(index->index, comments, indent), "]");
699 else return Texts(termify(index->indexed, comments, indent), "[]");
701 /*multiline*/ case TextJoin: {
702 if (inlined_fits) return inlined;
704 text_opts_t opts = choose_text_options(Match(ast, TextJoin)->children);
705 Text_t ret = format_text(opts, Match(ast, TextJoin)->children, comments, indent);
706 const char *lang = Match(ast, TextJoin)->lang;
707 return lang ? Texts("$", Text$from_str(lang), ret) : ret;
709 /*multiline*/ case InlineCCode: {
710 DeclareMatch(c_code, ast, InlineCCode);
711 if (inlined_fits && c_code->type != NULL) return inlined;
712 Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast)) : Text("C_code");
713 text_opts_t opts = {.quote = Text("`"), .unquote = Text("`"), .interp = Text("@")};
714 return Texts(code, format_text(opts, Match(ast, InlineCCode)->chunks, comments, indent));
716 /*multiline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); }
717 /*multiline*/ case Path: {
718 assert(inlined.length > 0);
719 return inlined;
721 /*multiline*/ case Min:
722 /*multiline*/ case Max: {
723 if (inlined_fits) return inlined;
724 Text_t lhs = termify(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments, indent);
725 Text_t rhs = termify(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments, indent);
726 ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key;
727 Text_t op = key ? fmt(key, comments, indent) : (ast->tag == Min ? Text("_min_") : Text("_max_"));
728 return Texts(lhs, " ", op, " ", rhs);
730 /*multiline*/ case Reduction: {
731 if (inlined_fits) return inlined;
732 DeclareMatch(reduction, ast, Reduction);
733 if (reduction->key) {
734 return Texts("(", fmt(reduction->key, comments, Texts(indent, single_indent)), ": ",
735 fmt(reduction->iter, comments, Texts(indent, single_indent)));
736 } else {
737 return Texts("(", binop_info[reduction->op].operator, ": ",
738 fmt(reduction->iter, comments, Texts(indent, single_indent)));
741 /*multiline*/ case Stop:
742 /*multiline*/ case Skip:
743 /*multiline*/ case None:
744 /*multiline*/ case Bool:
745 /*multiline*/ case Int:
746 /*multiline*/ case Num:
747 /*multiline*/ case Var: {
748 assert(inlined.tag != TEXT_NONE);
749 return inlined;
751 /*multiline*/ case FunctionCall: {
752 if (inlined_fits) return inlined;
753 DeclareMatch(call, ast, FunctionCall);
754 Text_t args = format_args(call->args, comments, indent);
755 return Texts(fmt(call->fn, comments, indent), "(", args,
756 Text$has(args, Text("\n")) ? Texts("\n", indent) : EMPTY_TEXT, ")");
758 /*multiline*/ case MethodCall: {
759 if (inlined_fits) return inlined;
760 DeclareMatch(call, ast, MethodCall);
761 Text_t args = format_args(call->args, comments, indent);
762 return Texts(termify(call->self, comments, indent), ".", Text$from_str(call->name), "(", args,
763 Text$has(args, Text("\n")) ? Texts("\n", indent) : EMPTY_TEXT, ")");
765 /*multiline*/ case DebugLog: {
766 DeclareMatch(debug, ast, DebugLog);
767 Text_t code = Texts(">> ");
768 for (ast_list_t *value = debug->values; value; value = value->next) {
769 Text_t expr = fmt(value->ast, comments, indent);
770 code = Texts(code, expr);
771 if (value->next) code = Texts(code, ", ");
773 return code;
775 /*multiline*/ case Assert: {
776 DeclareMatch(assert, ast, Assert);
777 Text_t expr = fmt(assert->expr, comments, indent);
778 if (!assert->message) return Texts("assert ", expr);
779 Text_t message = fmt(assert->message, comments, indent);
780 return Texts("assert ", expr, ", ", message);
782 /*multiline*/ case BINOP_CASES: {
783 if (inlined_fits) return inlined;
784 binary_operands_t operands = BINARY_OPERANDS(ast);
785 const char *op = binop_info[ast->tag].operator;
786 Text_t lhs = fmt(operands.lhs, comments, indent);
787 Text_t rhs = fmt(operands.rhs, comments, indent);
789 if (is_update_assignment(ast)) {
790 return Texts(lhs, " ", Text$from_str(op), " ", rhs);
793 if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])
794 lhs = parenthesize(lhs, indent);
795 if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])
796 rhs = parenthesize(rhs, indent);
798 Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" ");
799 return Texts(lhs, space, Text$from_str(binop_info[ast->tag].operator), space, rhs);
801 /*multiline*/ case Use: {
802 assert(inlined.length > 0);
803 return inlined;
805 /*multiline*/ case ExplicitlyTyped:
806 fail("Explicitly typed AST nodes are only meant to be used internally.");
807 default: {
808 if (inlined_fits) return inlined;
809 fail("Formatting not implemented for: ", ast_to_sexp(ast));
814 Text_t format_file(const char *path) {
815 file_t *file = load_file(path);
816 if (!file) return EMPTY_TEXT;
818 jmp_buf on_err;
819 if (setjmp(on_err) != 0) {
820 return Text$from_str(file->text);
822 parse_ctx_t ctx = {
823 .file = file,
824 .on_err = &on_err,
825 .comments = {},
828 const char *pos = file->text;
829 if (match(&pos, "#!")) // shebang
830 some_not(&pos, "\r\n");
832 whitespace(&ctx, &pos);
833 ast_t *ast = parse_file_body(&ctx, pos);
834 if (!ast) return Text$from_str(file->text);
835 pos = ast->end;
836 whitespace(&ctx, &pos);
837 if (pos < file->text + file->len && *pos != '\0') {
838 return Text$from_str(file->text);
841 const char *fmt_pos = file->text;
842 Text_t code = EMPTY_TEXT;
843 for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) {
844 code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n");
846 code = Texts(code, fmt(ast, ctx.comments, EMPTY_TEXT));
847 for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) {
848 code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n");
850 return code;