diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-08-25 15:26:07 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-08-25 15:26:07 -0400 |
| commit | fd0033f0ed993ff4dde64d4a33d7accf8ef52c54 (patch) | |
| tree | aafe2319084a5b219b95cadf9425822558ed8714 | |
| parent | 6c134dd340e24e6247a9ca5a03a7c47c27c3d34f (diff) | |
Much more formatting
| -rw-r--r-- | src/formatter.c | 221 |
1 files changed, 215 insertions, 6 deletions
diff --git a/src/formatter.c b/src/formatter.c index cd3e1463..384e3b9a 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -1,6 +1,6 @@ // This code defines functions for transforming ASTs back into Tomo source text -#include <gc.h> +#include <assert.h> #include <setjmp.h> #include "ast.h" @@ -9,7 +9,9 @@ #include "parse/files.h" #include "parse/utils.h" #include "stdlib/datatypes.h" +#include "stdlib/integers.h" #include "stdlib/optionals.h" +#include "stdlib/stdlib.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -197,7 +199,8 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { case EnumDef: case LangDef: case Extend: - case FunctionDef: return NONE_TEXT; + case FunctionDef: + case DocTest: return NONE_TEXT; case If: { DeclareMatch(if_, ast, If); @@ -224,11 +227,98 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { return Texts("while ", must(format_inline_code(loop->condition, comments)), " do ", must(format_inline_code(loop->body, comments))); } - default: { + case List: + case Set: { + ast_list_t *items = ast->tag == List ? Match(ast, List)->items : Match(ast, Set)->items; + Text_t code = EMPTY_TEXT; + for (ast_list_t *item = items; item; item = item->next) { + code = Texts(code, must(format_inline_code(item->ast, comments))); + if (item->next) code = Texts(code, ", "); + } + return ast->tag == List ? Texts("[", code, "]") : Texts("|", code, "|"); + } + case Declare: { + DeclareMatch(decl, ast, Declare); + Text_t code = must(format_inline_code(decl->var, comments)); + if (decl->type) code = Texts(code, " : ", must(format_inline_type(decl->type, comments))); + if (decl->value) + code = + Texts(code, decl->type ? Text(" = ") : Text(" := "), must(format_inline_code(decl->value, comments))); + return code; + } + case Return: { + ast_t *value = Match(ast, Return)->value; + return value ? Texts("return ", must(format_inline_code(value, comments))) : Text("return"); + } + case Optional: { + ast_t *val = Match(ast, Optional)->value; + return Texts(must(format_inline_code(val, comments)), "?"); + } + case NonOptional: { + ast_t *val = Match(ast, NonOptional)->value; + return Texts(must(format_inline_code(val, comments)), "!"); + } + case FieldAccess: { + DeclareMatch(access, ast, FieldAccess); + return Texts(must(format_inline_code(access->fielded, comments)), ".", Text$from_str(access->field)); + } + case Index: { + DeclareMatch(index, ast, Index); + if (index->index) + return Texts(must(format_inline_code(index->indexed, comments)), "[", + must(format_inline_code(index->index, comments)), "]"); + else return Texts(must(format_inline_code(index->indexed, comments)), "[]"); + } + case TextJoin: { + // TODO: choose quotation mark more smartly + Text_t source = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + Text_t quote = Text$to(source, I(1)); + const char *lang = Match(ast, TextJoin)->lang; + Text_t code = lang ? Texts("$", Text$from_str(lang), quote) : quote; + for (ast_list_t *chunk = Match(ast, TextJoin)->children; chunk; chunk = chunk->next) { + if (chunk->ast->tag == TextLiteral) { + Text_t literal = Match(chunk->ast, TextLiteral)->text; + code = Texts(code, Text$slice(Text$quoted(literal, false, quote), I(2), I(-2))); + } else { + code = Texts(code, "$(", must(format_inline_code(chunk->ast, comments)), ")"); + } + } + return Texts(code, quote); + } + case TextLiteral: { + fail("Something went wrong, we shouldn't be formatting text literals directly"); + } + case Stop: { + const char *target = Match(ast, Stop)->target; + return target ? Texts("stop ", Text$from_str(target)) : Text("stop"); + } + case Skip: { + const char *target = Match(ast, Skip)->target; + return target ? Texts("skip ", Text$from_str(target)) : Text("skip"); + } + case None: + case Bool: + case Int: + case Num: + case Var: { Text_t code = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); if (Text$has(code, Text("\n"))) return NONE_TEXT; return code; } + case FunctionCall: { + DeclareMatch(call, ast, FunctionCall); + return Texts(must(format_inline_code(call->fn, comments)), "(", must(format_inline_args(call->args, comments)), + ")"); + } + case BINOP_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + const char *op = binop_operator(ast->tag); + return Texts("(", must(format_inline_code(operands.lhs, comments)), " ", Text$from_str(op), " ", + must(format_inline_code(operands.rhs, comments)), ")"); + } + default: { + fail("Formatting not implemented for: ", ast_to_sexp(ast)); + } } } @@ -242,6 +332,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { bool gap_before_comment = false; const char *comment_pos = ast->start; for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + if (should_have_blank_line(stmt->ast)) add_line(&code, Text(""), indent); + for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, stmt->ast->start)).length > 0;) { if (gap_before_comment) { @@ -254,7 +346,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { add_line(&code, format_code(stmt->ast, comments, indent), indent); comment_pos = stmt->ast->end; - if (should_have_blank_line(stmt->ast) && stmt->next) add_line(&code, Text(""), indent); + if (should_have_blank_line(stmt->ast) && stmt->next && !should_have_blank_line(stmt->next->ast)) + add_line(&code, Text(""), indent); else gap_before_comment = true; } @@ -326,10 +419,126 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { DeclareMatch(extend, ast, Extend); return Texts("lang ", Text$from_str(extend->name), format_namespace(extend->body, comments, indent)); } + case List: + case Set: { + if (inlined_fits) return inlined; + ast_list_t *items = ast->tag == List ? Match(ast, List)->items : Match(ast, Set)->items; + Text_t code = EMPTY_TEXT; + const char *comment_pos = ast->start; + for (ast_list_t *item = items; item; item = item->next) { + for (OptionalText_t comment; + (comment = next_comment(comments, &comment_pos, item->ast->start)).length > 0;) { + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent)); + } + add_line(&code, Texts(format_code(item->ast, comments, Texts(indent, single_indent)), ","), + Texts(indent, single_indent)); + } + for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) { + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent)); + } + return ast->tag == List ? Texts("[\n", indent, single_indent, code, "\n", indent, "]") + : Texts("|\n", indent, single_indent, code, "\n", indent, "|"); + } + case Declare: { + DeclareMatch(decl, ast, Declare); + Text_t code = format_code(decl->var, comments, indent); + if (decl->type) code = Texts(code, " : ", format_type(decl->type, comments, indent)); + if (decl->value) + code = Texts(code, decl->type ? Text(" = ") : Text(" := "), format_code(decl->value, comments, indent)); + return code; + } + case Return: { + ast_t *value = Match(ast, Return)->value; + return value ? Texts("return ", format_code(value, comments, indent)) : Text("return"); + } + case Optional: { + if (inlined_fits) return inlined; + ast_t *val = Match(ast, Optional)->value; + return Texts("(", format_code(val, comments, indent), ")?"); + } + case NonOptional: { + if (inlined_fits) return inlined; + ast_t *val = Match(ast, NonOptional)->value; + return Texts("(", format_code(val, comments, indent), ")!"); + } + case FieldAccess: { + DeclareMatch(access, ast, FieldAccess); + return Texts(format_code(access->fielded, comments, indent), ".", Text$from_str(access->field)); + } + case Index: { + DeclareMatch(index, ast, Index); + if (index->index) + return Texts(format_code(index->indexed, comments, indent), "[", + format_code(index->index, comments, indent), "]"); + else return Texts(format_code(index->indexed, comments, indent), "[]"); + } + case TextJoin: { + if (inlined_fits) return inlined; + // TODO: choose quotation mark more smartly + Text_t source = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + Text_t quote = Text$to(source, I(1)); + const char *lang = Match(ast, TextJoin)->lang; + Text_t code = EMPTY_TEXT; + Text_t current_line = EMPTY_TEXT; + for (ast_list_t *chunk = Match(ast, TextJoin)->children; chunk; chunk = chunk->next) { + if (chunk->ast->tag == TextLiteral) { + Text_t literal = Match(chunk->ast, TextLiteral)->text; + List_t lines = Text$lines(literal); + if (lines.length == 0) continue; + current_line = Texts(current_line, *(Text_t *)lines.data); + for (int64_t i = 1; i < lines.length; i += 1) { + add_line(&code, current_line, Texts(indent, single_indent)); + current_line = *(Text_t *)(lines.data + i * lines.stride); + } + } else { + code = Texts(code, "$(", must(format_inline_code(chunk->ast, comments)), ")"); + } + } + code = Texts(quote, "\n", code, "\n", indent, quote); + if (lang) code = Texts("$", Text$from_str(lang), code); + return code; + } + case TextLiteral: { + fail("Something went wrong, we shouldn't be formatting text literals directly"); + } + case Stop: + case Skip: + case None: + case Bool: + case Int: + case Num: + case Var: { + assert(inlined.length >= 0); + return inlined; + } + case FunctionCall: { + if (inlined_fits) return inlined; + DeclareMatch(call, ast, FunctionCall); + return Texts(format_code(call->fn, comments, indent), "(", + format_args(call->args, comments, Texts(indent, single_indent)), ")"); + } + case DocTest: { + DeclareMatch(test, ast, DocTest); + Text_t expr = format_code(test->expr, comments, indent); + Text_t code = Texts(">> ", Text$replace(expr, Texts("\n", indent), Texts("\n", indent, ".. "))); + if (test->expected) { + Text_t expected = format_code(test->expected, comments, indent); + code = Texts(code, "\n", indent, "= ", + Text$replace(expected, Texts("\n", indent), Texts("\n", indent, ".. "))); + } + return code; + } + case BINOP_CASES: { + if (inlined_fits) return inlined; + binary_operands_t operands = BINARY_OPERANDS(ast); + const char *op = binop_operator(ast->tag); + return Texts("(\n", indent, single_indent, format_code(operands.lhs, comments, Texts(indent, single_indent)), + " ", Text$from_str(op), " ", format_code(operands.rhs, comments, Texts(indent, single_indent)), + "\n", indent, ")"); + } default: { if (inlined_fits) return inlined; - Text_t code = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); - return Text$replace(code, Text("\t"), single_indent); + fail("Formatting not implemented for: ", ast_to_sexp(ast)); } } } |
