From 55479cbf9e4a8f36afe41d84df687f05fc7661f0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 12:12:02 -0400 Subject: Initial work to pass metadata for code --- src/formatter.c | 23 +++++++++++++++++++++++ src/formatter.h | 9 +++++++++ src/parse/binops.c | 2 +- src/parse/containers.c | 32 ++++++++++++++++---------------- src/parse/context.c | 8 ++++++++ src/parse/context.h | 5 +++++ src/parse/controlflow.c | 12 ++++++------ src/parse/expressions.c | 24 ++++++++++++------------ src/parse/files.c | 18 +++++++++--------- src/parse/functions.c | 26 +++++++++++++------------- src/parse/statements.c | 10 +++++----- src/parse/suffixes.c | 28 ++++++++++++++-------------- src/parse/typedefs.c | 34 +++++++++++++++++----------------- src/parse/types.c | 20 ++++++++++---------- src/parse/utils.c | 16 ++++++++++------ src/parse/utils.h | 6 +++--- 16 files changed, 161 insertions(+), 112 deletions(-) create mode 100644 src/formatter.c create mode 100644 src/formatter.h create mode 100644 src/parse/context.c (limited to 'src') diff --git a/src/formatter.c b/src/formatter.c new file mode 100644 index 00000000..81a5e126 --- /dev/null +++ b/src/formatter.c @@ -0,0 +1,23 @@ +// This code defines functions for transforming ASTs back into Tomo source text + +#include "ast.h" +#include "stdlib/datatypes.h" +#include "stdlib/optionals.h" +#include "stdlib/text.h" + +Text_t code_format(ast_t *ast, Table_t *comments) { + (void)comments; + switch (ast->tag) { + default: return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + } +} + +OptionalText_t code_format_inline(ast_t *ast, Table_t *comments) { + (void)comments; + for (const char *p = ast->start; p < ast->end; p++) { + if (*p == '\n' || *p == '\r') return NONE_TEXT; + } + switch (ast->tag) { + default: return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + } +} diff --git a/src/formatter.h b/src/formatter.h new file mode 100644 index 00000000..c3941de9 --- /dev/null +++ b/src/formatter.h @@ -0,0 +1,9 @@ +// This code defines functions for transforming ASTs back into Tomo source text + +#pragma once + +#include "ast.h" +#include "stdlib/datatypes.h" + +Text_t code_format(ast_t *ast, Table_t *comments); +OptionalText_t code_format_inline(ast_t *ast, Table_t *comments); diff --git a/src/parse/binops.c b/src/parse/binops.c index 7ccf1379..a6ac43d2 100644 --- a/src/parse/binops.c +++ b/src/parse/binops.c @@ -108,7 +108,7 @@ ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightness) { else if (key) pos = key->end; } - whitespace(&pos); + whitespace(ctx, &pos); if (get_line_number(ctx->file, pos) != starting_line && get_indent(ctx, pos) < starting_indent) parser_err(ctx, pos, eol(pos), "I expected this line to be at least as indented than the line above it"); diff --git a/src/parse/containers.c b/src/parse/containers.c index 821cbdd4..73d30ecd 100644 --- a/src/parse/containers.c +++ b/src/parse/containers.c @@ -16,7 +16,7 @@ ast_t *parse_list(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match(&pos, "[")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); ast_list_t *items = NULL; for (;;) { @@ -29,9 +29,9 @@ ast_t *parse_list(parse_ctx_t *ctx, const char *pos) { suffixed = parse_comprehension_suffix(ctx, item); } items = new (ast_list_t, .ast = item, .next = items); - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this list"); REVERSE_LIST(items); @@ -42,14 +42,14 @@ ast_t *parse_table(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match(&pos, "{")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); ast_list_t *entries = NULL; for (;;) { const char *entry_start = pos; ast_t *key = optional(ctx, &pos, parse_extended_expr); if (!key) break; - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, "=")) return NULL; ast_t *value = expect(ctx, pos - 1, &pos, parse_expr, "I couldn't parse the value for this table entry"); ast_t *entry = NewAST(ctx->file, entry_start, pos, TableEntry, .key = key, .value = value); @@ -60,37 +60,37 @@ ast_t *parse_table(parse_ctx_t *ctx, const char *pos) { suffixed = parse_comprehension_suffix(ctx, entry); } entries = new (ast_list_t, .ast = entry, .next = entries); - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } REVERSE_LIST(entries); - whitespace(&pos); + whitespace(ctx, &pos); ast_t *fallback = NULL, *default_value = NULL; if (match(&pos, ";")) { for (;;) { - whitespace(&pos); + whitespace(ctx, &pos); const char *attr_start = pos; if (match_word(&pos, "fallback")) { - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, "=")) parser_err(ctx, attr_start, pos, "I expected an '=' after 'fallback'"); if (fallback) parser_err(ctx, attr_start, pos, "This table already has a fallback"); fallback = expect(ctx, attr_start, &pos, parse_expr, "I expected a fallback table"); } else if (match_word(&pos, "default")) { - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, "=")) parser_err(ctx, attr_start, pos, "I expected an '=' after 'default'"); if (default_value) parser_err(ctx, attr_start, pos, "This table already has a default"); default_value = expect(ctx, attr_start, &pos, parse_expr, "I expected a default value"); } else { break; } - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, ",")) break; } } - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this table"); return NewAST(ctx->file, start, pos, Table, .default_value = default_value, .entries = entries, @@ -102,13 +102,13 @@ ast_t *parse_set(parse_ctx_t *ctx, const char *pos) { if (match(&pos, "||")) return NewAST(ctx->file, start, pos, Set); if (!match(&pos, "|")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); ast_list_t *items = NULL; for (;;) { ast_t *item = optional(ctx, &pos, parse_extended_expr); if (!item) break; - whitespace(&pos); + whitespace(ctx, &pos); ast_t *suffixed = parse_comprehension_suffix(ctx, item); while (suffixed) { item = suffixed; @@ -116,12 +116,12 @@ ast_t *parse_set(parse_ctx_t *ctx, const char *pos) { suffixed = parse_comprehension_suffix(ctx, item); } items = new (ast_list_t, .ast = item, .next = items); - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } REVERSE_LIST(items); - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this set"); return NewAST(ctx->file, start, pos, Set, .items = items); diff --git a/src/parse/context.c b/src/parse/context.c new file mode 100644 index 00000000..689fa1a4 --- /dev/null +++ b/src/parse/context.c @@ -0,0 +1,8 @@ +// A context parameter that gets passed around during parsing. + +#include "../stdlib/memory.h" +#include "../stdlib/pointers.h" +#include "../stdlib/tables.h" +#include "../stdlib/types.h" + +TypeInfo_t *parse_comments_info = Table$info(Pointer$info("@", &Memory$info), Pointer$info("@", &Memory$info)); diff --git a/src/parse/context.h b/src/parse/context.h index 6008060e..4d519a83 100644 --- a/src/parse/context.h +++ b/src/parse/context.h @@ -4,10 +4,15 @@ #include #include +#include "../stdlib/datatypes.h" #include "../stdlib/files.h" +#include "../stdlib/types.h" + +const TypeInfo_t *parse_comments_info; typedef struct { file_t *file; jmp_buf *on_err; int64_t next_lambda_id; + Table_t comments; // Map of -> } parse_ctx_t; diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c index 6f6292af..f50c84d7 100644 --- a/src/parse/controlflow.c +++ b/src/parse/controlflow.c @@ -36,7 +36,7 @@ ast_t *parse_block(parse_ctx_t *ctx, const char *pos) { if (indent(ctx, &pos)) { indented:; int64_t block_indent = get_indent(ctx, pos); - whitespace(&pos); + whitespace(ctx, &pos); while (*pos) { ast_t *stmt = optional(ctx, &pos, parse_statement); if (!stmt) { @@ -55,7 +55,7 @@ ast_t *parse_block(parse_ctx_t *ctx, const char *pos) { break; } statements = new (ast_list_t, .ast = stmt, .next = statements); - whitespace(&pos); + whitespace(ctx, &pos); // Guard against having two valid statements on the same line, separated by spaces (but no newlines): if (!memchr(stmt->end, '\n', (size_t)(pos - stmt->end))) { @@ -174,7 +174,7 @@ ast_t *parse_if(parse_ctx_t *ctx, const char *pos) { ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'if' statement"); const char *tmp = pos; - whitespace(&tmp); + whitespace(ctx, &tmp); ast_t *else_body = NULL; const char *else_start = pos; if (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "else")) { @@ -198,7 +198,7 @@ ast_t *parse_when(parse_ctx_t *ctx, const char *pos) { when_clause_t *clauses = NULL; const char *tmp = pos; - whitespace(&tmp); + whitespace(ctx, &tmp); while (get_indent(ctx, tmp) == starting_indent && match_word(&tmp, "is")) { pos = tmp; spaces(&pos); @@ -217,7 +217,7 @@ ast_t *parse_when(parse_ctx_t *ctx, const char *pos) { } clauses = new_clauses; tmp = pos; - whitespace(&tmp); + whitespace(ctx, &tmp); } REVERSE_LIST(clauses); @@ -255,7 +255,7 @@ ast_t *parse_for(parse_ctx_t *ctx, const char *pos) { ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'for'"); const char *else_start = pos; - whitespace(&else_start); + whitespace(ctx, &else_start); ast_t *empty = NULL; if (match_word(&else_start, "else") && get_indent(ctx, else_start) == starting_indent) { pos = else_start; diff --git a/src/parse/expressions.c b/src/parse/expressions.c index d643d4e7..6104b2d2 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -10,10 +10,10 @@ #include "context.h" #include "controlflow.h" #include "errors.h" +#include "expressions.h" #include "files.h" #include "functions.h" #include "numbers.h" -#include "expressions.h" #include "suffixes.h" #include "text.h" #include "types.h" @@ -23,7 +23,7 @@ ast_t *parse_parens(parse_ctx_t *ctx, const char *pos) { const char *start = pos; spaces(&pos); if (!match(&pos, "(")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); ast_t *expr = optional(ctx, &pos, parse_extended_expr); if (!expr) return NULL; @@ -34,7 +34,7 @@ ast_t *parse_parens(parse_ctx_t *ctx, const char *pos) { comprehension = parse_comprehension_suffix(ctx, expr); } - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this expression"); // Update the span to include the parens: @@ -45,7 +45,7 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match(&pos, "(")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); ast_e op = match_binary_operator(&pos); if (op == Unknown) return NULL; @@ -61,7 +61,7 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) { if (key && key->tag == Var) key = NULL; else if (key) pos = key->end; - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, ":")) return NULL; ast_t *iter = optional(ctx, &pos, parse_extended_expr); @@ -73,7 +73,7 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) { suffixed = parse_comprehension_suffix(ctx, iter); } - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this reduction"); return NewAST(ctx->file, start, pos, Reduction, .iter = iter, .op = op, .key = key); @@ -164,14 +164,14 @@ ast_t *parse_deserialize(parse_ctx_t *ctx, const char *pos) { spaces(&pos); expect_str(ctx, start, &pos, "(", "I expected arguments for this `deserialize` call"); - whitespace(&pos); + whitespace(ctx, &pos); ast_t *value = expect(ctx, start, &pos, parse_extended_expr, "I expected an expression here"); - whitespace(&pos); + whitespace(ctx, &pos); expect_str(ctx, start, &pos, "->", "I expected a `-> Type` for this `deserialize` call so I know what it deserializes to"); - whitespace(&pos); + whitespace(ctx, &pos); type_ast_t *type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this deserialization"); - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, ")", "I expected a closing ')' for this `deserialize` call"); return NewAST(ctx->file, start, pos, Deserialize, .value = value, .type = type); } @@ -238,10 +238,10 @@ ast_t *parse_expr_str(const char *str) { }; const char *pos = file->text; - whitespace(&pos); + whitespace(&ctx, &pos); ast_t *ast = parse_extended_expr(&ctx, pos); pos = ast->end; - whitespace(&pos); + whitespace(&ctx, &pos); if (pos < file->text + file->len && *pos != '\0') parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the string"); return ast; diff --git a/src/parse/files.c b/src/parse/files.c index 8078d544..5ff41c68 100644 --- a/src/parse/files.c +++ b/src/parse/files.c @@ -11,9 +11,9 @@ #include "../stdlib/util.h" #include "context.h" #include "errors.h" +#include "expressions.h" #include "files.h" #include "functions.h" -#include "expressions.h" #include "statements.h" #include "text.h" #include "typedefs.h" @@ -33,11 +33,11 @@ static ast_t *parse_top_declaration(parse_ctx_t *ctx, const char *pos) { ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { const char *start = pos; - whitespace(&pos); + whitespace(ctx, &pos); ast_list_t *statements = NULL; for (;;) { const char *next = pos; - whitespace(&next); + whitespace(ctx, &next); if (get_indent(ctx, next) != 0) break; ast_t *stmt; if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def)) @@ -47,12 +47,12 @@ ast_t *parse_file_body(parse_ctx_t *ctx, const char *pos) { || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_top_declaration))) { statements = new (ast_list_t, .ast = stmt, .next = statements); pos = stmt->end; - whitespace(&pos); // TODO: check for newline + whitespace(ctx, &pos); // TODO: check for newline } else { break; } } - whitespace(&pos); + whitespace(ctx, &pos); if (pos < ctx->file->text + ctx->file->len && *pos != '\0') { parser_err(ctx, pos, eol(pos), "I expect all top-level statements to be declarations of some kind"); } @@ -90,10 +90,10 @@ ast_t *parse_file(const char *path, jmp_buf *on_err) { if (match(&pos, "#!")) // shebang some_not(&pos, "\r\n"); - whitespace(&pos); + whitespace(&ctx, &pos); ast = parse_file_body(&ctx, pos); pos = ast->end; - whitespace(&pos); + whitespace(&ctx, &pos); if (pos < file->text + file->len && *pos != '\0') { parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the file"); } @@ -171,10 +171,10 @@ ast_t *parse_file_str(const char *str) { }; const char *pos = file->text; - whitespace(&pos); + whitespace(&ctx, &pos); ast_t *ast = parse_file_body(&ctx, pos); pos = ast->end; - whitespace(&pos); + whitespace(&ctx, &pos); if (pos < file->text + file->len && *pos != '\0') parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the string"); return ast; diff --git a/src/parse/functions.c b/src/parse/functions.c index 0779bb7b..e820182b 100644 --- a/src/parse/functions.c +++ b/src/parse/functions.c @@ -13,8 +13,8 @@ #include "context.h" #include "controlflow.h" #include "errors.h" -#include "functions.h" #include "expressions.h" +#include "functions.h" #include "types.h" #include "utils.h" @@ -32,15 +32,15 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { name_list_t *names = NULL; for (;;) { - whitespace(pos); + whitespace(ctx, pos); const char *name = get_id(pos); if (!name) break; - whitespace(pos); + whitespace(ctx, pos); if (match(pos, ":")) { type = expect(ctx, *pos - 1, pos, parse_type, "I expected a type here"); names = new (name_list_t, .name = name, .next = names); - whitespace(pos); + whitespace(ctx, pos); if (match(pos, "=")) default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='"); break; @@ -66,7 +66,7 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { for (; names; names = names->next) args = new (arg_ast_t, .name = names->name, .type = type, .value = default_val, .next = args); - if (!match_separator(pos)) break; + if (!match_separator(ctx, pos)) break; } REVERSE_LIST(args); @@ -87,19 +87,19 @@ ast_t *parse_func_def(parse_ctx_t *ctx, const char *pos) { arg_ast_t *args = parse_args(ctx, &pos); spaces(&pos); type_ast_t *ret_type = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL; - whitespace(&pos); + whitespace(ctx, &pos); bool is_inline = false; ast_t *cache_ast = NULL; - for (bool specials = match(&pos, ";"); specials; specials = match_separator(&pos)) { + for (bool specials = match(&pos, ";"); specials; specials = match_separator(ctx, &pos)) { const char *flag_start = pos; if (match_word(&pos, "inline")) { is_inline = true; } else if (match_word(&pos, "cached")) { if (!cache_ast) cache_ast = NewAST(ctx->file, pos, pos, Int, .str = "-1"); } else if (match_word(&pos, "cache_size")) { - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, "=")) parser_err(ctx, flag_start, pos, "I expected a value for 'cache_size'"); - whitespace(&pos); + whitespace(ctx, &pos); cache_ast = expect(ctx, start, &pos, parse_expr, "I expected a maximum size for the cache"); } } @@ -121,19 +121,19 @@ ast_t *parse_convert_def(parse_ctx_t *ctx, const char *pos) { arg_ast_t *args = parse_args(ctx, &pos); spaces(&pos); type_ast_t *ret_type = match(&pos, "->") ? optional(ctx, &pos, parse_type) : NULL; - whitespace(&pos); + whitespace(ctx, &pos); bool is_inline = false; ast_t *cache_ast = NULL; - for (bool specials = match(&pos, ";"); specials; specials = match_separator(&pos)) { + for (bool specials = match(&pos, ";"); specials; specials = match_separator(ctx, &pos)) { const char *flag_start = pos; if (match_word(&pos, "inline")) { is_inline = true; } else if (match_word(&pos, "cached")) { if (!cache_ast) cache_ast = NewAST(ctx->file, pos, pos, Int, .str = "-1"); } else if (match_word(&pos, "cache_size")) { - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, "=")) parser_err(ctx, flag_start, pos, "I expected a value for 'cache_size'"); - whitespace(&pos); + whitespace(ctx, &pos); cache_ast = expect(ctx, start, &pos, parse_expr, "I expected a maximum size for the cache"); } } diff --git a/src/parse/statements.c b/src/parse/statements.c index a30231f0..9606acdc 100644 --- a/src/parse/statements.c +++ b/src/parse/statements.c @@ -8,8 +8,8 @@ #include "../stdlib/util.h" #include "context.h" #include "errors.h" -#include "files.h" #include "expressions.h" +#include "files.h" #include "statements.h" #include "suffixes.h" #include "types.h" @@ -46,7 +46,7 @@ ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos) { targets = new (ast_list_t, .ast = lhs, .next = targets); spaces(&pos); if (!match(&pos, ",")) break; - whitespace(&pos); + whitespace(ctx, &pos); } if (!targets) return NULL; @@ -62,7 +62,7 @@ ast_t *parse_assignment(parse_ctx_t *ctx, const char *pos) { values = new (ast_list_t, .ast = rhs, .next = values); spaces(&pos); if (!match(&pos, ",")) break; - whitespace(&pos); + whitespace(ctx, &pos); } REVERSE_LIST(targets); @@ -101,7 +101,7 @@ ast_t *parse_doctest(parse_ctx_t *ctx, const char *pos) { if (!match(&pos, ">>")) return NULL; spaces(&pos); ast_t *expr = expect(ctx, start, &pos, parse_statement, "I couldn't parse the expression for this doctest"); - whitespace(&pos); + whitespace(ctx, &pos); ast_t *expected = NULL; if (match(&pos, "=")) { spaces(&pos); @@ -120,7 +120,7 @@ ast_t *parse_assert(parse_ctx_t *ctx, const char *pos) { spaces(&pos); ast_t *message = NULL; if (match(&pos, ",")) { - whitespace(&pos); + whitespace(ctx, &pos); message = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the error message for this assert"); } else { pos = expr->end; diff --git a/src/parse/suffixes.c b/src/parse/suffixes.c index 7e748caf..58d951c4 100644 --- a/src/parse/suffixes.c +++ b/src/parse/suffixes.c @@ -14,10 +14,10 @@ ast_t *parse_field_suffix(parse_ctx_t *ctx, ast_t *lhs) { if (!lhs) return NULL; const char *pos = lhs->end; - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, ".")) return NULL; if (*pos == '.') return NULL; - whitespace(&pos); + whitespace(ctx, &pos); bool dollar = match(&pos, "$"); const char *field = get_id(&pos); if (!field) return NULL; @@ -44,9 +44,9 @@ ast_t *parse_index_suffix(parse_ctx_t *ctx, ast_t *lhs) { const char *start = lhs->start; const char *pos = lhs->end; if (!match(&pos, "[")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); ast_t *index = optional(ctx, &pos, parse_extended_expr); - whitespace(&pos); + whitespace(ctx, &pos); bool unchecked = match(&pos, ";") && (spaces(&pos), match_word(&pos, "unchecked") != 0); expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this index"); return NewAST(ctx->file, start, pos, Index, .indexed = lhs, .index = index, .unchecked = unchecked); @@ -57,7 +57,7 @@ ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *expr) { if (!expr) return NULL; const char *start = expr->start; const char *pos = expr->end; - whitespace(&pos); + whitespace(ctx, &pos); if (!match_word(&pos, "for")) return NULL; ast_list_t *vars = NULL; @@ -73,7 +73,7 @@ ast_t *parse_comprehension_suffix(parse_ctx_t *ctx, ast_t *expr) { expect_str(ctx, start, &pos, "in", "I expected an 'in' for this 'for'"); ast_t *iter = expect(ctx, start, &pos, parse_expr, "I expected an iterable value for this 'for'"); const char *next_pos = pos; - whitespace(&next_pos); + whitespace(ctx, &next_pos); ast_t *filter = NULL; if (match_word(&next_pos, "if")) { pos = next_pos; @@ -115,13 +115,13 @@ ast_t *parse_method_call_suffix(parse_ctx_t *ctx, ast_t *self) { if (!fn) return NULL; spaces(&pos); if (!match(&pos, "(")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); arg_ast_t *args = NULL; for (;;) { const char *arg_start = pos; const char *name = get_id(&pos); - whitespace(&pos); + whitespace(ctx, &pos); if (!name || !match(&pos, "=")) { name = NULL; pos = arg_start; @@ -133,11 +133,11 @@ ast_t *parse_method_call_suffix(parse_ctx_t *ctx, ast_t *self) { break; } args = new (arg_ast_t, .name = name, .value = arg, .next = args); - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } REVERSE_LIST(args); - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, ")")) parser_err(ctx, start, pos, "This parenthesis is unclosed"); @@ -152,13 +152,13 @@ ast_t *parse_fncall_suffix(parse_ctx_t *ctx, ast_t *fn) { if (!match(&pos, "(")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); arg_ast_t *args = NULL; for (;;) { const char *arg_start = pos; const char *name = get_id(&pos); - whitespace(&pos); + whitespace(ctx, &pos); if (!name || !match(&pos, "=")) { name = NULL; pos = arg_start; @@ -170,10 +170,10 @@ ast_t *parse_fncall_suffix(parse_ctx_t *ctx, ast_t *fn) { break; } args = new (arg_ast_t, .name = name, .value = arg, .next = args); - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } - whitespace(&pos); + whitespace(ctx, &pos); if (!match(&pos, ")")) parser_err(ctx, start, pos, "This parenthesis is unclosed"); diff --git a/src/parse/typedefs.c b/src/parse/typedefs.c index 73fe9d7c..d5ade767 100644 --- a/src/parse/typedefs.c +++ b/src/parse/typedefs.c @@ -16,12 +16,12 @@ ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos) { const char *start = pos; - whitespace(&pos); + whitespace(ctx, &pos); int64_t indent = get_indent(ctx, pos); ast_list_t *statements = NULL; for (;;) { const char *next = pos; - whitespace(&next); + whitespace(ctx, &next); if (get_indent(ctx, next) != indent) break; ast_t *stmt; if ((stmt = optional(ctx, &pos, parse_struct_def)) || (stmt = optional(ctx, &pos, parse_func_def)) @@ -31,7 +31,7 @@ ast_t *parse_namespace(parse_ctx_t *ctx, const char *pos) { || (stmt = optional(ctx, &pos, parse_inline_c)) || (stmt = optional(ctx, &pos, parse_declaration))) { statements = new (ast_list_t, .ast = stmt, .next = statements); pos = stmt->end; - whitespace(&pos); // TODO: check for newline + whitespace(ctx, &pos); // TODO: check for newline // if (!(space_types & WHITESPACE_NEWLINES)) { // pos = stmt->end; // break; @@ -62,10 +62,10 @@ ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) { arg_ast_t *fields = parse_args(ctx, &pos); - whitespace(&pos); + whitespace(ctx, &pos); bool secret = false, external = false, opaque = false; if (match(&pos, ";")) { // Extra flags - whitespace(&pos); + whitespace(ctx, &pos); for (;;) { if (match_word(&pos, "secret")) { secret = true; @@ -79,7 +79,7 @@ ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) { break; } - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } } @@ -87,7 +87,7 @@ ast_t *parse_struct_def(parse_ctx_t *ctx, const char *pos) { ast_t *namespace = NULL; const char *ns_pos = pos; - whitespace(&ns_pos); + whitespace(ctx, &ns_pos); int64_t ns_indent = get_indent(ctx, ns_pos); if (ns_indent > starting_indent) { pos = ns_pos; @@ -110,7 +110,7 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { if (!match(&pos, "(")) return NULL; tag_ast_t *tags = NULL; - whitespace(&pos); + whitespace(ctx, &pos); for (;;) { spaces(&pos); const char *tag_name = get_id(&pos); @@ -120,13 +120,13 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { arg_ast_t *fields; bool secret = false; if (match(&pos, "(")) { - whitespace(&pos); + whitespace(ctx, &pos); fields = parse_args(ctx, &pos); - whitespace(&pos); + whitespace(ctx, &pos); if (match(&pos, ";")) { // Extra flags - whitespace(&pos); + whitespace(ctx, &pos); secret = match_word(&pos, "secret"); - whitespace(&pos); + whitespace(ctx, &pos); } expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this tagged union member"); } else { @@ -135,10 +135,10 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { tags = new (tag_ast_t, .name = tag_name, .fields = fields, .secret = secret, .next = tags); - if (!match_separator(&pos)) break; + if (!match_separator(ctx, &pos)) break; } - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this enum definition"); REVERSE_LIST(tags); @@ -147,7 +147,7 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { ast_t *namespace = NULL; const char *ns_pos = pos; - whitespace(&ns_pos); + whitespace(ctx, &ns_pos); int64_t ns_indent = get_indent(ctx, ns_pos); if (ns_indent > starting_indent) { pos = ns_pos; @@ -170,7 +170,7 @@ ast_t *parse_lang_def(parse_ctx_t *ctx, const char *pos) { ast_t *namespace = NULL; const char *ns_pos = pos; - whitespace(&ns_pos); + whitespace(ctx, &ns_pos); int64_t ns_indent = get_indent(ctx, ns_pos); if (ns_indent > starting_indent) { pos = ns_pos; @@ -192,7 +192,7 @@ ast_t *parse_extend(parse_ctx_t *ctx, const char *pos) { ast_t *body = NULL; const char *ns_pos = pos; - whitespace(&ns_pos); + whitespace(ctx, &ns_pos); int64_t ns_indent = get_indent(ctx, ns_pos); if (ns_indent > starting_indent) { pos = ns_pos; diff --git a/src/parse/types.c b/src/parse/types.c index 54bc0c03..ffb7d869 100644 --- a/src/parse/types.c +++ b/src/parse/types.c @@ -9,19 +9,19 @@ #include "../stdlib/print.h" #include "context.h" #include "errors.h" -#include "functions.h" #include "expressions.h" +#include "functions.h" #include "types.h" #include "utils.h" type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match(&pos, "{")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); type_ast_t *key_type = parse_type(ctx, pos); if (!key_type) return NULL; pos = key_type->end; - whitespace(&pos); + whitespace(ctx, &pos); type_ast_t *value_type = NULL; if (match(&pos, "=")) { value_type = expect(ctx, start, &pos, parse_type, "I couldn't parse the rest of this table type"); @@ -35,7 +35,7 @@ type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) { default_value = expect(ctx, start, &pos, parse_extended_expr, "I couldn't parse the default value for this table"); } - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, "}", "I wasn't able to parse the rest of this table type"); return NewTypeAST(ctx->file, start, pos, TableTypeAST, .key = key_type, .value = value_type, .default_value = default_value); @@ -44,11 +44,11 @@ type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) { type_ast_t *parse_set_type(parse_ctx_t *ctx, const char *pos) { const char *start = pos; if (!match(&pos, "|")) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); type_ast_t *item_type = parse_type(ctx, pos); if (!item_type) return NULL; pos = item_type->end; - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, "|", "I wasn't able to parse the rest of this set type"); return NewTypeAST(ctx->file, start, pos, SetTypeAST, .item = item_type); } @@ -113,10 +113,10 @@ type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) { || (type = parse_table_type(ctx, pos)) || (type = parse_set_type(ctx, pos)) || (type = parse_type_name(ctx, pos)) || (type = parse_func_type(ctx, pos))); if (!success && match(&pos, "(")) { - whitespace(&pos); + whitespace(ctx, &pos); type = optional(ctx, &pos, parse_type); if (!type) return NULL; - whitespace(&pos); + whitespace(ctx, &pos); expect_closing(ctx, &pos, ")", "I wasn't able to parse the rest of this type"); type->start = start; type->end = pos; @@ -144,11 +144,11 @@ type_ast_t *parse_type_str(const char *str) { }; const char *pos = file->text; - whitespace(&pos); + whitespace(&ctx, &pos); type_ast_t *ast = parse_type(&ctx, pos); if (!ast) return ast; pos = ast->end; - whitespace(&pos); + whitespace(&ctx, &pos); if (strlen(pos) > 0) { parser_err(&ctx, pos, pos + strlen(pos), "I couldn't parse this part of the type"); } diff --git a/src/parse/utils.c b/src/parse/utils.c index 7e827ac6..0644bfa0 100644 --- a/src/parse/utils.c +++ b/src/parse/utils.c @@ -6,6 +6,7 @@ #include #include +#include "../stdlib/tables.h" #include "../stdlib/util.h" #include "errors.h" #include "utils.h" @@ -43,8 +44,8 @@ size_t some_not(const char **pos, const char *forbid) { size_t spaces(const char **pos) { return some_of(pos, " \t"); } -void whitespace(const char **pos) { - while (some_of(pos, " \t\r\n") || comment(pos)) +void whitespace(parse_ctx_t *ctx, const char **pos) { + while (some_of(pos, " \t\r\n") || comment(ctx, pos)) continue; } @@ -95,9 +96,12 @@ const char *get_id(const char **inout) { PUREFUNC const char *eol(const char *str) { return str + strcspn(str, "\r\n"); } -bool comment(const char **pos) { +bool comment(parse_ctx_t *ctx, const char **pos) { if ((*pos)[0] == '#') { + const char *start = *pos; *pos += strcspn(*pos, "\r\n"); + const char *end = *pos; + Table$set(&ctx->comments, &start, &end, parse_comments_info); return true; } else { return false; @@ -129,7 +133,7 @@ PUREFUNC int64_t get_indent(parse_ctx_t *ctx, const char *pos) { bool indent(parse_ctx_t *ctx, const char **out) { const char *pos = *out; int64_t starting_indent = get_indent(ctx, pos); - whitespace(&pos); + whitespace(ctx, &pos); const char *next_line = get_line(ctx->file, get_line_number(ctx->file, pos)); if (next_line <= *out) return false; @@ -239,12 +243,12 @@ const char *unescape(parse_ctx_t *ctx, const char **out) { #pragma GCC diagnostic pop #endif -bool match_separator(const char **pos) { // Either comma or newline +bool match_separator(parse_ctx_t *ctx, const char **pos) { // Either comma or newline const char *p = *pos; int separators = 0; for (;;) { if (some_of(&p, "\r\n,")) ++separators; - else if (!comment(&p) && !some_of(&p, " \t")) break; + else if (!comment(ctx, &p) && !some_of(&p, " \t")) break; } if (separators > 0) { *pos = p; diff --git a/src/parse/utils.h b/src/parse/utils.h index ba54120a..b8fb0756 100644 --- a/src/parse/utils.h +++ b/src/parse/utils.h @@ -12,16 +12,16 @@ CONSTFUNC bool is_keyword(const char *word); size_t some_of(const char **pos, const char *allow); size_t some_not(const char **pos, const char *forbid); size_t spaces(const char **pos); -void whitespace(const char **pos); +void whitespace(parse_ctx_t *ctx, const char **pos); size_t match(const char **pos, const char *target); size_t match_word(const char **pos, const char *word); const char *get_word(const char **pos); const char *get_id(const char **pos); -bool comment(const char **pos); +bool comment(parse_ctx_t *ctx, const char **pos); bool indent(parse_ctx_t *ctx, const char **pos); const char *eol(const char *str); PUREFUNC int64_t get_indent(parse_ctx_t *ctx, const char *pos); const char *unescape(parse_ctx_t *ctx, const char **out); bool is_xid_continue_next(const char *pos); bool newline_with_indentation(const char **out, int64_t target); -bool match_separator(const char **pos); +bool match_separator(parse_ctx_t *ctx, const char **pos); -- cgit v1.2.3 From 3e2c911ce260907e3f8f4a4278a7c0ec4793e78a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 12:43:34 -0400 Subject: First pass at formatter --- src/formatter.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/formatter.h | 6 +++-- src/parse/context.c | 2 +- src/parse/context.h | 2 +- src/tomo.c | 62 ++++++++++++++++++++++++++++++--------------- 5 files changed, 116 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/formatter.c b/src/formatter.c index 81a5e126..83d1b7a0 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -1,23 +1,87 @@ // This code defines functions for transforming ASTs back into Tomo source text +#include +#include +#include + #include "ast.h" +#include "formatter.h" +#include "parse/context.h" +#include "parse/files.h" +#include "parse/utils.h" #include "stdlib/datatypes.h" #include "stdlib/optionals.h" +#include "stdlib/tables.h" #include "stdlib/text.h" -Text_t code_format(ast_t *ast, Table_t *comments) { +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) { + for (const char *p = *pos; p < end; p++) { + const char **comment_end = Table$get(comments, &p, parse_comments_info); + if (comment_end) { + *pos = *comment_end; + return Text$from_strn(p, (int64_t)(*comment_end - p)); + } + } + return NONE_TEXT; +} + +Text_t format_code(ast_t *ast, Table_t comments) { (void)comments; switch (ast->tag) { - default: return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + default: { + Text_t code = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + return Text$replace(code, Text("\t"), Text(" ")); + } } } -OptionalText_t code_format_inline(ast_t *ast, Table_t *comments) { - (void)comments; +OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { for (const char *p = ast->start; p < ast->end; p++) { if (*p == '\n' || *p == '\r') return NONE_TEXT; } + const char *pos = ast->start; + OptionalText_t comment = next_comment(comments, &pos, ast->end); + if (comment.length >= 0) return NONE_TEXT; switch (ast->tag) { default: return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); } } + +Text_t format_file(const char *path) { + file_t *file = load_file(path); + if (!file) return EMPTY_TEXT; + + jmp_buf on_err; + if (setjmp(on_err) != 0) { + return Text$from_str(file->text); + } + parse_ctx_t ctx = { + .file = file, + .on_err = &on_err, + .comments = {}, + }; + + const char *pos = file->text; + if (match(&pos, "#!")) // shebang + some_not(&pos, "\r\n"); + + whitespace(&ctx, &pos); + ast_t *ast = parse_file_body(&ctx, pos); + if (!ast) return Text$from_str(file->text); + pos = ast->end; + whitespace(&ctx, &pos); + if (pos < file->text + file->len && *pos != '\0') { + return Text$from_str(file->text); + } + + const char *fmt_pos = file->text; + Text_t code = EMPTY_TEXT; + for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { + code = Texts(code, comment, "\n"); + } + code = Texts(code, format_code(ast, ctx.comments)); + for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { + code = Texts(code, comment, "\n"); + } + return code; +} diff --git a/src/formatter.h b/src/formatter.h index c3941de9..dd4c7815 100644 --- a/src/formatter.h +++ b/src/formatter.h @@ -5,5 +5,7 @@ #include "ast.h" #include "stdlib/datatypes.h" -Text_t code_format(ast_t *ast, Table_t *comments); -OptionalText_t code_format_inline(ast_t *ast, Table_t *comments); +Text_t format_file(const char *path); +Text_t format_code(ast_t *ast, Table_t comments); +OptionalText_t format_inline_code(ast_t *ast, Table_t comments); +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); diff --git a/src/parse/context.c b/src/parse/context.c index 689fa1a4..cd8d16bc 100644 --- a/src/parse/context.c +++ b/src/parse/context.c @@ -5,4 +5,4 @@ #include "../stdlib/tables.h" #include "../stdlib/types.h" -TypeInfo_t *parse_comments_info = Table$info(Pointer$info("@", &Memory$info), Pointer$info("@", &Memory$info)); +const TypeInfo_t *parse_comments_info = Table$info(Pointer$info("@", &Memory$info), Pointer$info("@", &Memory$info)); diff --git a/src/parse/context.h b/src/parse/context.h index 4d519a83..f1e3be2f 100644 --- a/src/parse/context.h +++ b/src/parse/context.h @@ -8,7 +8,7 @@ #include "../stdlib/files.h" #include "../stdlib/types.h" -const TypeInfo_t *parse_comments_info; +extern const TypeInfo_t *parse_comments_info; typedef struct { file_t *file; diff --git a/src/tomo.c b/src/tomo.c index d02cce74..36a47b7f 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -19,6 +19,7 @@ #include "compile/files.h" #include "compile/headers.h" #include "config.h" +#include "formatter.h" #include "modules.h" #include "naming.h" #include "parse/files.h" @@ -74,9 +75,9 @@ static const char *paths_str(List_t paths) { static OptionalList_t files = NONE_LIST, args = NONE_LIST, uninstall = NONE_LIST, libraries = NONE_LIST; static OptionalBool_t verbose = false, quiet = false, show_version = false, show_parse_tree = false, - show_prefix = false, stop_at_transpile = false, stop_at_obj_compilation = false, - compile_exe = false, should_install = false, clean_build = false, source_mapping = true, - show_changelog = false; + do_format_code = false, show_prefix = false, stop_at_transpile = false, + stop_at_obj_compilation = false, compile_exe = false, should_install = false, clean_build = false, + source_mapping = true, show_changelog = false; static OptionalText_t show_codegen = NONE_TEXT, cflags = Text("-Werror -fdollars-in-identifiers -std=c2x -Wno-trigraphs " @@ -185,23 +186,38 @@ int main(int argc, char *argv[]) { " --optimization|-O : set optimization level\n" " --run|-r: run a program from " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed\n"); Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage); - tomo_parse_args( - argc, argv, usage, help, TOMO_VERSION, {"files", true, List$info(&Path$info), &files}, - {"args", true, List$info(&Text$info), &args}, {"verbose", false, &Bool$info, &verbose}, - {"v", false, &Bool$info, &verbose}, {"version", false, &Bool$info, &show_version}, - {"parse", false, &Bool$info, &show_parse_tree}, {"p", false, &Bool$info, &show_parse_tree}, - {"prefix", false, &Bool$info, &show_prefix}, {"quiet", false, &Bool$info, &quiet}, - {"q", false, &Bool$info, &quiet}, {"transpile", false, &Bool$info, &stop_at_transpile}, - {"t", false, &Bool$info, &stop_at_transpile}, {"compile-obj", false, &Bool$info, &stop_at_obj_compilation}, - {"c", false, &Bool$info, &stop_at_obj_compilation}, {"compile-exe", false, &Bool$info, &compile_exe}, - {"e", false, &Bool$info, &compile_exe}, {"uninstall", false, List$info(&Text$info), &uninstall}, - {"u", false, List$info(&Text$info), &uninstall}, {"library", false, List$info(&Path$info), &libraries}, - {"L", false, List$info(&Path$info), &libraries}, {"show-codegen", false, &Text$info, &show_codegen}, - {"C", false, &Text$info, &show_codegen}, {"install", false, &Bool$info, &should_install}, - {"I", false, &Bool$info, &should_install}, {"optimization", false, &Text$info, &optimization}, - {"O", false, &Text$info, &optimization}, {"force-rebuild", false, &Bool$info, &clean_build}, - {"f", false, &Bool$info, &clean_build}, {"source-mapping", false, &Bool$info, &source_mapping}, - {"m", false, &Bool$info, &source_mapping}, {"changelog", false, &Bool$info, &show_changelog}, ); + tomo_parse_args(argc, argv, usage, help, TOMO_VERSION, // + {"files", true, List$info(&Path$info), &files}, // + {"args", true, List$info(&Text$info), &args}, // + {"verbose", false, &Bool$info, &verbose}, // + {"v", false, &Bool$info, &verbose}, // + {"version", false, &Bool$info, &show_version}, // + {"parse", false, &Bool$info, &show_parse_tree}, // + {"p", false, &Bool$info, &show_parse_tree}, // + {"format", false, &Bool$info, &do_format_code}, // + {"prefix", false, &Bool$info, &show_prefix}, // + {"quiet", false, &Bool$info, &quiet}, // + {"q", false, &Bool$info, &quiet}, // + {"transpile", false, &Bool$info, &stop_at_transpile}, // + {"t", false, &Bool$info, &stop_at_transpile}, // + {"compile-obj", false, &Bool$info, &stop_at_obj_compilation}, // + {"c", false, &Bool$info, &stop_at_obj_compilation}, // + {"compile-exe", false, &Bool$info, &compile_exe}, // + {"e", false, &Bool$info, &compile_exe}, // + {"uninstall", false, List$info(&Text$info), &uninstall}, // + {"u", false, List$info(&Text$info), &uninstall}, // + {"library", false, List$info(&Path$info), &libraries}, // + {"L", false, List$info(&Path$info), &libraries}, // + {"show-codegen", false, &Text$info, &show_codegen}, // + {"C", false, &Text$info, &show_codegen}, // + {"install", false, &Bool$info, &should_install}, // + {"I", false, &Bool$info, &should_install}, // + {"optimization", false, &Text$info, &optimization}, // + {"O", false, &Text$info, &optimization}, // + {"force-rebuild", false, &Bool$info, &clean_build}, {"f", false, &Bool$info, &clean_build}, // + {"source-mapping", false, &Bool$info, &source_mapping}, + {"m", false, &Bool$info, &source_mapping}, // + {"changelog", false, &Bool$info, &show_changelog}, ); if (show_prefix) { print(TOMO_PREFIX); @@ -299,6 +315,12 @@ int main(int argc, char *argv[]) { continue; } + if (do_format_code) { + Text_t formatted = format_file(Path$as_c_string(path)); + print(formatted); + continue; + } + Path_t exe_path = compile_exe ? Path$with_extension(path, Text(""), true) : build_file(Path$with_extension(path, Text(""), true), ""); -- cgit v1.2.3 From 1f78aa8783eba223427509834d64f8f5214f1a98 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 14:00:30 -0400 Subject: Improved formatting --- src/ast.h | 2 + src/formatter.c | 231 +++++++++++++++++++++++++++++++++++++++++++++++--- src/formatter.h | 2 +- src/parse/functions.c | 11 ++- src/parse/suffixes.c | 2 +- 5 files changed, 228 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/ast.h b/src/ast.h index 3fdff98c..43972371 100644 --- a/src/ast.h +++ b/src/ast.h @@ -65,6 +65,8 @@ typedef struct ast_list_s { } ast_list_t; typedef struct arg_ast_s { + file_t *file; + const char *start, *end; const char *name; type_ast_t *type; ast_t *value; diff --git a/src/formatter.c b/src/formatter.c index 83d1b7a0..2b367e3d 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -2,7 +2,6 @@ #include #include -#include #include "ast.h" #include "formatter.h" @@ -14,6 +13,26 @@ #include "stdlib/tables.h" #include "stdlib/text.h" +#define MAX_WIDTH 100 + +#define must(expr) \ + ({ \ + OptionalText_t _expr = expr; \ + if (_expr.length < 0) return NONE_TEXT; \ + (Text_t) _expr; \ + }) + +const Text_t single_indent = Text(" "); + +static void add_line(Text_t *code, Text_t line, Text_t indent) { + if (code->length == 0) { + *code = line; + } else { + if (line.length > 0) *code = Texts(*code, "\n", indent, line); + else *code = Texts(*code, "\n"); + } +} + OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) { for (const char *p = *pos; p < end; p++) { const char **comment_end = Table$get(comments, &p, parse_comments_info); @@ -25,25 +44,209 @@ OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) return NONE_TEXT; } -Text_t format_code(ast_t *ast, Table_t comments) { - (void)comments; +static bool range_has_comment(const char *start, const char *end, Table_t comments) { + OptionalText_t comment = next_comment(comments, &start, end); + return (comment.length >= 0); +} + +static bool should_have_blank_line(ast_t *ast) { switch (ast->tag) { + case If: + case When: + case Repeat: + case While: + case For: + case Block: + case FunctionDef: + case StructDef: + case EnumDef: + case LangDef: + case ConvertDef: + case Extend: return true; + default: return false; + } +} + +static OptionalText_t format_inline_type(type_ast_t *type, Table_t comments) { + if (range_has_comment(type->start, type->end, comments)) return NONE_TEXT; + switch (type->tag) { default: { - Text_t code = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); - return Text$replace(code, Text("\t"), Text(" ")); + Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); + if (Text$has(code, Text("\n"))) return NONE_TEXT; + return Text$replace(code, Text("\t"), single_indent); + } + } +} + +static Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent) { + (void)comments, (void)indent; + switch (type->tag) { + default: { + OptionalText_t inline_type = format_inline_type(type, comments); + if (inline_type.length >= 0) return inline_type; + Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); + return Text$replace(code, Text("\t"), single_indent); + } + } +} + +static OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { + if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT; + Text_t code = Text$from_str(arg->name); + if (arg->type) code = Texts(code, ":", must(format_inline_type(arg->type, comments))); + if (arg->value) code = Texts(code, " = ", must(format_inline_code(arg->value, comments))); + return code; +} + +static Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { + (void)comments; + Text_t code = Text$from_str(arg->name); + if (arg->type) code = Texts(code, ":", format_type(arg->type, comments, indent)); + if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); + return code; +} + +static OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) { + Text_t code = EMPTY_TEXT; + for (; args; args = args->next) { + if (args->next && args->type == args->next->type && args->value == args->next->value) { + code = Texts(code, must(Text$from_str(args->name)), ","); + } else { + code = Texts(code, must(format_inline_arg(args, comments))); + if (args->next) code = Texts(code, ", "); + } + if (args->next && range_has_comment(args->end, args->next->start, comments)) return NONE_TEXT; + } + return code; +} + +static Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { + OptionalText_t inline_args = format_inline_args(args, comments); + if (inline_args.length >= 0) return inline_args; + Text_t code = EMPTY_TEXT; + for (; args; args = args->next) { + if (args->next && args->type == args->next->type && args->value == args->next->value) { + code = Texts(code, must(Text$from_str(args->name)), ","); + } else { + add_line(&code, Texts(format_arg(args, comments, indent), ","), indent); + } } + return code; +} + +ast_t *unwrap_block(ast_t *ast) { + while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { + ast = Match(ast, Block)->statements->ast; } + return ast; } OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { - for (const char *p = ast->start; p < ast->end; p++) { - if (*p == '\n' || *p == '\r') return NONE_TEXT; + if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; + switch (ast->tag) { + case Block: { + ast_list_t *statements = Match(ast, Block)->statements; + if (statements == NULL) return Text("pass"); + else if (statements->next == NULL) return format_inline_code(statements->ast, comments); + else return NONE_TEXT; + } + case FunctionDef: return NONE_TEXT; + case If: { + DeclareMatch(if_, ast, If); + + if (if_->else_body == NULL && if_->condition->tag != Declare) { + ast_t *stmt = unwrap_block(if_->body); + switch (stmt->tag) { + case Return: + case Skip: + case Stop: + return Texts(must(format_inline_code(stmt, comments)), " if ", + must(format_inline_code(if_->condition, comments))); + default: break; + } + } + + Text_t code = Texts("if ", must(format_inline_code(if_->condition, comments)), " then ", + must(format_inline_code(if_->body, comments))); + if (if_->else_body) code = Texts(code, " else ", must(format_inline_code(if_->else_body, comments))); + return code; + } + default: { + 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; } - const char *pos = ast->start; - OptionalText_t comment = next_comment(comments, &pos, ast->end); - if (comment.length >= 0) return NONE_TEXT; + } +} + +Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { + OptionalText_t inlined = format_inline_code(ast, comments); + bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH); + switch (ast->tag) { - default: return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + case Block: { + Text_t code = EMPTY_TEXT; + bool gap_before_comment = false; + const char *comment_pos = ast->start; + for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { + for (OptionalText_t comment; + (comment = next_comment(comments, &comment_pos, stmt->ast->start)).length > 0;) { + if (gap_before_comment) { + add_line(&code, Text(""), indent); + gap_before_comment = false; + } + add_line(&code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), 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); + else gap_before_comment = true; + } + + for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) { + if (gap_before_comment) { + add_line(&code, Text(""), indent); + gap_before_comment = false; + } + add_line(&code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), indent); + } + return code; + } + case If: { + DeclareMatch(if_, ast, If); + if (inlined_fits && if_->else_body == NULL) return inlined; + + Text_t code = Texts("if ", format_code(if_->condition, comments, indent), "\n", indent, single_indent, + format_code(if_->body, comments, Texts(indent, single_indent))); + if (if_->else_body) + code = Texts(code, "\n", indent, "else \n", indent, single_indent, + format_code(if_->else_body, comments, Texts(indent, single_indent))); + return code; + } + case FunctionDef: { + DeclareMatch(func, ast, FunctionDef); + // ast_t *name; + // arg_ast_t *args; + // type_ast_t *ret_type; + // ast_t *body; + // ast_t *cache; + // bool is_inline; + Text_t code = + Texts("func ", format_code(func->name, comments, indent), "(", format_args(func->args, comments, indent)); + if (func->ret_type) + code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type, comments, indent)); + if (func->cache) code = Texts(code, "; cache=", format_code(func->cache, comments, indent)); + if (func->is_inline) code = Texts(code, "; inline"); + code = Texts(code, ")\n", single_indent, format_code(func->body, comments, Texts(indent, single_indent))); + return Texts(code, "\n"); + } + 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); + } } } @@ -77,11 +280,11 @@ Text_t format_file(const char *path) { const char *fmt_pos = file->text; Text_t code = EMPTY_TEXT; for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { - code = Texts(code, comment, "\n"); + code = Texts(code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), "\n"); } - code = Texts(code, format_code(ast, ctx.comments)); + code = Texts(code, format_code(ast, ctx.comments, EMPTY_TEXT)); for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { - code = Texts(code, comment, "\n"); + code = Texts(code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), "\n"); } return code; } diff --git a/src/formatter.h b/src/formatter.h index dd4c7815..bead52ec 100644 --- a/src/formatter.h +++ b/src/formatter.h @@ -6,6 +6,6 @@ #include "stdlib/datatypes.h" Text_t format_file(const char *path); -Text_t format_code(ast_t *ast, Table_t comments); +Text_t format_code(ast_t *ast, Table_t comments, Text_t indentation); OptionalText_t format_inline_code(ast_t *ast, Table_t comments); OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); diff --git a/src/parse/functions.c b/src/parse/functions.c index e820182b..b50519b7 100644 --- a/src/parse/functions.c +++ b/src/parse/functions.c @@ -26,6 +26,7 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { type_ast_t *type = NULL; typedef struct name_list_s { + const char *start, *end; const char *name; struct name_list_s *next; } name_list_t; @@ -35,21 +36,22 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { whitespace(ctx, pos); const char *name = get_id(pos); if (!name) break; + const char *name_start = *pos; whitespace(ctx, pos); if (match(pos, ":")) { type = expect(ctx, *pos - 1, pos, parse_type, "I expected a type here"); - names = new (name_list_t, .name = name, .next = names); whitespace(ctx, pos); if (match(pos, "=")) default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='"); + names = new (name_list_t, .start = name_start, .end = *pos, .name = name, .next = names); break; } else if (strncmp(*pos, "==", 2) != 0 && match(pos, "=")) { default_val = expect(ctx, *pos - 1, pos, parse_term, "I expected a value after this '='"); - names = new (name_list_t, .name = name, .next = names); + names = new (name_list_t, .start = name_start, .end = *pos, .name = name, .next = names); break; } else if (name) { - names = new (name_list_t, .name = name, .next = names); + names = new (name_list_t, .start = name_start, .end = *pos, .name = name, .next = names); spaces(pos); if (!match(pos, ",")) break; } else { @@ -64,7 +66,8 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { REVERSE_LIST(names); for (; names; names = names->next) - args = new (arg_ast_t, .name = names->name, .type = type, .value = default_val, .next = args); + args = new (arg_ast_t, .start = names->start, .end = names->end, .name = names->name, .type = type, + .value = default_val, .next = args); if (!match_separator(ctx, pos)) break; } diff --git a/src/parse/suffixes.c b/src/parse/suffixes.c index 58d951c4..cb54b2f6 100644 --- a/src/parse/suffixes.c +++ b/src/parse/suffixes.c @@ -132,7 +132,7 @@ ast_t *parse_method_call_suffix(parse_ctx_t *ctx, ast_t *self) { if (name) parser_err(ctx, arg_start, pos, "I expected an argument here"); break; } - args = new (arg_ast_t, .name = name, .value = arg, .next = args); + args = new (arg_ast_t, .start = arg_start, .end = arg->end, .name = name, .value = arg, .next = args); if (!match_separator(ctx, &pos)) break; } REVERSE_LIST(args); -- cgit v1.2.3 From 15f3227ae693b6b9c07f8a89909faaff297ceb9a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 14:12:36 -0400 Subject: Incremental improvements --- src/formatter.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/formatter.c b/src/formatter.c index 2b367e3d..9b7439c3 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -171,6 +171,12 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (if_->else_body) code = Texts(code, " else ", must(format_inline_code(if_->else_body, comments))); return code; } + case Repeat: return Texts("repeat ", must(format_inline_code(Match(ast, Repeat)->body, comments))); + case While: { + DeclareMatch(loop, ast, While); + return Texts("while ", must(format_inline_code(loop->condition, comments)), " do ", + must(format_inline_code(loop->body, comments))); + } default: { Text_t code = Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); if (Text$has(code, Text("\n"))) return NONE_TEXT; @@ -195,7 +201,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { add_line(&code, Text(""), indent); gap_before_comment = false; } - add_line(&code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), indent); + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent); } add_line(&code, format_code(stmt->ast, comments, indent), indent); @@ -210,7 +216,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { add_line(&code, Text(""), indent); gap_before_comment = false; } - add_line(&code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), indent); + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent); } return code; } @@ -225,6 +231,15 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { format_code(if_->else_body, comments, Texts(indent, single_indent))); return code; } + case Repeat: { + return Texts("repeat\n", indent, single_indent, + format_code(Match(ast, Repeat)->body, comments, Texts(indent, single_indent))); + } + case While: { + DeclareMatch(loop, ast, While); + return Texts("while ", format_code(loop->condition, comments, indent), "\n", indent, single_indent, + format_code(loop->body, comments, Texts(indent, single_indent))); + } case FunctionDef: { DeclareMatch(func, ast, FunctionDef); // ast_t *name; @@ -280,11 +295,11 @@ Text_t format_file(const char *path) { const char *fmt_pos = file->text; Text_t code = EMPTY_TEXT; for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { - code = Texts(code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), "\n"); + code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); } code = Texts(code, format_code(ast, ctx.comments, EMPTY_TEXT)); for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { - code = Texts(code, Texts("# ", Text$trim(comment, Text("# \t\r\n\v"), true, true)), "\n"); + code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); } return code; } -- cgit v1.2.3 From 6c134dd340e24e6247a9ca5a03a7c47c27c3d34f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 14:36:51 -0400 Subject: More formatter functionality --- src/ast.h | 1 + src/formatter.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/parse/typedefs.c | 4 ++- 3 files changed, 77 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/ast.h b/src/ast.h index 43972371..8facc202 100644 --- a/src/ast.h +++ b/src/ast.h @@ -90,6 +90,7 @@ typedef enum { } type_ast_e; typedef struct tag_ast_s { + const char *start, *end; const char *name; arg_ast_t *fields; struct tag_ast_s *next; diff --git a/src/formatter.c b/src/formatter.c index 9b7439c3..cd3e1463 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -99,7 +99,8 @@ static OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { } static Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { - (void)comments; + OptionalText_t inline_arg = format_inline_arg(arg, comments); + if (inline_arg.length >= 0) return inline_arg; Text_t code = Text$from_str(arg->name); if (arg->type) code = Texts(code, ":", format_type(arg->type, comments, indent)); if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); @@ -134,13 +135,55 @@ static Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { return code; } -ast_t *unwrap_block(ast_t *ast) { +static OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments) { + if (range_has_comment(tag->start, tag->end, comments)) return NONE_TEXT; + Text_t code = Texts(Text$from_str(tag->name), "(", must(format_inline_args(tag->fields, comments))); + if (tag->secret) code = Texts(code, "; secret"); + return Texts(code, ")"); +} + +static Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent) { + OptionalText_t inline_tag = format_inline_tag(tag, comments); + if (inline_tag.length >= 0) return inline_tag; + Text_t code = + Texts(Text$from_str(tag->name), "(", format_args(tag->fields, comments, Texts(indent, single_indent))); + if (tag->secret) code = Texts(code, "; secret"); + return Texts(code, ")"); +} + +static OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments) { + Text_t code = EMPTY_TEXT; + for (; tags; tags = tags->next) { + code = Texts(code, must(format_inline_tag(tags, comments))); + if (tags->next) code = Texts(code, ", "); + if (tags->next && range_has_comment(tags->end, tags->next->start, comments)) return NONE_TEXT; + } + return code; +} + +static Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent) { + OptionalText_t inline_tags = format_inline_tags(tags, comments); + if (inline_tags.length >= 0) return inline_tags; + Text_t code = EMPTY_TEXT; + for (; tags; tags = tags->next) { + add_line(&code, Texts(format_tag(tags, comments, indent), ","), indent); + } + return code; +} + +static CONSTFUNC ast_t *unwrap_block(ast_t *ast) { + if (ast == NULL) return NULL; while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { ast = Match(ast, Block)->statements->ast; } return ast; } +static Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { + if (unwrap_block(namespace) == NULL) return EMPTY_TEXT; + return Texts("\n", indent, single_indent, format_code(namespace, comments, Texts(indent, single_indent))); +} + OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { @@ -150,6 +193,10 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { else if (statements->next == NULL) return format_inline_code(statements->ast, comments); else return NONE_TEXT; } + case StructDef: + case EnumDef: + case LangDef: + case Extend: case FunctionDef: return NONE_TEXT; case If: { DeclareMatch(if_, ast, If); @@ -254,8 +301,30 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type, comments, indent)); if (func->cache) code = Texts(code, "; cache=", format_code(func->cache, comments, indent)); if (func->is_inline) code = Texts(code, "; inline"); - code = Texts(code, ")\n", single_indent, format_code(func->body, comments, Texts(indent, single_indent))); - return Texts(code, "\n"); + code = + Texts(code, ")\n", indent, single_indent, format_code(func->body, comments, Texts(indent, single_indent))); + return Texts(code); + } + case StructDef: { + DeclareMatch(def, ast, StructDef); + Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); + if (def->secret) code = Texts(code, "; secret"); + if (def->external) code = Texts(code, "; external"); + if (def->opaque) code = Texts(code, "; opaque"); + return Texts(code, ")", format_namespace(def->namespace, comments, indent)); + } + case EnumDef: { + DeclareMatch(def, ast, EnumDef); + Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent)); + return Texts(code, ")", format_namespace(def->namespace, comments, indent)); + } + case LangDef: { + DeclareMatch(def, ast, LangDef); + return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent)); + } + case Extend: { + DeclareMatch(extend, ast, Extend); + return Texts("lang ", Text$from_str(extend->name), format_namespace(extend->body, comments, indent)); } default: { if (inlined_fits) return inlined; diff --git a/src/parse/typedefs.c b/src/parse/typedefs.c index d5ade767..6e5e40d0 100644 --- a/src/parse/typedefs.c +++ b/src/parse/typedefs.c @@ -113,6 +113,7 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { whitespace(ctx, &pos); for (;;) { spaces(&pos); + const char *tag_start = pos; const char *tag_name = get_id(&pos); if (!tag_name) break; @@ -133,7 +134,8 @@ ast_t *parse_enum_def(parse_ctx_t *ctx, const char *pos) { fields = NULL; } - tags = new (tag_ast_t, .name = tag_name, .fields = fields, .secret = secret, .next = tags); + tags = new (tag_ast_t, .start = tag_start, .end = pos, .name = tag_name, .fields = fields, .secret = secret, + .next = tags); if (!match_separator(ctx, &pos)) break; } -- cgit v1.2.3 From fd0033f0ed993ff4dde64d4a33d7accf8ef52c54 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 15:26:07 -0400 Subject: Much more formatting --- src/formatter.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 215 insertions(+), 6 deletions(-) (limited to 'src') 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 +#include #include #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)); } } } -- cgit v1.2.3 From e927a088be671a003b8e4816a4a963243c0b61aa Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 15:55:51 -0400 Subject: More formatting --- src/formatter.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src') diff --git a/src/formatter.c b/src/formatter.c index 384e3b9a..94c32b07 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -189,6 +189,7 @@ static Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { + case Unknown: fail("Invalid AST"); case Block: { ast_list_t *statements = Match(ast, Block)->statements; if (statements == NULL) return Text("pass"); @@ -246,6 +247,20 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { Texts(code, decl->type ? Text(" = ") : Text(" := "), must(format_inline_code(decl->value, comments))); return code; } + case Assign: { + DeclareMatch(assign, ast, Assign); + Text_t code = EMPTY_TEXT; + for (ast_list_t *target = assign->targets; target; target = target->next) { + code = Texts(code, must(format_inline_code(target->ast, comments))); + if (target->next) code = Texts(code, ", "); + } + code = Texts(code, " = "); + for (ast_list_t *value = assign->values; value; value = value->next) { + code = Texts(code, must(format_inline_code(value->ast, comments))); + if (value->next) code = Texts(code, ", "); + } + return code; + } case Return: { ast_t *value = Match(ast, Return)->value; return value ? Texts("return ", must(format_inline_code(value, comments))) : Text("return"); @@ -327,6 +342,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH); switch (ast->tag) { + case Unknown: fail("Invalid AST"); case Block: { Text_t code = EMPTY_TEXT; bool gap_before_comment = false; @@ -447,6 +463,20 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { code = Texts(code, decl->type ? Text(" = ") : Text(" := "), format_code(decl->value, comments, indent)); return code; } + case Assign: { + DeclareMatch(assign, ast, Assign); + Text_t code = EMPTY_TEXT; + for (ast_list_t *target = assign->targets; target; target = target->next) { + code = Texts(code, format_code(target->ast, comments, indent)); + if (target->next) code = Texts(code, ", "); + } + code = Texts(code, " = "); + for (ast_list_t *value = assign->values; value; value = value->next) { + code = Texts(code, format_code(value->ast, comments, indent)); + if (value->next) code = Texts(code, ", "); + } + return code; + } case Return: { ast_t *value = Match(ast, Return)->value; return value ? Texts("return ", format_code(value, comments, indent)) : Text("return"); -- cgit v1.2.3 From 247043eae0ad88b1833d6a64e7ded130605dd83a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 19:11:17 -0400 Subject: Better support for binops in formatter --- src/ast.c | 31 +++++++++++++++++-- src/ast.h | 3 ++ src/formatter.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++----- src/parse/binops.c | 27 ---------------- 4 files changed, 115 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 53d80a81..314ff998 100644 --- a/src/ast.c +++ b/src/ast.c @@ -8,6 +8,33 @@ #include "stdlib/tables.h" #include "stdlib/text.h" +const int op_tightness[NUM_AST_TAGS] = { + [Power] = 9, + [Multiply] = 8, + [Divide] = 8, + [Mod] = 8, + [Mod1] = 8, + [Plus] = 7, + [Minus] = 7, + [Concat] = 6, + [LeftShift] = 5, + [RightShift] = 5, + [UnsignedLeftShift] = 5, + [UnsignedRightShift] = 5, + [Min] = 4, + [Max] = 4, + [Equals] = 3, + [NotEquals] = 3, + [LessThan] = 2, + [LessThanOrEquals] = 2, + [GreaterThan] = 2, + [GreaterThanOrEquals] = 2, + [Compare] = 2, + [And] = 1, + [Or] = 1, + [Xor] = 1, +}; + static Text_t quoted_text(const char *text) { return Text$quoted(Text$from_str(text), false, Text("\"")); } CONSTFUNC const char *binop_method_name(ast_e tag) { @@ -44,7 +71,7 @@ CONSTFUNC const char *binop_method_name(ast_e tag) { case XorUpdate: return "bit_xor"; default: return NULL; } -}; +} CONSTFUNC const char *binop_operator(ast_e tag) { switch (tag) { @@ -76,7 +103,7 @@ CONSTFUNC const char *binop_operator(ast_e tag) { case GreaterThanOrEquals: return ">="; default: return NULL; } -}; +} static Text_t ast_list_to_sexp(ast_list_t *asts); static Text_t arg_list_to_sexp(arg_ast_t *args); diff --git a/src/ast.h b/src/ast.h index 8facc202..30c413ca 100644 --- a/src/ast.h +++ b/src/ast.h @@ -274,6 +274,7 @@ typedef enum { Extend, ExplicitlyTyped, } ast_e; +#define NUM_AST_TAGS (ExplicitlyTyped + 1) struct ast_s { ast_e tag; @@ -473,6 +474,8 @@ struct ast_s { } __data; }; +extern const int op_tightness[NUM_AST_TAGS]; + const char *ast_source(ast_t *ast); Text_t ast_to_sexp(ast_t *ast); diff --git a/src/formatter.c b/src/formatter.c index 94c32b07..224a1489 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -69,6 +69,16 @@ static bool should_have_blank_line(ast_t *ast) { } } +static Text_t indent_code(Text_t code) { + if (code.length <= 0) return code; + return Texts(single_indent, Text$replace(code, Text("\n"), Texts("\n", single_indent))); +} + +static Text_t parenthesize(Text_t code, Text_t indent) { + if (Text$has(code, Text("\n"))) return Texts("(\n", indent, indent_code(code), "\n", indent, ")"); + else return Texts("(", code, ")"); +} + static OptionalText_t format_inline_type(type_ast_t *type, Table_t comments) { if (range_has_comment(type->start, type->end, comments)) return NONE_TEXT; switch (type->tag) { @@ -186,6 +196,44 @@ static Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent return Texts("\n", indent, single_indent, format_code(namespace, comments, Texts(indent, single_indent))); } +static CONSTFUNC const char *binop_tomo_operator(ast_e tag) { + switch (tag) { + case Power: return "^"; + case PowerUpdate: return "^="; + case Concat: return "++"; + case ConcatUpdate: return "++="; + case Multiply: return "*"; + case MultiplyUpdate: return "*="; + case Divide: return "/"; + case DivideUpdate: return "/="; + case Mod: return "mod"; + case ModUpdate: return "mod="; + case Mod1: return "mod1"; + case Mod1Update: return "mod1="; + case Plus: return "+"; + case PlusUpdate: return "+="; + case Minus: return "-"; + case MinusUpdate: return "-="; + case LeftShift: return "<<"; + case LeftShiftUpdate: return "<<="; + case RightShift: return ">>"; + case RightShiftUpdate: return ">>="; + case And: return "and"; + case AndUpdate: return "and="; + case Or: return "or"; + case OrUpdate: return "or="; + case Xor: return "xor"; + case XorUpdate: return "xor="; + case Equals: return "=="; + case NotEquals: return "!="; + case LessThan: return "<"; + case LessThanOrEquals: return "<="; + case GreaterThan: return ">"; + case GreaterThanOrEquals: return ">="; + default: return NULL; + } +} + OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { @@ -327,9 +375,24 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t 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)), ")"); + const char *op = binop_tomo_operator(ast->tag); + + Text_t lhs = must(format_inline_code(operands.lhs, comments)); + Text_t rhs = must(format_inline_code(operands.rhs, comments)); + + if (is_update_assignment(ast)) { + return Texts(lhs, " ", Text$from_str(op), " ", rhs); + } + + if (Text$has(lhs, Text("\n")) + || (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])) + lhs = parenthesize(lhs, EMPTY_TEXT); + if (Text$has(rhs, Text("\n")) + || (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])) + rhs = parenthesize(rhs, EMPTY_TEXT); + + Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); + return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); } default: { fail("Formatting not implemented for: ", ast_to_sexp(ast)); @@ -561,10 +624,23 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { 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, ")"); + const char *op = binop_tomo_operator(ast->tag); + Text_t lhs = format_code(operands.lhs, comments, Texts(indent, single_indent)); + Text_t rhs = format_code(operands.rhs, comments, Texts(indent, single_indent)); + + if (is_update_assignment(ast)) { + return Texts(lhs, " ", Text$from_str(op), " ", rhs); + } + + if (Text$has(lhs, Text("\n")) + || (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])) + lhs = parenthesize(lhs, indent); + if (Text$has(rhs, Text("\n")) + || (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])) + rhs = parenthesize(rhs, indent); + + Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); + return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); } default: { if (inlined_fits) return inlined; diff --git a/src/parse/binops.c b/src/parse/binops.c index a6ac43d2..80bd7dd5 100644 --- a/src/parse/binops.c +++ b/src/parse/binops.c @@ -9,33 +9,6 @@ #include "suffixes.h" #include "utils.h" -int op_tightness[] = { - [Power] = 9, - [Multiply] = 8, - [Divide] = 8, - [Mod] = 8, - [Mod1] = 8, - [Plus] = 7, - [Minus] = 7, - [Concat] = 6, - [LeftShift] = 5, - [RightShift] = 5, - [UnsignedLeftShift] = 5, - [UnsignedRightShift] = 5, - [Min] = 4, - [Max] = 4, - [Equals] = 3, - [NotEquals] = 3, - [LessThan] = 2, - [LessThanOrEquals] = 2, - [GreaterThan] = 2, - [GreaterThanOrEquals] = 2, - [Compare] = 2, - [And] = 1, - [Or] = 1, - [Xor] = 1, -}; - ast_e match_binary_operator(const char **pos) { switch (**pos) { case '+': { -- cgit v1.2.3 From 8898144eeaeab90cefeb6cbf746e9a336bf239be Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 19:31:57 -0400 Subject: Better wrapping for text and binops --- src/formatter.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/formatter.c b/src/formatter.c index 224a1489..51155015 100644 --- a/src/formatter.c +++ b/src/formatter.c @@ -104,6 +104,7 @@ static Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent) { static OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT; + if (arg->name == NULL && arg->value) return must(format_inline_code(arg->value, comments)); Text_t code = Text$from_str(arg->name); if (arg->type) code = Texts(code, ":", must(format_inline_type(arg->type, comments))); if (arg->value) code = Texts(code, " = ", must(format_inline_code(arg->value, comments))); @@ -112,7 +113,8 @@ static OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { static Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { OptionalText_t inline_arg = format_inline_arg(arg, comments); - if (inline_arg.length >= 0) return inline_arg; + if (inline_arg.length >= 0 && inline_arg.length <= MAX_WIDTH) return inline_arg; + if (arg->name == NULL && arg->value) return format_code(arg->value, comments, indent); Text_t code = Text$from_str(arg->name); if (arg->type) code = Texts(code, ":", format_type(arg->type, comments, indent)); if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); @@ -122,8 +124,8 @@ static Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { static OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) { Text_t code = EMPTY_TEXT; for (; args; args = args->next) { - if (args->next && args->type == args->next->type && args->value == args->next->value) { - code = Texts(code, must(Text$from_str(args->name)), ","); + if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { + code = Texts(code, Text$from_str(args->name), ","); } else { code = Texts(code, must(format_inline_arg(args, comments))); if (args->next) code = Texts(code, ", "); @@ -135,11 +137,11 @@ static OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) { static Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { OptionalText_t inline_args = format_inline_args(args, comments); - if (inline_args.length >= 0) return inline_args; + if (inline_args.length >= 0 && inline_args.length <= MAX_WIDTH) return inline_args; Text_t code = EMPTY_TEXT; for (; args; args = args->next) { - if (args->next && args->type == args->next->type && args->value == args->next->value) { - code = Texts(code, must(Text$from_str(args->name)), ","); + if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { + code = Texts(code, Text$from_str(args->name), ","); } else { add_line(&code, Texts(format_arg(args, comments, indent), ","), indent); } @@ -384,11 +386,9 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { return Texts(lhs, " ", Text$from_str(op), " ", rhs); } - if (Text$has(lhs, Text("\n")) - || (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])) + if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag]) lhs = parenthesize(lhs, EMPTY_TEXT); - if (Text$has(rhs, Text("\n")) - || (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])) + if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag]) rhs = parenthesize(rhs, EMPTY_TEXT); Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); @@ -587,7 +587,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { code = Texts(code, "$(", must(format_inline_code(chunk->ast, comments)), ")"); } } - code = Texts(quote, "\n", code, "\n", indent, quote); + add_line(&code, current_line, Texts(indent, single_indent)); + code = Texts(quote, "\n", indent, single_indent, code, "\n", indent, quote); if (lang) code = Texts("$", Text$from_str(lang), code); return code; } @@ -607,8 +608,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { 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)), ")"); + return Texts(format_code(call->fn, comments, indent), "(\n", indent, single_indent, + format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); } case DocTest: { DeclareMatch(test, ast, DocTest); @@ -625,18 +626,16 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { if (inlined_fits) return inlined; binary_operands_t operands = BINARY_OPERANDS(ast); const char *op = binop_tomo_operator(ast->tag); - Text_t lhs = format_code(operands.lhs, comments, Texts(indent, single_indent)); - Text_t rhs = format_code(operands.rhs, comments, Texts(indent, single_indent)); + Text_t lhs = format_code(operands.lhs, comments, indent); + Text_t rhs = format_code(operands.rhs, comments, indent); if (is_update_assignment(ast)) { return Texts(lhs, " ", Text$from_str(op), " ", rhs); } - if (Text$has(lhs, Text("\n")) - || (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag])) + if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag]) lhs = parenthesize(lhs, indent); - if (Text$has(rhs, Text("\n")) - || (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag])) + if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag]) rhs = parenthesize(rhs, indent); Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); -- cgit v1.2.3 From 91b6746fe1315aa9c09936b69cea3892c04c11af Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 23:53:03 -0400 Subject: Split out formatter into subfiles --- src/formatter.c | 688 ---------------------------------------------- src/formatter.h | 11 - src/formatter/args.c | 55 ++++ src/formatter/args.h | 15 + src/formatter/enums.c | 45 +++ src/formatter/enums.h | 11 + src/formatter/formatter.c | 576 ++++++++++++++++++++++++++++++++++++++ src/formatter/formatter.h | 26 ++ src/formatter/types.c | 30 ++ src/formatter/types.h | 9 + src/tomo.c | 2 +- 11 files changed, 768 insertions(+), 700 deletions(-) delete mode 100644 src/formatter.c delete mode 100644 src/formatter.h create mode 100644 src/formatter/args.c create mode 100644 src/formatter/args.h create mode 100644 src/formatter/enums.c create mode 100644 src/formatter/enums.h create mode 100644 src/formatter/formatter.c create mode 100644 src/formatter/formatter.h create mode 100644 src/formatter/types.c create mode 100644 src/formatter/types.h (limited to 'src') diff --git a/src/formatter.c b/src/formatter.c deleted file mode 100644 index 51155015..00000000 --- a/src/formatter.c +++ /dev/null @@ -1,688 +0,0 @@ -// This code defines functions for transforming ASTs back into Tomo source text - -#include -#include - -#include "ast.h" -#include "formatter.h" -#include "parse/context.h" -#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" - -#define MAX_WIDTH 100 - -#define must(expr) \ - ({ \ - OptionalText_t _expr = expr; \ - if (_expr.length < 0) return NONE_TEXT; \ - (Text_t) _expr; \ - }) - -const Text_t single_indent = Text(" "); - -static void add_line(Text_t *code, Text_t line, Text_t indent) { - if (code->length == 0) { - *code = line; - } else { - if (line.length > 0) *code = Texts(*code, "\n", indent, line); - else *code = Texts(*code, "\n"); - } -} - -OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) { - for (const char *p = *pos; p < end; p++) { - const char **comment_end = Table$get(comments, &p, parse_comments_info); - if (comment_end) { - *pos = *comment_end; - return Text$from_strn(p, (int64_t)(*comment_end - p)); - } - } - return NONE_TEXT; -} - -static bool range_has_comment(const char *start, const char *end, Table_t comments) { - OptionalText_t comment = next_comment(comments, &start, end); - return (comment.length >= 0); -} - -static bool should_have_blank_line(ast_t *ast) { - switch (ast->tag) { - case If: - case When: - case Repeat: - case While: - case For: - case Block: - case FunctionDef: - case StructDef: - case EnumDef: - case LangDef: - case ConvertDef: - case Extend: return true; - default: return false; - } -} - -static Text_t indent_code(Text_t code) { - if (code.length <= 0) return code; - return Texts(single_indent, Text$replace(code, Text("\n"), Texts("\n", single_indent))); -} - -static Text_t parenthesize(Text_t code, Text_t indent) { - if (Text$has(code, Text("\n"))) return Texts("(\n", indent, indent_code(code), "\n", indent, ")"); - else return Texts("(", code, ")"); -} - -static OptionalText_t format_inline_type(type_ast_t *type, Table_t comments) { - if (range_has_comment(type->start, type->end, comments)) return NONE_TEXT; - switch (type->tag) { - default: { - Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); - if (Text$has(code, Text("\n"))) return NONE_TEXT; - return Text$replace(code, Text("\t"), single_indent); - } - } -} - -static Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent) { - (void)comments, (void)indent; - switch (type->tag) { - default: { - OptionalText_t inline_type = format_inline_type(type, comments); - if (inline_type.length >= 0) return inline_type; - Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); - return Text$replace(code, Text("\t"), single_indent); - } - } -} - -static OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { - if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT; - if (arg->name == NULL && arg->value) return must(format_inline_code(arg->value, comments)); - Text_t code = Text$from_str(arg->name); - if (arg->type) code = Texts(code, ":", must(format_inline_type(arg->type, comments))); - if (arg->value) code = Texts(code, " = ", must(format_inline_code(arg->value, comments))); - return code; -} - -static Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { - OptionalText_t inline_arg = format_inline_arg(arg, comments); - if (inline_arg.length >= 0 && inline_arg.length <= MAX_WIDTH) return inline_arg; - if (arg->name == NULL && arg->value) return format_code(arg->value, comments, indent); - Text_t code = Text$from_str(arg->name); - if (arg->type) code = Texts(code, ":", format_type(arg->type, comments, indent)); - if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); - return code; -} - -static OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) { - Text_t code = EMPTY_TEXT; - for (; args; args = args->next) { - if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { - code = Texts(code, Text$from_str(args->name), ","); - } else { - code = Texts(code, must(format_inline_arg(args, comments))); - if (args->next) code = Texts(code, ", "); - } - if (args->next && range_has_comment(args->end, args->next->start, comments)) return NONE_TEXT; - } - return code; -} - -static Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { - OptionalText_t inline_args = format_inline_args(args, comments); - if (inline_args.length >= 0 && inline_args.length <= MAX_WIDTH) return inline_args; - Text_t code = EMPTY_TEXT; - for (; args; args = args->next) { - if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { - code = Texts(code, Text$from_str(args->name), ","); - } else { - add_line(&code, Texts(format_arg(args, comments, indent), ","), indent); - } - } - return code; -} - -static OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments) { - if (range_has_comment(tag->start, tag->end, comments)) return NONE_TEXT; - Text_t code = Texts(Text$from_str(tag->name), "(", must(format_inline_args(tag->fields, comments))); - if (tag->secret) code = Texts(code, "; secret"); - return Texts(code, ")"); -} - -static Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent) { - OptionalText_t inline_tag = format_inline_tag(tag, comments); - if (inline_tag.length >= 0) return inline_tag; - Text_t code = - Texts(Text$from_str(tag->name), "(", format_args(tag->fields, comments, Texts(indent, single_indent))); - if (tag->secret) code = Texts(code, "; secret"); - return Texts(code, ")"); -} - -static OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments) { - Text_t code = EMPTY_TEXT; - for (; tags; tags = tags->next) { - code = Texts(code, must(format_inline_tag(tags, comments))); - if (tags->next) code = Texts(code, ", "); - if (tags->next && range_has_comment(tags->end, tags->next->start, comments)) return NONE_TEXT; - } - return code; -} - -static Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent) { - OptionalText_t inline_tags = format_inline_tags(tags, comments); - if (inline_tags.length >= 0) return inline_tags; - Text_t code = EMPTY_TEXT; - for (; tags; tags = tags->next) { - add_line(&code, Texts(format_tag(tags, comments, indent), ","), indent); - } - return code; -} - -static CONSTFUNC ast_t *unwrap_block(ast_t *ast) { - if (ast == NULL) return NULL; - while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { - ast = Match(ast, Block)->statements->ast; - } - return ast; -} - -static Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { - if (unwrap_block(namespace) == NULL) return EMPTY_TEXT; - return Texts("\n", indent, single_indent, format_code(namespace, comments, Texts(indent, single_indent))); -} - -static CONSTFUNC const char *binop_tomo_operator(ast_e tag) { - switch (tag) { - case Power: return "^"; - case PowerUpdate: return "^="; - case Concat: return "++"; - case ConcatUpdate: return "++="; - case Multiply: return "*"; - case MultiplyUpdate: return "*="; - case Divide: return "/"; - case DivideUpdate: return "/="; - case Mod: return "mod"; - case ModUpdate: return "mod="; - case Mod1: return "mod1"; - case Mod1Update: return "mod1="; - case Plus: return "+"; - case PlusUpdate: return "+="; - case Minus: return "-"; - case MinusUpdate: return "-="; - case LeftShift: return "<<"; - case LeftShiftUpdate: return "<<="; - case RightShift: return ">>"; - case RightShiftUpdate: return ">>="; - case And: return "and"; - case AndUpdate: return "and="; - case Or: return "or"; - case OrUpdate: return "or="; - case Xor: return "xor"; - case XorUpdate: return "xor="; - case Equals: return "=="; - case NotEquals: return "!="; - case LessThan: return "<"; - case LessThanOrEquals: return "<="; - case GreaterThan: return ">"; - case GreaterThanOrEquals: return ">="; - default: return NULL; - } -} - -OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { - if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; - switch (ast->tag) { - case Unknown: fail("Invalid AST"); - case Block: { - ast_list_t *statements = Match(ast, Block)->statements; - if (statements == NULL) return Text("pass"); - else if (statements->next == NULL) return format_inline_code(statements->ast, comments); - else return NONE_TEXT; - } - case StructDef: - case EnumDef: - case LangDef: - case Extend: - case FunctionDef: - case DocTest: return NONE_TEXT; - case If: { - DeclareMatch(if_, ast, If); - - if (if_->else_body == NULL && if_->condition->tag != Declare) { - ast_t *stmt = unwrap_block(if_->body); - switch (stmt->tag) { - case Return: - case Skip: - case Stop: - return Texts(must(format_inline_code(stmt, comments)), " if ", - must(format_inline_code(if_->condition, comments))); - default: break; - } - } - - Text_t code = Texts("if ", must(format_inline_code(if_->condition, comments)), " then ", - must(format_inline_code(if_->body, comments))); - if (if_->else_body) code = Texts(code, " else ", must(format_inline_code(if_->else_body, comments))); - return code; - } - case Repeat: return Texts("repeat ", must(format_inline_code(Match(ast, Repeat)->body, comments))); - case While: { - DeclareMatch(loop, ast, While); - return Texts("while ", must(format_inline_code(loop->condition, comments)), " do ", - must(format_inline_code(loop->body, comments))); - } - 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 Assign: { - DeclareMatch(assign, ast, Assign); - Text_t code = EMPTY_TEXT; - for (ast_list_t *target = assign->targets; target; target = target->next) { - code = Texts(code, must(format_inline_code(target->ast, comments))); - if (target->next) code = Texts(code, ", "); - } - code = Texts(code, " = "); - for (ast_list_t *value = assign->values; value; value = value->next) { - code = Texts(code, must(format_inline_code(value->ast, comments))); - if (value->next) code = Texts(code, ", "); - } - 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_tomo_operator(ast->tag); - - Text_t lhs = must(format_inline_code(operands.lhs, comments)); - Text_t rhs = must(format_inline_code(operands.rhs, comments)); - - if (is_update_assignment(ast)) { - return Texts(lhs, " ", Text$from_str(op), " ", rhs); - } - - if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag]) - lhs = parenthesize(lhs, EMPTY_TEXT); - if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag]) - rhs = parenthesize(rhs, EMPTY_TEXT); - - Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); - return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); - } - default: { - fail("Formatting not implemented for: ", ast_to_sexp(ast)); - } - } -} - -Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { - OptionalText_t inlined = format_inline_code(ast, comments); - bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH); - - switch (ast->tag) { - case Unknown: fail("Invalid AST"); - case Block: { - Text_t code = EMPTY_TEXT; - 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) { - add_line(&code, Text(""), indent); - gap_before_comment = false; - } - add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), 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 && !should_have_blank_line(stmt->next->ast)) - add_line(&code, Text(""), indent); - else gap_before_comment = true; - } - - for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) { - if (gap_before_comment) { - add_line(&code, Text(""), indent); - gap_before_comment = false; - } - add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent); - } - return code; - } - case If: { - DeclareMatch(if_, ast, If); - if (inlined_fits && if_->else_body == NULL) return inlined; - - Text_t code = Texts("if ", format_code(if_->condition, comments, indent), "\n", indent, single_indent, - format_code(if_->body, comments, Texts(indent, single_indent))); - if (if_->else_body) - code = Texts(code, "\n", indent, "else \n", indent, single_indent, - format_code(if_->else_body, comments, Texts(indent, single_indent))); - return code; - } - case Repeat: { - return Texts("repeat\n", indent, single_indent, - format_code(Match(ast, Repeat)->body, comments, Texts(indent, single_indent))); - } - case While: { - DeclareMatch(loop, ast, While); - return Texts("while ", format_code(loop->condition, comments, indent), "\n", indent, single_indent, - format_code(loop->body, comments, Texts(indent, single_indent))); - } - case FunctionDef: { - DeclareMatch(func, ast, FunctionDef); - // ast_t *name; - // arg_ast_t *args; - // type_ast_t *ret_type; - // ast_t *body; - // ast_t *cache; - // bool is_inline; - Text_t code = - Texts("func ", format_code(func->name, comments, indent), "(", format_args(func->args, comments, indent)); - if (func->ret_type) - code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type, comments, indent)); - if (func->cache) code = Texts(code, "; cache=", format_code(func->cache, comments, indent)); - if (func->is_inline) code = Texts(code, "; inline"); - code = - Texts(code, ")\n", indent, single_indent, format_code(func->body, comments, Texts(indent, single_indent))); - return Texts(code); - } - case StructDef: { - DeclareMatch(def, ast, StructDef); - Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); - if (def->secret) code = Texts(code, "; secret"); - if (def->external) code = Texts(code, "; external"); - if (def->opaque) code = Texts(code, "; opaque"); - return Texts(code, ")", format_namespace(def->namespace, comments, indent)); - } - case EnumDef: { - DeclareMatch(def, ast, EnumDef); - Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent)); - return Texts(code, ")", format_namespace(def->namespace, comments, indent)); - } - case LangDef: { - DeclareMatch(def, ast, LangDef); - return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent)); - } - case Extend: { - 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 Assign: { - DeclareMatch(assign, ast, Assign); - Text_t code = EMPTY_TEXT; - for (ast_list_t *target = assign->targets; target; target = target->next) { - code = Texts(code, format_code(target->ast, comments, indent)); - if (target->next) code = Texts(code, ", "); - } - code = Texts(code, " = "); - for (ast_list_t *value = assign->values; value; value = value->next) { - code = Texts(code, format_code(value->ast, comments, indent)); - if (value->next) code = Texts(code, ", "); - } - 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)), ")"); - } - } - add_line(&code, current_line, Texts(indent, single_indent)); - code = Texts(quote, "\n", indent, single_indent, 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), "(\n", indent, single_indent, - format_args(call->args, comments, Texts(indent, single_indent)), "\n", 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_tomo_operator(ast->tag); - Text_t lhs = format_code(operands.lhs, comments, indent); - Text_t rhs = format_code(operands.rhs, comments, indent); - - if (is_update_assignment(ast)) { - return Texts(lhs, " ", Text$from_str(op), " ", rhs); - } - - if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag]) - lhs = parenthesize(lhs, indent); - if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag]) - rhs = parenthesize(rhs, indent); - - Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); - return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); - } - default: { - if (inlined_fits) return inlined; - fail("Formatting not implemented for: ", ast_to_sexp(ast)); - } - } -} - -Text_t format_file(const char *path) { - file_t *file = load_file(path); - if (!file) return EMPTY_TEXT; - - jmp_buf on_err; - if (setjmp(on_err) != 0) { - return Text$from_str(file->text); - } - parse_ctx_t ctx = { - .file = file, - .on_err = &on_err, - .comments = {}, - }; - - const char *pos = file->text; - if (match(&pos, "#!")) // shebang - some_not(&pos, "\r\n"); - - whitespace(&ctx, &pos); - ast_t *ast = parse_file_body(&ctx, pos); - if (!ast) return Text$from_str(file->text); - pos = ast->end; - whitespace(&ctx, &pos); - if (pos < file->text + file->len && *pos != '\0') { - return Text$from_str(file->text); - } - - const char *fmt_pos = file->text; - Text_t code = EMPTY_TEXT; - for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { - code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); - } - code = Texts(code, format_code(ast, ctx.comments, EMPTY_TEXT)); - for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { - code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); - } - return code; -} diff --git a/src/formatter.h b/src/formatter.h deleted file mode 100644 index bead52ec..00000000 --- a/src/formatter.h +++ /dev/null @@ -1,11 +0,0 @@ -// This code defines functions for transforming ASTs back into Tomo source text - -#pragma once - -#include "ast.h" -#include "stdlib/datatypes.h" - -Text_t format_file(const char *path); -Text_t format_code(ast_t *ast, Table_t comments, Text_t indentation); -OptionalText_t format_inline_code(ast_t *ast, Table_t comments); -OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); diff --git a/src/formatter/args.c b/src/formatter/args.c new file mode 100644 index 00000000..9467c9fe --- /dev/null +++ b/src/formatter/args.c @@ -0,0 +1,55 @@ +// Logic for formatting arguments and argument lists + +#include "../ast.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/optionals.h" +#include "../stdlib/text.h" +#include "formatter.h" +#include "types.h" + +OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { + if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT; + if (arg->name == NULL && arg->value) return must(format_inline_code(arg->value, comments)); + Text_t code = Text$from_str(arg->name); + if (arg->type) code = Texts(code, ":", must(format_inline_type(arg->type, comments))); + if (arg->value) code = Texts(code, " = ", must(format_inline_code(arg->value, comments))); + return code; +} + +Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { + OptionalText_t inline_arg = format_inline_arg(arg, comments); + if (inline_arg.length >= 0 && inline_arg.length <= MAX_WIDTH) return inline_arg; + if (arg->name == NULL && arg->value) return format_code(arg->value, comments, indent); + Text_t code = Text$from_str(arg->name); + if (arg->type) code = Texts(code, ":", format_type(arg->type, comments, indent)); + if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); + return code; +} + +OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) { + Text_t code = EMPTY_TEXT; + for (; args; args = args->next) { + if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { + code = Texts(code, Text$from_str(args->name), ","); + } else { + code = Texts(code, must(format_inline_arg(args, comments))); + if (args->next) code = Texts(code, ", "); + } + if (args->next && range_has_comment(args->end, args->next->start, comments)) return NONE_TEXT; + } + return code; +} + +Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { + OptionalText_t inline_args = format_inline_args(args, comments); + if (inline_args.length >= 0 && inline_args.length <= MAX_WIDTH) return inline_args; + Text_t code = EMPTY_TEXT; + for (; args; args = args->next) { + if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { + code = Texts(code, Text$from_str(args->name), ","); + } else { + add_line(&code, Texts(format_arg(args, comments, indent), ","), indent); + } + } + return code; +} diff --git a/src/formatter/args.h b/src/formatter/args.h new file mode 100644 index 00000000..815069a4 --- /dev/null +++ b/src/formatter/args.h @@ -0,0 +1,15 @@ +// Logic for formatting arguments and argument lists + +#pragma once + +#include "../ast.h" +#include "../stdlib/datatypes.h" + +OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments); +Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent); +OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments); +Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent); +OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments); +Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent); +OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments); +Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent); diff --git a/src/formatter/enums.c b/src/formatter/enums.c new file mode 100644 index 00000000..430e72ea --- /dev/null +++ b/src/formatter/enums.c @@ -0,0 +1,45 @@ +// Logic for formatting enums and enum tags + +#include "../ast.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/optionals.h" +#include "../stdlib/text.h" +#include "args.h" +#include "formatter.h" +#include "types.h" + +OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments) { + if (range_has_comment(tag->start, tag->end, comments)) return NONE_TEXT; + Text_t code = Texts(Text$from_str(tag->name), "(", must(format_inline_args(tag->fields, comments))); + if (tag->secret) code = Texts(code, "; secret"); + return Texts(code, ")"); +} + +Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent) { + OptionalText_t inline_tag = format_inline_tag(tag, comments); + if (inline_tag.length >= 0) return inline_tag; + Text_t code = + Texts(Text$from_str(tag->name), "(", format_args(tag->fields, comments, Texts(indent, single_indent))); + if (tag->secret) code = Texts(code, "; secret"); + return Texts(code, ")"); +} + +OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments) { + Text_t code = EMPTY_TEXT; + for (; tags; tags = tags->next) { + code = Texts(code, must(format_inline_tag(tags, comments))); + if (tags->next) code = Texts(code, ", "); + if (tags->next && range_has_comment(tags->end, tags->next->start, comments)) return NONE_TEXT; + } + return code; +} + +Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent) { + OptionalText_t inline_tags = format_inline_tags(tags, comments); + if (inline_tags.length >= 0) return inline_tags; + Text_t code = EMPTY_TEXT; + for (; tags; tags = tags->next) { + add_line(&code, Texts(format_tag(tags, comments, indent), ","), indent); + } + return code; +} diff --git a/src/formatter/enums.h b/src/formatter/enums.h new file mode 100644 index 00000000..e7233df4 --- /dev/null +++ b/src/formatter/enums.h @@ -0,0 +1,11 @@ +// Logic for formatting enums and enum tags + +#pragma once + +#include "../ast.h" +#include "../stdlib/datatypes.h" + +OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments); +Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent); +OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments); +Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent); diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c new file mode 100644 index 00000000..d2168231 --- /dev/null +++ b/src/formatter/formatter.c @@ -0,0 +1,576 @@ +// This code defines functions for transforming ASTs back into Tomo source text + +#include +#include + +#include "../ast.h" +#include "../parse/context.h" +#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" +#include "args.h" +#include "enums.h" +#include "formatter.h" +#include "types.h" + +const Text_t single_indent = Text(" "); + +void add_line(Text_t *code, Text_t line, Text_t indent) { + if (code->length == 0) { + *code = line; + } else { + if (line.length > 0) *code = Texts(*code, "\n", indent, line); + else *code = Texts(*code, "\n"); + } +} + +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) { + for (const char *p = *pos; p < end; p++) { + const char **comment_end = Table$get(comments, &p, parse_comments_info); + if (comment_end) { + *pos = *comment_end; + return Text$from_strn(p, (int64_t)(*comment_end - p)); + } + } + return NONE_TEXT; +} + +bool range_has_comment(const char *start, const char *end, Table_t comments) { + OptionalText_t comment = next_comment(comments, &start, end); + return (comment.length >= 0); +} + +static bool should_have_blank_line(ast_t *ast) { + switch (ast->tag) { + case If: + case When: + case Repeat: + case While: + case For: + case Block: + case FunctionDef: + case StructDef: + case EnumDef: + case LangDef: + case ConvertDef: + case Extend: return true; + default: return false; + } +} + +static Text_t indent_code(Text_t code) { + if (code.length <= 0) return code; + return Texts(single_indent, Text$replace(code, Text("\n"), Texts("\n", single_indent))); +} + +static Text_t parenthesize(Text_t code, Text_t indent) { + if (Text$has(code, Text("\n"))) return Texts("(\n", indent, indent_code(code), "\n", indent, ")"); + else return Texts("(", code, ")"); +} + +static CONSTFUNC ast_t *unwrap_block(ast_t *ast) { + if (ast == NULL) return NULL; + while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { + ast = Match(ast, Block)->statements->ast; + } + return ast; +} + +static Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { + if (unwrap_block(namespace) == NULL) return EMPTY_TEXT; + return Texts("\n", indent, single_indent, format_code(namespace, comments, Texts(indent, single_indent))); +} + +static CONSTFUNC const char *binop_tomo_operator(ast_e tag) { + switch (tag) { + case Power: return "^"; + case PowerUpdate: return "^="; + case Concat: return "++"; + case ConcatUpdate: return "++="; + case Multiply: return "*"; + case MultiplyUpdate: return "*="; + case Divide: return "/"; + case DivideUpdate: return "/="; + case Mod: return "mod"; + case ModUpdate: return "mod="; + case Mod1: return "mod1"; + case Mod1Update: return "mod1="; + case Plus: return "+"; + case PlusUpdate: return "+="; + case Minus: return "-"; + case MinusUpdate: return "-="; + case LeftShift: return "<<"; + case LeftShiftUpdate: return "<<="; + case RightShift: return ">>"; + case RightShiftUpdate: return ">>="; + case And: return "and"; + case AndUpdate: return "and="; + case Or: return "or"; + case OrUpdate: return "or="; + case Xor: return "xor"; + case XorUpdate: return "xor="; + case Equals: return "=="; + case NotEquals: return "!="; + case LessThan: return "<"; + case LessThanOrEquals: return "<="; + case GreaterThan: return ">"; + case GreaterThanOrEquals: return ">="; + default: return NULL; + } +} + +OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { + if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; + switch (ast->tag) { + case Unknown: fail("Invalid AST"); + case Block: { + ast_list_t *statements = Match(ast, Block)->statements; + if (statements == NULL) return Text("pass"); + else if (statements->next == NULL) return format_inline_code(statements->ast, comments); + else return NONE_TEXT; + } + case StructDef: + case EnumDef: + case LangDef: + case Extend: + case FunctionDef: + case DocTest: return NONE_TEXT; + case If: { + DeclareMatch(if_, ast, If); + + if (if_->else_body == NULL && if_->condition->tag != Declare) { + ast_t *stmt = unwrap_block(if_->body); + switch (stmt->tag) { + case Return: + case Skip: + case Stop: + return Texts(must(format_inline_code(stmt, comments)), " if ", + must(format_inline_code(if_->condition, comments))); + default: break; + } + } + + Text_t code = Texts("if ", must(format_inline_code(if_->condition, comments)), " then ", + must(format_inline_code(if_->body, comments))); + if (if_->else_body) code = Texts(code, " else ", must(format_inline_code(if_->else_body, comments))); + return code; + } + case Repeat: return Texts("repeat ", must(format_inline_code(Match(ast, Repeat)->body, comments))); + case While: { + DeclareMatch(loop, ast, While); + return Texts("while ", must(format_inline_code(loop->condition, comments)), " do ", + must(format_inline_code(loop->body, comments))); + } + 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 Assign: { + DeclareMatch(assign, ast, Assign); + Text_t code = EMPTY_TEXT; + for (ast_list_t *target = assign->targets; target; target = target->next) { + code = Texts(code, must(format_inline_code(target->ast, comments))); + if (target->next) code = Texts(code, ", "); + } + code = Texts(code, " = "); + for (ast_list_t *value = assign->values; value; value = value->next) { + code = Texts(code, must(format_inline_code(value->ast, comments))); + if (value->next) code = Texts(code, ", "); + } + 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_tomo_operator(ast->tag); + + Text_t lhs = must(format_inline_code(operands.lhs, comments)); + Text_t rhs = must(format_inline_code(operands.rhs, comments)); + + if (is_update_assignment(ast)) { + return Texts(lhs, " ", Text$from_str(op), " ", rhs); + } + + if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag]) + lhs = parenthesize(lhs, EMPTY_TEXT); + if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag]) + rhs = parenthesize(rhs, EMPTY_TEXT); + + Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); + return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); + } + default: { + fail("Formatting not implemented for: ", ast_to_sexp(ast)); + } + } +} + +Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { + OptionalText_t inlined = format_inline_code(ast, comments); + bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH); + + switch (ast->tag) { + case Unknown: fail("Invalid AST"); + case Block: { + Text_t code = EMPTY_TEXT; + 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) { + add_line(&code, Text(""), indent); + gap_before_comment = false; + } + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), 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 && !should_have_blank_line(stmt->next->ast)) + add_line(&code, Text(""), indent); + else gap_before_comment = true; + } + + for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) { + if (gap_before_comment) { + add_line(&code, Text(""), indent); + gap_before_comment = false; + } + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent); + } + return code; + } + case If: { + DeclareMatch(if_, ast, If); + if (inlined_fits && if_->else_body == NULL) return inlined; + + Text_t code = Texts("if ", format_code(if_->condition, comments, indent), "\n", indent, single_indent, + format_code(if_->body, comments, Texts(indent, single_indent))); + if (if_->else_body) + code = Texts(code, "\n", indent, "else \n", indent, single_indent, + format_code(if_->else_body, comments, Texts(indent, single_indent))); + return code; + } + case Repeat: { + return Texts("repeat\n", indent, single_indent, + format_code(Match(ast, Repeat)->body, comments, Texts(indent, single_indent))); + } + case While: { + DeclareMatch(loop, ast, While); + return Texts("while ", format_code(loop->condition, comments, indent), "\n", indent, single_indent, + format_code(loop->body, comments, Texts(indent, single_indent))); + } + case FunctionDef: { + DeclareMatch(func, ast, FunctionDef); + // ast_t *name; + // arg_ast_t *args; + // type_ast_t *ret_type; + // ast_t *body; + // ast_t *cache; + // bool is_inline; + Text_t code = + Texts("func ", format_code(func->name, comments, indent), "(", format_args(func->args, comments, indent)); + if (func->ret_type) + code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type, comments, indent)); + if (func->cache) code = Texts(code, "; cache=", format_code(func->cache, comments, indent)); + if (func->is_inline) code = Texts(code, "; inline"); + code = + Texts(code, ")\n", indent, single_indent, format_code(func->body, comments, Texts(indent, single_indent))); + return Texts(code); + } + case StructDef: { + DeclareMatch(def, ast, StructDef); + Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); + if (def->secret) code = Texts(code, "; secret"); + if (def->external) code = Texts(code, "; external"); + if (def->opaque) code = Texts(code, "; opaque"); + return Texts(code, ")", format_namespace(def->namespace, comments, indent)); + } + case EnumDef: { + DeclareMatch(def, ast, EnumDef); + Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent)); + return Texts(code, ")", format_namespace(def->namespace, comments, indent)); + } + case LangDef: { + DeclareMatch(def, ast, LangDef); + return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent)); + } + case Extend: { + 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 Assign: { + DeclareMatch(assign, ast, Assign); + Text_t code = EMPTY_TEXT; + for (ast_list_t *target = assign->targets; target; target = target->next) { + code = Texts(code, format_code(target->ast, comments, indent)); + if (target->next) code = Texts(code, ", "); + } + code = Texts(code, " = "); + for (ast_list_t *value = assign->values; value; value = value->next) { + code = Texts(code, format_code(value->ast, comments, indent)); + if (value->next) code = Texts(code, ", "); + } + 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)), ")"); + } + } + add_line(&code, current_line, Texts(indent, single_indent)); + code = Texts(quote, "\n", indent, single_indent, 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), "(\n", indent, single_indent, + format_args(call->args, comments, Texts(indent, single_indent)), "\n", 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_tomo_operator(ast->tag); + Text_t lhs = format_code(operands.lhs, comments, indent); + Text_t rhs = format_code(operands.rhs, comments, indent); + + if (is_update_assignment(ast)) { + return Texts(lhs, " ", Text$from_str(op), " ", rhs); + } + + if (is_binary_operation(operands.lhs) && op_tightness[operands.lhs->tag] < op_tightness[ast->tag]) + lhs = parenthesize(lhs, indent); + if (is_binary_operation(operands.rhs) && op_tightness[operands.rhs->tag] < op_tightness[ast->tag]) + rhs = parenthesize(rhs, indent); + + Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); + return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); + } + default: { + if (inlined_fits) return inlined; + fail("Formatting not implemented for: ", ast_to_sexp(ast)); + } + } +} + +Text_t format_file(const char *path) { + file_t *file = load_file(path); + if (!file) return EMPTY_TEXT; + + jmp_buf on_err; + if (setjmp(on_err) != 0) { + return Text$from_str(file->text); + } + parse_ctx_t ctx = { + .file = file, + .on_err = &on_err, + .comments = {}, + }; + + const char *pos = file->text; + if (match(&pos, "#!")) // shebang + some_not(&pos, "\r\n"); + + whitespace(&ctx, &pos); + ast_t *ast = parse_file_body(&ctx, pos); + if (!ast) return Text$from_str(file->text); + pos = ast->end; + whitespace(&ctx, &pos); + if (pos < file->text + file->len && *pos != '\0') { + return Text$from_str(file->text); + } + + const char *fmt_pos = file->text; + Text_t code = EMPTY_TEXT; + for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { + code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); + } + code = Texts(code, format_code(ast, ctx.comments, EMPTY_TEXT)); + for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { + code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); + } + return code; +} diff --git a/src/formatter/formatter.h b/src/formatter/formatter.h new file mode 100644 index 00000000..83bb9e56 --- /dev/null +++ b/src/formatter/formatter.h @@ -0,0 +1,26 @@ +// This code defines functions for transforming ASTs back into Tomo source text + +#pragma once + +#include "../ast.h" +#include "../stdlib/datatypes.h" + +#define MAX_WIDTH 100 + +#define must(expr) \ + ({ \ + OptionalText_t _expr = expr; \ + if (_expr.length < 0) return NONE_TEXT; \ + (Text_t) _expr; \ + }) + +extern const Text_t single_indent; + +Text_t format_file(const char *path); +Text_t format_code(ast_t *ast, Table_t comments, Text_t indentation); +OptionalText_t format_inline_code(ast_t *ast, Table_t comments); + +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); +bool range_has_comment(const char *start, const char *end, Table_t comments); +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); +void add_line(Text_t *code, Text_t line, Text_t indent); diff --git a/src/formatter/types.c b/src/formatter/types.c new file mode 100644 index 00000000..282a299f --- /dev/null +++ b/src/formatter/types.c @@ -0,0 +1,30 @@ +// Logic for formatting types + +#include "../ast.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/optionals.h" +#include "../stdlib/text.h" +#include "formatter.h" + +OptionalText_t format_inline_type(type_ast_t *type, Table_t comments) { + if (range_has_comment(type->start, type->end, comments)) return NONE_TEXT; + switch (type->tag) { + default: { + Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); + if (Text$has(code, Text("\n"))) return NONE_TEXT; + return Text$replace(code, Text("\t"), single_indent); + } + } +} + +Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent) { + (void)comments, (void)indent; + switch (type->tag) { + default: { + OptionalText_t inline_type = format_inline_type(type, comments); + if (inline_type.length >= 0) return inline_type; + Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); + return Text$replace(code, Text("\t"), single_indent); + } + } +} diff --git a/src/formatter/types.h b/src/formatter/types.h new file mode 100644 index 00000000..1f0698b3 --- /dev/null +++ b/src/formatter/types.h @@ -0,0 +1,9 @@ +// Logic for formatting types + +#pragma once + +#include "../ast.h" +#include "../stdlib/datatypes.h" + +OptionalText_t format_inline_type(type_ast_t *type, Table_t comments); +Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent); diff --git a/src/tomo.c b/src/tomo.c index 36a47b7f..25a92889 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -19,7 +19,7 @@ #include "compile/files.h" #include "compile/headers.h" #include "config.h" -#include "formatter.h" +#include "formatter/formatter.h" #include "modules.h" #include "naming.h" #include "parse/files.h" -- cgit v1.2.3 From 978835e3dd8dd59a1efaa869f2f603eb9eea5a3f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 25 Aug 2025 23:59:09 -0400 Subject: Split out utility functions --- src/formatter/args.c | 1 + src/formatter/enums.c | 3 +- src/formatter/formatter.c | 107 ++----------------------------------------- src/formatter/formatter.h | 19 ++------ src/formatter/types.c | 2 +- src/formatter/utils.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++ src/formatter/utils.h | 28 ++++++++++++ 7 files changed, 150 insertions(+), 122 deletions(-) create mode 100644 src/formatter/utils.c create mode 100644 src/formatter/utils.h (limited to 'src') diff --git a/src/formatter/args.c b/src/formatter/args.c index 9467c9fe..2bd115cd 100644 --- a/src/formatter/args.c +++ b/src/formatter/args.c @@ -6,6 +6,7 @@ #include "../stdlib/text.h" #include "formatter.h" #include "types.h" +#include "utils.h" OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT; diff --git a/src/formatter/enums.c b/src/formatter/enums.c index 430e72ea..7a89705d 100644 --- a/src/formatter/enums.c +++ b/src/formatter/enums.c @@ -5,8 +5,7 @@ #include "../stdlib/optionals.h" #include "../stdlib/text.h" #include "args.h" -#include "formatter.h" -#include "types.h" +#include "utils.h" OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments) { if (range_has_comment(tag->start, tag->end, comments)) return NONE_TEXT; diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index d2168231..e4c92542 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include "../ast.h" #include "../parse/context.h" @@ -11,119 +13,18 @@ #include "../stdlib/integers.h" #include "../stdlib/optionals.h" #include "../stdlib/stdlib.h" -#include "../stdlib/tables.h" #include "../stdlib/text.h" #include "args.h" #include "enums.h" #include "formatter.h" #include "types.h" +#include "utils.h" -const Text_t single_indent = Text(" "); - -void add_line(Text_t *code, Text_t line, Text_t indent) { - if (code->length == 0) { - *code = line; - } else { - if (line.length > 0) *code = Texts(*code, "\n", indent, line); - else *code = Texts(*code, "\n"); - } -} - -OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) { - for (const char *p = *pos; p < end; p++) { - const char **comment_end = Table$get(comments, &p, parse_comments_info); - if (comment_end) { - *pos = *comment_end; - return Text$from_strn(p, (int64_t)(*comment_end - p)); - } - } - return NONE_TEXT; -} - -bool range_has_comment(const char *start, const char *end, Table_t comments) { - OptionalText_t comment = next_comment(comments, &start, end); - return (comment.length >= 0); -} - -static bool should_have_blank_line(ast_t *ast) { - switch (ast->tag) { - case If: - case When: - case Repeat: - case While: - case For: - case Block: - case FunctionDef: - case StructDef: - case EnumDef: - case LangDef: - case ConvertDef: - case Extend: return true; - default: return false; - } -} - -static Text_t indent_code(Text_t code) { - if (code.length <= 0) return code; - return Texts(single_indent, Text$replace(code, Text("\n"), Texts("\n", single_indent))); -} - -static Text_t parenthesize(Text_t code, Text_t indent) { - if (Text$has(code, Text("\n"))) return Texts("(\n", indent, indent_code(code), "\n", indent, ")"); - else return Texts("(", code, ")"); -} - -static CONSTFUNC ast_t *unwrap_block(ast_t *ast) { - if (ast == NULL) return NULL; - while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { - ast = Match(ast, Block)->statements->ast; - } - return ast; -} - -static Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { +Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { if (unwrap_block(namespace) == NULL) return EMPTY_TEXT; return Texts("\n", indent, single_indent, format_code(namespace, comments, Texts(indent, single_indent))); } -static CONSTFUNC const char *binop_tomo_operator(ast_e tag) { - switch (tag) { - case Power: return "^"; - case PowerUpdate: return "^="; - case Concat: return "++"; - case ConcatUpdate: return "++="; - case Multiply: return "*"; - case MultiplyUpdate: return "*="; - case Divide: return "/"; - case DivideUpdate: return "/="; - case Mod: return "mod"; - case ModUpdate: return "mod="; - case Mod1: return "mod1"; - case Mod1Update: return "mod1="; - case Plus: return "+"; - case PlusUpdate: return "+="; - case Minus: return "-"; - case MinusUpdate: return "-="; - case LeftShift: return "<<"; - case LeftShiftUpdate: return "<<="; - case RightShift: return ">>"; - case RightShiftUpdate: return ">>="; - case And: return "and"; - case AndUpdate: return "and="; - case Or: return "or"; - case OrUpdate: return "or="; - case Xor: return "xor"; - case XorUpdate: return "xor="; - case Equals: return "=="; - case NotEquals: return "!="; - case LessThan: return "<"; - case LessThanOrEquals: return "<="; - case GreaterThan: return ">"; - case GreaterThanOrEquals: return ">="; - default: return NULL; - } -} - OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { diff --git a/src/formatter/formatter.h b/src/formatter/formatter.h index 83bb9e56..a8f9013a 100644 --- a/src/formatter/formatter.h +++ b/src/formatter/formatter.h @@ -2,25 +2,12 @@ #pragma once +#include + #include "../ast.h" #include "../stdlib/datatypes.h" -#define MAX_WIDTH 100 - -#define must(expr) \ - ({ \ - OptionalText_t _expr = expr; \ - if (_expr.length < 0) return NONE_TEXT; \ - (Text_t) _expr; \ - }) - -extern const Text_t single_indent; - Text_t format_file(const char *path); Text_t format_code(ast_t *ast, Table_t comments, Text_t indentation); +Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent); OptionalText_t format_inline_code(ast_t *ast, Table_t comments); - -OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); -bool range_has_comment(const char *start, const char *end, Table_t comments); -OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); -void add_line(Text_t *code, Text_t line, Text_t indent); diff --git a/src/formatter/types.c b/src/formatter/types.c index 282a299f..34128b0b 100644 --- a/src/formatter/types.c +++ b/src/formatter/types.c @@ -4,7 +4,7 @@ #include "../stdlib/datatypes.h" #include "../stdlib/optionals.h" #include "../stdlib/text.h" -#include "formatter.h" +#include "utils.h" OptionalText_t format_inline_type(type_ast_t *type, Table_t comments) { if (range_has_comment(type->start, type->end, comments)) return NONE_TEXT; diff --git a/src/formatter/utils.c b/src/formatter/utils.c new file mode 100644 index 00000000..472829d9 --- /dev/null +++ b/src/formatter/utils.c @@ -0,0 +1,112 @@ +// This file defines utility functions for autoformatting code + +#include +#include + +#include "../ast.h" +#include "../parse/context.h" +#include "../stdlib/datatypes.h" +#include "../stdlib/optionals.h" +#include "../stdlib/tables.h" +#include "../stdlib/text.h" + +const Text_t single_indent = Text(" "); + +void add_line(Text_t *code, Text_t line, Text_t indent) { + if (code->length == 0) { + *code = line; + } else { + if (line.length > 0) *code = Texts(*code, "\n", indent, line); + else *code = Texts(*code, "\n"); + } +} + +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) { + for (const char *p = *pos; p < end; p++) { + const char **comment_end = Table$get(comments, &p, parse_comments_info); + if (comment_end) { + *pos = *comment_end; + return Text$from_strn(p, (int64_t)(*comment_end - p)); + } + } + return NONE_TEXT; +} + +bool range_has_comment(const char *start, const char *end, Table_t comments) { + OptionalText_t comment = next_comment(comments, &start, end); + return (comment.length >= 0); +} + +CONSTFUNC bool should_have_blank_line(ast_t *ast) { + switch (ast->tag) { + case If: + case When: + case Repeat: + case While: + case For: + case Block: + case FunctionDef: + case StructDef: + case EnumDef: + case LangDef: + case ConvertDef: + case Extend: return true; + default: return false; + } +} + +Text_t indent_code(Text_t code) { + if (code.length <= 0) return code; + return Texts(single_indent, Text$replace(code, Text("\n"), Texts("\n", single_indent))); +} + +Text_t parenthesize(Text_t code, Text_t indent) { + if (Text$has(code, Text("\n"))) return Texts("(\n", indent, indent_code(code), "\n", indent, ")"); + else return Texts("(", code, ")"); +} + +CONSTFUNC ast_t *unwrap_block(ast_t *ast) { + if (ast == NULL) return NULL; + while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { + ast = Match(ast, Block)->statements->ast; + } + return ast; +} + +CONSTFUNC const char *binop_tomo_operator(ast_e tag) { + switch (tag) { + case Power: return "^"; + case PowerUpdate: return "^="; + case Concat: return "++"; + case ConcatUpdate: return "++="; + case Multiply: return "*"; + case MultiplyUpdate: return "*="; + case Divide: return "/"; + case DivideUpdate: return "/="; + case Mod: return "mod"; + case ModUpdate: return "mod="; + case Mod1: return "mod1"; + case Mod1Update: return "mod1="; + case Plus: return "+"; + case PlusUpdate: return "+="; + case Minus: return "-"; + case MinusUpdate: return "-="; + case LeftShift: return "<<"; + case LeftShiftUpdate: return "<<="; + case RightShift: return ">>"; + case RightShiftUpdate: return ">>="; + case And: return "and"; + case AndUpdate: return "and="; + case Or: return "or"; + case OrUpdate: return "or="; + case Xor: return "xor"; + case XorUpdate: return "xor="; + case Equals: return "=="; + case NotEquals: return "!="; + case LessThan: return "<"; + case LessThanOrEquals: return "<="; + case GreaterThan: return ">"; + case GreaterThanOrEquals: return ">="; + default: return NULL; + } +} diff --git a/src/formatter/utils.h b/src/formatter/utils.h new file mode 100644 index 00000000..a8def627 --- /dev/null +++ b/src/formatter/utils.h @@ -0,0 +1,28 @@ +// This file defines utility functions for autoformatting code + +#pragma once + +#include + +#include "../ast.h" +#include "../stdlib/datatypes.h" + +#define MAX_WIDTH 100 + +#define must(expr) \ + ({ \ + OptionalText_t _expr = expr; \ + if (_expr.length < 0) return NONE_TEXT; \ + (Text_t) _expr; \ + }) + +extern const Text_t single_indent; + +void add_line(Text_t *code, Text_t line, Text_t indent); +OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); +bool range_has_comment(const char *start, const char *end, Table_t comments); +CONSTFUNC bool should_have_blank_line(ast_t *ast); +Text_t indent_code(Text_t code); +Text_t parenthesize(Text_t code, Text_t indent); +CONSTFUNC ast_t *unwrap_block(ast_t *ast); +CONSTFUNC const char *binop_tomo_operator(ast_e tag); -- cgit v1.2.3 From f192834a6405d9a568782b637943588c6af7a8de Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 00:32:13 -0400 Subject: Add more formatting --- src/formatter/args.h | 2 - src/formatter/formatter.c | 376 ++++++++++++++++++++++++++++++---------------- 2 files changed, 250 insertions(+), 128 deletions(-) (limited to 'src') diff --git a/src/formatter/args.h b/src/formatter/args.h index 815069a4..42722f32 100644 --- a/src/formatter/args.h +++ b/src/formatter/args.h @@ -11,5 +11,3 @@ OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments); Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent); OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments); Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent); -OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments); -Text_t format_tags(tag_ast_t *tags, Table_t comments, Text_t indent); diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index e4c92542..b0fad38a 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -20,28 +20,33 @@ #include "types.h" #include "utils.h" +#define fmt_inline(...) must(format_inline_code(__VA_ARGS__)) +#define fmt(...) format_code(__VA_ARGS__) + Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { if (unwrap_block(namespace) == NULL) return EMPTY_TEXT; - return Texts("\n", indent, single_indent, format_code(namespace, comments, Texts(indent, single_indent))); + return Texts("\n", indent, single_indent, fmt(namespace, comments, Texts(indent, single_indent))); } OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { - case Unknown: fail("Invalid AST"); - case Block: { + /*inline*/ case Unknown: + fail("Invalid AST"); + /*inline*/ case Block: { ast_list_t *statements = Match(ast, Block)->statements; if (statements == NULL) return Text("pass"); - else if (statements->next == NULL) return format_inline_code(statements->ast, comments); + else if (statements->next == NULL) return fmt_inline(statements->ast, comments); else return NONE_TEXT; } - case StructDef: - case EnumDef: - case LangDef: - case Extend: - case FunctionDef: - case DocTest: return NONE_TEXT; - case If: { + /*inline*/ case StructDef: + /*inline*/ case EnumDef: + /*inline*/ case LangDef: + /*inline*/ case Extend: + /*inline*/ case FunctionDef: + /*inline*/ case DocTest: + return NONE_TEXT; + /*inline*/ case If: { DeclareMatch(if_, ast, If); if (if_->else_body == NULL && if_->condition->tag != Declare) { @@ -49,81 +54,122 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { switch (stmt->tag) { case Return: case Skip: - case Stop: - return Texts(must(format_inline_code(stmt, comments)), " if ", - must(format_inline_code(if_->condition, comments))); + case Stop: return Texts(fmt_inline(stmt, comments), " if ", fmt_inline(if_->condition, comments)); default: break; } } - Text_t code = Texts("if ", must(format_inline_code(if_->condition, comments)), " then ", - must(format_inline_code(if_->body, comments))); - if (if_->else_body) code = Texts(code, " else ", must(format_inline_code(if_->else_body, comments))); + Text_t code = Texts("if ", fmt_inline(if_->condition, comments), " then ", fmt_inline(if_->body, comments)); + if (if_->else_body) code = Texts(code, " else ", fmt_inline(if_->else_body, comments)); return code; } - case Repeat: return Texts("repeat ", must(format_inline_code(Match(ast, Repeat)->body, comments))); - case While: { + /*inline*/ case Repeat: + return Texts("repeat ", fmt_inline(Match(ast, Repeat)->body, comments)); + /*inline*/ case While: { DeclareMatch(loop, ast, While); - return Texts("while ", must(format_inline_code(loop->condition, comments)), " do ", - must(format_inline_code(loop->body, comments))); + return Texts("while ", fmt_inline(loop->condition, comments), " do ", fmt_inline(loop->body, comments)); + } + /*inline*/ case For: { + DeclareMatch(loop, ast, For); + Text_t code = Text("for "); + for (ast_list_t *var = loop->vars; var; var = var->next) { + code = Texts(code, fmt_inline(var->ast, comments)); + if (var->next) code = Texts(code, ", "); + } + code = Texts(code, " in ", fmt_inline(loop->iter, comments), " do ", fmt_inline(loop->body, comments)); + if (loop->empty) code = Texts(code, " else ", fmt_inline(loop->empty, comments)); + return code; } - case List: - case Set: { + /*inline*/ case Comprehension: { + DeclareMatch(comp, ast, Comprehension); + Text_t code = Texts(fmt_inline(comp->expr, comments), " for "); + for (ast_list_t *var = comp->vars; var; var = var->next) { + code = Texts(code, fmt_inline(var->ast, comments)); + if (var->next) code = Texts(code, ", "); + } + code = Texts(code, " in ", fmt_inline(comp->iter, comments)); + if (comp->filter) code = Texts(code, " if ", fmt_inline(comp->filter, comments)); + return code; + } + /*inline*/ case List: + /*inline*/ 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))); + code = Texts(code, fmt_inline(item->ast, comments)); if (item->next) code = Texts(code, ", "); } return ast->tag == List ? Texts("[", code, "]") : Texts("|", code, "|"); } - case Declare: { + /*inline*/ case Table: { + DeclareMatch(table, ast, Table); + Text_t code = EMPTY_TEXT; + for (ast_list_t *entry = table->entries; entry; entry = entry->next) { + code = Texts(code, fmt_inline(entry->ast, comments)); + if (entry->next) code = Texts(code, ", "); + } + if (table->fallback) code = Texts(code, "; fallback=", fmt_inline(table->fallback, comments)); + if (table->default_value) code = Texts(code, "; default=", fmt_inline(table->default_value, comments)); + return Texts("{", code, "}"); + } + /*inline*/ case TableEntry: { + DeclareMatch(entry, ast, TableEntry); + return Texts(fmt_inline(entry->key, comments), "=", fmt_inline(entry->value, comments)); + } + /*inline*/ case Declare: { DeclareMatch(decl, ast, Declare); - Text_t code = must(format_inline_code(decl->var, comments)); + Text_t code = fmt_inline(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))); + if (decl->value) code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt_inline(decl->value, comments)); return code; } - case Assign: { + /*inline*/ case Assign: { DeclareMatch(assign, ast, Assign); Text_t code = EMPTY_TEXT; for (ast_list_t *target = assign->targets; target; target = target->next) { - code = Texts(code, must(format_inline_code(target->ast, comments))); + code = Texts(code, fmt_inline(target->ast, comments)); if (target->next) code = Texts(code, ", "); } code = Texts(code, " = "); for (ast_list_t *value = assign->values; value; value = value->next) { - code = Texts(code, must(format_inline_code(value->ast, comments))); + code = Texts(code, fmt_inline(value->ast, comments)); if (value->next) code = Texts(code, ", "); } return code; } - case Return: { + /*inline*/ case Pass: + return Text("pass"); + /*inline*/ case Return: { ast_t *value = Match(ast, Return)->value; - return value ? Texts("return ", must(format_inline_code(value, comments))) : Text("return"); + return value ? Texts("return ", fmt_inline(value, comments)) : Text("return"); + } + /*inline*/ case HeapAllocate: { + ast_t *val = Match(ast, HeapAllocate)->value; + return Texts("@", fmt_inline(val, comments)); + } + /*inline*/ case StackReference: { + ast_t *val = Match(ast, StackReference)->value; + return Texts("&", fmt_inline(val, comments)); } - case Optional: { + /*inline*/ case Optional: { ast_t *val = Match(ast, Optional)->value; - return Texts(must(format_inline_code(val, comments)), "?"); + return Texts(fmt_inline(val, comments), "?"); } - case NonOptional: { + /*inline*/ case NonOptional: { ast_t *val = Match(ast, NonOptional)->value; - return Texts(must(format_inline_code(val, comments)), "!"); + return Texts(fmt_inline(val, comments), "!"); } - case FieldAccess: { + /*inline*/ case FieldAccess: { DeclareMatch(access, ast, FieldAccess); - return Texts(must(format_inline_code(access->fielded, comments)), ".", Text$from_str(access->field)); + return Texts(fmt_inline(access->fielded, comments), ".", Text$from_str(access->field)); } - case Index: { + /*inline*/ 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)), "[]"); + return Texts(fmt_inline(index->indexed, comments), "[", fmt_inline(index->index, comments), "]"); + else return Texts(fmt_inline(index->indexed, comments), "[]"); } - case TextJoin: { + /*inline*/ 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)); @@ -134,42 +180,44 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { 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)), ")"); + code = Texts(code, "$(", fmt_inline(chunk->ast, comments), ")"); } } return Texts(code, quote); } - case TextLiteral: { - fail("Something went wrong, we shouldn't be formatting text literals directly"); - } - case Stop: { + /*inline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } + /*inline*/ case Stop: { const char *target = Match(ast, Stop)->target; return target ? Texts("stop ", Text$from_str(target)) : Text("stop"); } - case Skip: { + /*inline*/ 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: { + /*inline*/ case None: + /*inline*/ case Bool: + /*inline*/ case Int: + /*inline*/ case Num: + /*inline*/ 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: { + /*inline*/ case FunctionCall: { DeclareMatch(call, ast, FunctionCall); - return Texts(must(format_inline_code(call->fn, comments)), "(", must(format_inline_args(call->args, comments)), - ")"); + return Texts(fmt_inline(call->fn, comments), "(", must(format_inline_args(call->args, comments)), ")"); + } + /*inline*/ case MethodCall: { + DeclareMatch(call, ast, MethodCall); + return Texts(fmt_inline(call->self, comments), ".", Text$from_str(call->name), "(", + must(format_inline_args(call->args, comments)), ")"); } - case BINOP_CASES: { + /*inline*/ case BINOP_CASES: { binary_operands_t operands = BINARY_OPERANDS(ast); const char *op = binop_tomo_operator(ast->tag); - Text_t lhs = must(format_inline_code(operands.lhs, comments)); - Text_t rhs = must(format_inline_code(operands.rhs, comments)); + Text_t lhs = fmt_inline(operands.lhs, comments); + Text_t rhs = fmt_inline(operands.rhs, comments); if (is_update_assignment(ast)) { return Texts(lhs, " ", Text$from_str(op), " ", rhs); @@ -194,8 +242,9 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH); switch (ast->tag) { - case Unknown: fail("Invalid AST"); - case Block: { + /*multiline*/ case Unknown: + fail("Invalid AST"); + /*multiline*/ case Block: { Text_t code = EMPTY_TEXT; bool gap_before_comment = false; const char *comment_pos = ast->start; @@ -211,7 +260,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent); } - add_line(&code, format_code(stmt->ast, comments, indent), indent); + add_line(&code, fmt(stmt->ast, comments, indent), indent); comment_pos = stmt->ast->end; if (should_have_blank_line(stmt->ast) && stmt->next && !should_have_blank_line(stmt->next->ast)) @@ -228,27 +277,59 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { } return code; } - case If: { + /*multiline*/ case If: { DeclareMatch(if_, ast, If); if (inlined_fits && if_->else_body == NULL) return inlined; - Text_t code = Texts("if ", format_code(if_->condition, comments, indent), "\n", indent, single_indent, - format_code(if_->body, comments, Texts(indent, single_indent))); + Text_t code = Texts("if ", fmt(if_->condition, comments, indent), "\n", indent, single_indent, + fmt(if_->body, comments, Texts(indent, single_indent))); if (if_->else_body) code = Texts(code, "\n", indent, "else \n", indent, single_indent, - format_code(if_->else_body, comments, Texts(indent, single_indent))); + fmt(if_->else_body, comments, Texts(indent, single_indent))); return code; } - case Repeat: { + /*multiline*/ case Repeat: { return Texts("repeat\n", indent, single_indent, - format_code(Match(ast, Repeat)->body, comments, Texts(indent, single_indent))); + fmt(Match(ast, Repeat)->body, comments, Texts(indent, single_indent))); } - case While: { + /*multiline*/ case While: { DeclareMatch(loop, ast, While); - return Texts("while ", format_code(loop->condition, comments, indent), "\n", indent, single_indent, - format_code(loop->body, comments, Texts(indent, single_indent))); + return Texts("while ", fmt(loop->condition, comments, indent), "\n", indent, single_indent, + fmt(loop->body, comments, Texts(indent, single_indent))); + } + /*multiline*/ case For: { + DeclareMatch(loop, ast, For); + Text_t code = Text("for "); + for (ast_list_t *var = loop->vars; var; var = var->next) { + code = Texts(code, fmt(var->ast, comments, indent)); + if (var->next) code = Texts(code, ", "); + } + code = Texts(code, " in ", fmt(loop->iter, comments, indent), format_namespace(loop->body, comments, indent)); + if (loop->empty) code = Texts(code, "\n", indent, "else", format_namespace(loop->empty, comments, indent)); + return code; + } + /*multiline*/ case Comprehension: { + if (inlined_fits) return inlined; + DeclareMatch(comp, ast, Comprehension); + Text_t code = Texts("(", fmt(comp->expr, comments, indent)); + if (code.length >= MAX_WIDTH) code = Texts(code, "\n", indent, "for "); + else code = Texts(code, " for "); + + for (ast_list_t *var = comp->vars; var; var = var->next) { + code = Texts(code, fmt(var->ast, comments, indent)); + if (var->next) code = Texts(code, ", "); + } + + code = Texts(code, " in ", fmt(comp->iter, comments, indent)); + + if (comp->filter) { + if (code.length >= MAX_WIDTH) code = Texts(code, "\n", indent, "if "); + else code = Texts(code, " if "); + code = Texts(code, fmt(comp->filter, comments, indent)); + } + return code; } - case FunctionDef: { + /*multiline*/ case FunctionDef: { DeclareMatch(func, ast, FunctionDef); // ast_t *name; // arg_ast_t *args; @@ -256,17 +337,15 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { // ast_t *body; // ast_t *cache; // bool is_inline; - Text_t code = - Texts("func ", format_code(func->name, comments, indent), "(", format_args(func->args, comments, indent)); + Text_t code = Texts("func ", fmt(func->name, comments, indent), "(", format_args(func->args, comments, indent)); if (func->ret_type) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type, comments, indent)); - if (func->cache) code = Texts(code, "; cache=", format_code(func->cache, comments, indent)); + if (func->cache) code = Texts(code, "; cache=", fmt(func->cache, comments, indent)); if (func->is_inline) code = Texts(code, "; inline"); - code = - Texts(code, ")\n", indent, single_indent, format_code(func->body, comments, Texts(indent, single_indent))); + code = Texts(code, ")\n", indent, single_indent, fmt(func->body, comments, Texts(indent, single_indent))); return Texts(code); } - case StructDef: { + /*multiline*/ case StructDef: { DeclareMatch(def, ast, StructDef); Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); if (def->secret) code = Texts(code, "; secret"); @@ -274,21 +353,21 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { if (def->opaque) code = Texts(code, "; opaque"); return Texts(code, ")", format_namespace(def->namespace, comments, indent)); } - case EnumDef: { + /*multiline*/ case EnumDef: { DeclareMatch(def, ast, EnumDef); Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent)); return Texts(code, ")", format_namespace(def->namespace, comments, indent)); } - case LangDef: { + /*multiline*/ case LangDef: { DeclareMatch(def, ast, LangDef); return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent)); } - case Extend: { + /*multiline*/ case Extend: { DeclareMatch(extend, ast, Extend); return Texts("lang ", Text$from_str(extend->name), format_namespace(extend->body, comments, indent)); } - case List: - case Set: { + /*multiline*/ case List: + /*multiline*/ 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; @@ -298,7 +377,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { (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)), ","), + add_line(&code, Texts(fmt(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;) { @@ -307,54 +386,95 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { return ast->tag == List ? Texts("[\n", indent, single_indent, code, "\n", indent, "]") : Texts("|\n", indent, single_indent, code, "\n", indent, "|"); } - case Declare: { + /*multiline*/ case Table: { + if (inlined_fits) return inlined; + DeclareMatch(table, ast, Table); + Text_t code = EMPTY_TEXT; + const char *comment_pos = ast->start; + for (ast_list_t *entry = table->entries; entry; entry = entry->next) { + for (OptionalText_t comment; + (comment = next_comment(comments, &comment_pos, entry->ast->start)).length > 0;) { + add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent)); + } + add_line(&code, Texts(fmt(entry->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)); + } + + if (table->fallback) + code = Texts(code, ";\n", indent, single_indent, "fallback=", fmt(table->fallback, comments, indent)); + + if (table->default_value) + code = Texts(code, ";\n", indent, single_indent, "default=", fmt(table->default_value, comments, indent)); + + return Texts("{\n", indent, single_indent, code, "\n", indent, "}"); + } + /*multiline*/ case TableEntry: { + if (inlined_fits) return inlined; + DeclareMatch(entry, ast, TableEntry); + return Texts(fmt(entry->key, comments, indent), "=", fmt(entry->value, comments, indent)); + } + /*multiline*/ case Declare: { DeclareMatch(decl, ast, Declare); - Text_t code = format_code(decl->var, comments, indent); + Text_t code = fmt(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)); + code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt(decl->value, comments, indent)); return code; } - case Assign: { + /*multiline*/ case Assign: { DeclareMatch(assign, ast, Assign); Text_t code = EMPTY_TEXT; for (ast_list_t *target = assign->targets; target; target = target->next) { - code = Texts(code, format_code(target->ast, comments, indent)); + code = Texts(code, fmt(target->ast, comments, indent)); if (target->next) code = Texts(code, ", "); } code = Texts(code, " = "); for (ast_list_t *value = assign->values; value; value = value->next) { - code = Texts(code, format_code(value->ast, comments, indent)); + code = Texts(code, fmt(value->ast, comments, indent)); if (value->next) code = Texts(code, ", "); } return code; } - case Return: { + /*multiline*/ case Pass: + return Text("pass"); + /*multiline*/ case Return: { ast_t *value = Match(ast, Return)->value; - return value ? Texts("return ", format_code(value, comments, indent)) : Text("return"); + return value ? Texts("return ", fmt(value, comments, indent)) : Text("return"); + } + /*multiline*/ case HeapAllocate: { + if (inlined_fits) return inlined; + ast_t *val = Match(ast, HeapAllocate)->value; + return Texts("@(", fmt(val, comments, indent), ")"); } - case Optional: { + /*multiline*/ case StackReference: { + if (inlined_fits) return inlined; + ast_t *val = Match(ast, StackReference)->value; + return Texts("&(", fmt(val, comments, indent), ")"); + } + /*multiline*/ case Optional: { if (inlined_fits) return inlined; ast_t *val = Match(ast, Optional)->value; - return Texts("(", format_code(val, comments, indent), ")?"); + return Texts("(", fmt(val, comments, indent), ")?"); } - case NonOptional: { + /*multiline*/ case NonOptional: { if (inlined_fits) return inlined; ast_t *val = Match(ast, NonOptional)->value; - return Texts("(", format_code(val, comments, indent), ")!"); + return Texts("(", fmt(val, comments, indent), ")!"); } - case FieldAccess: { + /*multiline*/ case FieldAccess: { DeclareMatch(access, ast, FieldAccess); - return Texts(format_code(access->fielded, comments, indent), ".", Text$from_str(access->field)); + return Texts(fmt(access->fielded, comments, indent), ".", Text$from_str(access->field)); } - case Index: { + /*multiline*/ 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), "[]"); + return Texts(fmt(index->indexed, comments, indent), "[", fmt(index->index, comments, indent), "]"); + else return Texts(fmt(index->indexed, comments, indent), "[]"); } - case TextJoin: { + /*multiline*/ 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)); @@ -373,7 +493,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { current_line = *(Text_t *)(lines.data + i * lines.stride); } } else { - code = Texts(code, "$(", must(format_inline_code(chunk->ast, comments)), ")"); + code = Texts(code, "$(", fmt_inline(chunk->ast, comments), ")"); } } add_line(&code, current_line, Texts(indent, single_indent)); @@ -381,42 +501,46 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { 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: { + /*multiline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } + /*multiline*/ case Stop: + /*multiline*/ case Skip: + /*multiline*/ case None: + /*multiline*/ case Bool: + /*multiline*/ case Int: + /*multiline*/ case Num: + /*multiline*/ case Var: { assert(inlined.length >= 0); return inlined; } - case FunctionCall: { + /*multiline*/ case FunctionCall: { if (inlined_fits) return inlined; DeclareMatch(call, ast, FunctionCall); - return Texts(format_code(call->fn, comments, indent), "(\n", indent, single_indent, + return Texts(fmt(call->fn, comments, indent), "(\n", indent, single_indent, + format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); + } + /*multiline*/ case MethodCall: { + if (inlined_fits) return inlined; + DeclareMatch(call, ast, MethodCall); + return Texts(fmt(call->self, comments, indent), ".", Text$from_str(call->name), "(\n", indent, single_indent, format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); } - case DocTest: { + /*multiline*/ case DocTest: { DeclareMatch(test, ast, DocTest); - Text_t expr = format_code(test->expr, comments, indent); + Text_t expr = fmt(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); + Text_t expected = fmt(test->expected, comments, indent); code = Texts(code, "\n", indent, "= ", Text$replace(expected, Texts("\n", indent), Texts("\n", indent, ".. "))); } return code; } - case BINOP_CASES: { + /*multiline*/ case BINOP_CASES: { if (inlined_fits) return inlined; binary_operands_t operands = BINARY_OPERANDS(ast); const char *op = binop_tomo_operator(ast->tag); - Text_t lhs = format_code(operands.lhs, comments, indent); - Text_t rhs = format_code(operands.rhs, comments, indent); + Text_t lhs = fmt(operands.lhs, comments, indent); + Text_t rhs = fmt(operands.rhs, comments, indent); if (is_update_assignment(ast)) { return Texts(lhs, " ", Text$from_str(op), " ", rhs); @@ -469,7 +593,7 @@ Text_t format_file(const char *path) { for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); } - code = Texts(code, format_code(ast, ctx.comments, EMPTY_TEXT)); + code = Texts(code, fmt(ast, ctx.comments, EMPTY_TEXT)); for (OptionalText_t comment; (comment = next_comment(ctx.comments, &fmt_pos, ast->start)).length > 0;) { code = Texts(code, Text$trim(comment, Text(" \t\r\n"), false, true), "\n"); } -- cgit v1.2.3 From b5dee4f7ed1b8181df22a9824b7026a125ebbc53 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 00:45:56 -0400 Subject: Actual type formatting --- src/formatter/args.c | 4 ++-- src/formatter/formatter.c | 7 +++---- src/formatter/types.c | 51 ++++++++++++++++++++++++++++++----------------- src/formatter/types.h | 3 +-- 4 files changed, 39 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/formatter/args.c b/src/formatter/args.c index 2bd115cd..6bd07b28 100644 --- a/src/formatter/args.c +++ b/src/formatter/args.c @@ -12,7 +12,7 @@ OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { if (range_has_comment(arg->start, arg->end, comments)) return NONE_TEXT; if (arg->name == NULL && arg->value) return must(format_inline_code(arg->value, comments)); Text_t code = Text$from_str(arg->name); - if (arg->type) code = Texts(code, ":", must(format_inline_type(arg->type, comments))); + if (arg->type) code = Texts(code, ":", must(format_type(arg->type))); if (arg->value) code = Texts(code, " = ", must(format_inline_code(arg->value, comments))); return code; } @@ -22,7 +22,7 @@ Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { if (inline_arg.length >= 0 && inline_arg.length <= MAX_WIDTH) return inline_arg; if (arg->name == NULL && arg->value) return format_code(arg->value, comments, indent); Text_t code = Text$from_str(arg->name); - if (arg->type) code = Texts(code, ":", format_type(arg->type, comments, indent)); + if (arg->type) code = Texts(code, ":", format_type(arg->type)); if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); return code; } diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index b0fad38a..f0a1c125 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -119,7 +119,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { /*inline*/ case Declare: { DeclareMatch(decl, ast, Declare); Text_t code = fmt_inline(decl->var, comments); - if (decl->type) code = Texts(code, " : ", must(format_inline_type(decl->type, comments))); + if (decl->type) code = Texts(code, " : ", format_type(decl->type)); if (decl->value) code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt_inline(decl->value, comments)); return code; } @@ -338,8 +338,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { // ast_t *cache; // bool is_inline; Text_t code = Texts("func ", fmt(func->name, comments, indent), "(", format_args(func->args, comments, indent)); - if (func->ret_type) - code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type, comments, indent)); + if (func->ret_type) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type)); if (func->cache) code = Texts(code, "; cache=", fmt(func->cache, comments, indent)); if (func->is_inline) code = Texts(code, "; inline"); code = Texts(code, ")\n", indent, single_indent, fmt(func->body, comments, Texts(indent, single_indent))); @@ -419,7 +418,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case Declare: { DeclareMatch(decl, ast, Declare); Text_t code = fmt(decl->var, comments, indent); - if (decl->type) code = Texts(code, " : ", format_type(decl->type, comments, indent)); + if (decl->type) code = Texts(code, " : ", format_type(decl->type)); if (decl->value) code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt(decl->value, comments, indent)); return code; diff --git a/src/formatter/types.c b/src/formatter/types.c index 34128b0b..24171984 100644 --- a/src/formatter/types.c +++ b/src/formatter/types.c @@ -2,29 +2,44 @@ #include "../ast.h" #include "../stdlib/datatypes.h" -#include "../stdlib/optionals.h" +#include "../stdlib/stdlib.h" #include "../stdlib/text.h" -#include "utils.h" +#include "args.h" +#include "formatter.h" -OptionalText_t format_inline_type(type_ast_t *type, Table_t comments) { - if (range_has_comment(type->start, type->end, comments)) return NONE_TEXT; +Text_t format_type(type_ast_t *type) { switch (type->tag) { - default: { - Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); - if (Text$has(code, Text("\n"))) return NONE_TEXT; - return Text$replace(code, Text("\t"), single_indent); + case VarTypeAST: return Text$from_str(Match(type, VarTypeAST)->name); + case PointerTypeAST: { + DeclareMatch(ptr, type, PointerTypeAST); + return Texts(ptr->is_stack ? Text("&") : Text("@"), format_type(ptr->pointed)); } + case ListTypeAST: { + return Texts("[", format_type(Match(type, ListTypeAST)->item), "]"); } -} - -Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent) { - (void)comments, (void)indent; - switch (type->tag) { - default: { - OptionalText_t inline_type = format_inline_type(type, comments); - if (inline_type.length >= 0) return inline_type; - Text_t code = Text$from_strn(type->start, (int64_t)(type->end - type->start)); - return Text$replace(code, Text("\t"), single_indent); + case SetTypeAST: { + return Texts("|", format_type(Match(type, ListTypeAST)->item), "|"); + } + case TableTypeAST: { + DeclareMatch(table, type, TableTypeAST); + Text_t code = Texts("{", format_type(table->key), "=", format_type(table->value)); + if (table->default_value) { + OptionalText_t val = format_inline_code(table->default_value, (Table_t){}); + assert(val.length >= 0); + code = Texts(code, "; default=", val); + } + return Texts(code, "}"); + } + case FunctionTypeAST: { + DeclareMatch(func, type, FunctionTypeAST); + Text_t code = Texts("func(", format_inline_args(func->args, (Table_t){})); + if (func->ret) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret)); + return Texts(code, ")"); + } + case OptionalTypeAST: { + return Texts(format_type(Match(type, OptionalTypeAST)->type), "?"); } + case UnknownTypeAST: + default: fail("Invalid Type AST"); } } diff --git a/src/formatter/types.h b/src/formatter/types.h index 1f0698b3..2571f880 100644 --- a/src/formatter/types.h +++ b/src/formatter/types.h @@ -5,5 +5,4 @@ #include "../ast.h" #include "../stdlib/datatypes.h" -OptionalText_t format_inline_type(type_ast_t *type, Table_t comments); -Text_t format_type(type_ast_t *type, Table_t comments, Text_t indent); +Text_t format_type(type_ast_t *type); -- cgit v1.2.3 From 794cffd4da6694d06d1a7c6f77c157e58354ac25 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 00:46:31 -0400 Subject: Fix typo --- src/formatter/types.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/types.c b/src/formatter/types.c index 24171984..e52faf70 100644 --- a/src/formatter/types.c +++ b/src/formatter/types.c @@ -18,7 +18,7 @@ Text_t format_type(type_ast_t *type) { return Texts("[", format_type(Match(type, ListTypeAST)->item), "]"); } case SetTypeAST: { - return Texts("|", format_type(Match(type, ListTypeAST)->item), "|"); + return Texts("|", format_type(Match(type, SetTypeAST)->item), "|"); } case TableTypeAST: { DeclareMatch(table, type, TableTypeAST); -- cgit v1.2.3 From 8166e4c194b039f0a1e5c011286fb156a12c82c2 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 01:01:42 -0400 Subject: More formatting --- src/formatter/formatter.c | 52 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index f0a1c125..b62d3fbe 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -46,6 +46,14 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { /*inline*/ case FunctionDef: /*inline*/ case DocTest: return NONE_TEXT; + /*inline*/ case Lambda: { + DeclareMatch(lambda, ast, Lambda); + Text_t code = Texts("func(", format_inline_args(lambda->args, comments)); + if (lambda->ret_type) + code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type)); + code = Texts(code, ") ", fmt_inline(lambda->body, comments)); + return Texts(code); + } /*inline*/ case If: { DeclareMatch(if_, ast, If); @@ -63,6 +71,20 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (if_->else_body) code = Texts(code, " else ", fmt_inline(if_->else_body, comments)); return code; } + /*inline*/ case When: { + DeclareMatch(when, ast, When); + Text_t code = Texts("when ", fmt_inline(when->subject, comments)); + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + code = Texts(code, " is ", fmt_inline(clause->pattern, comments)); + while (clause->next && clause->next->body == clause->body) { + clause = clause->next; + code = Texts(code, ", ", fmt_inline(clause->pattern, comments)); + } + code = Texts(code, " then ", fmt_inline(clause->body, comments)); + } + if (when->else_body) code = Texts(code, " else ", fmt_inline(when->else_body, comments)); + return code; + } /*inline*/ case Repeat: return Texts("repeat ", fmt_inline(Match(ast, Repeat)->body, comments)); /*inline*/ case While: { @@ -288,6 +310,21 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { fmt(if_->else_body, comments, Texts(indent, single_indent))); return code; } + /*multiline*/ case When: { + DeclareMatch(when, ast, When); + Text_t code = Texts("when ", fmt(when->subject, comments, indent)); + for (when_clause_t *clause = when->clauses; clause; clause = clause->next) { + code = Texts(code, "\n", indent, "is ", fmt(clause->pattern, comments, indent)); + while (clause->next && clause->next->body == clause->body) { + clause = clause->next; + code = Texts(code, ", ", fmt(clause->pattern, comments, indent)); + } + code = Texts(code, format_namespace(clause->body, comments, indent)); + } + if (when->else_body) + code = Texts(code, "\n", indent, "else", format_namespace(when->else_body, comments, indent)); + return code; + } /*multiline*/ case Repeat: { return Texts("repeat\n", indent, single_indent, fmt(Match(ast, Repeat)->body, comments, Texts(indent, single_indent))); @@ -331,12 +368,6 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { } /*multiline*/ case FunctionDef: { DeclareMatch(func, ast, FunctionDef); - // ast_t *name; - // arg_ast_t *args; - // type_ast_t *ret_type; - // ast_t *body; - // ast_t *cache; - // bool is_inline; Text_t code = Texts("func ", fmt(func->name, comments, indent), "(", format_args(func->args, comments, indent)); if (func->ret_type) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type)); if (func->cache) code = Texts(code, "; cache=", fmt(func->cache, comments, indent)); @@ -344,6 +375,15 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { code = Texts(code, ")\n", indent, single_indent, fmt(func->body, comments, Texts(indent, single_indent))); return Texts(code); } + /*multiline*/ case Lambda: { + if (inlined_fits) return inlined; + DeclareMatch(lambda, ast, Lambda); + Text_t code = Texts("func(", format_args(lambda->args, comments, indent)); + if (lambda->ret_type) + code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type)); + code = Texts(code, ")\n", indent, single_indent, fmt(lambda->body, comments, Texts(indent, single_indent))); + return Texts(code); + } /*multiline*/ case StructDef: { DeclareMatch(def, ast, StructDef); Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); -- cgit v1.2.3 From fc126b1103a8b6b6cbde7cf10ee6e25a3279cd9f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 01:05:25 -0400 Subject: Fix for 'else if' --- src/formatter/formatter.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index b62d3fbe..7c27d053 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -305,9 +305,14 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { Text_t code = Texts("if ", fmt(if_->condition, comments, indent), "\n", indent, single_indent, fmt(if_->body, comments, Texts(indent, single_indent))); - if (if_->else_body) - code = Texts(code, "\n", indent, "else \n", indent, single_indent, - fmt(if_->else_body, comments, Texts(indent, single_indent))); + if (if_->else_body) { + if (if_->else_body->tag != If) { + code = Texts(code, "\n", indent, "else\n", indent, single_indent, + fmt(if_->else_body, comments, Texts(indent, single_indent))); + } else { + code = Texts(code, "\n", indent, "else ", fmt(if_->else_body, comments, indent)); + } + } return code; } /*multiline*/ case When: { -- cgit v1.2.3 From 180a48ed733bf0ee1e296e3afe5a9be2d1deebcf Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 02:36:59 -0400 Subject: Add missing cases --- src/ast.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ast.h b/src/ast.h index 30c413ca..bdccb704 100644 --- a/src/ast.h +++ b/src/ast.h @@ -163,7 +163,12 @@ struct type_ast_s { case MinusUpdate: \ case ConcatUpdate: \ case LeftShiftUpdate: \ - case UnsignedLeftShiftUpdate + case UnsignedLeftShiftUpdate: \ + case RightShiftUpdate: \ + case UnsignedRightShiftUpdate: \ + case AndUpdate: \ + case OrUpdate: \ + case XorUpdate #define UPDATE_CASES \ PowerUpdate: \ case MultiplyUpdate: \ -- cgit v1.2.3 From 5b06702dde3f53510a3d64a8156a349914afa605 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 02:54:56 -0400 Subject: Fixes for unops --- src/formatter/formatter.c | 58 +++++++++++++++++++++++++++++++---------------- src/formatter/utils.c | 20 ++++++++++++++++ src/formatter/utils.h | 2 ++ 3 files changed, 61 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 7c27d053..96bdf632 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -165,31 +165,39 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { ast_t *value = Match(ast, Return)->value; return value ? Texts("return ", fmt_inline(value, comments)) : Text("return"); } + /*inline*/ case Not: { + ast_t *val = Match(ast, Not)->value; + return Texts("not ", must(termify_inline(val, comments))); + } + /*inline*/ case Negative: { + ast_t *val = Match(ast, Negative)->value; + return Texts("-", must(termify_inline(val, comments))); + } /*inline*/ case HeapAllocate: { ast_t *val = Match(ast, HeapAllocate)->value; - return Texts("@", fmt_inline(val, comments)); + return Texts("@", must(termify_inline(val, comments))); } /*inline*/ case StackReference: { ast_t *val = Match(ast, StackReference)->value; - return Texts("&", fmt_inline(val, comments)); + return Texts("&", must(termify_inline(val, comments))); } /*inline*/ case Optional: { ast_t *val = Match(ast, Optional)->value; - return Texts(fmt_inline(val, comments), "?"); + return Texts(must(termify_inline(val, comments)), "?"); } /*inline*/ case NonOptional: { ast_t *val = Match(ast, NonOptional)->value; - return Texts(fmt_inline(val, comments), "!"); + return Texts(must(termify_inline(val, comments)), "!"); } /*inline*/ case FieldAccess: { DeclareMatch(access, ast, FieldAccess); - return Texts(fmt_inline(access->fielded, comments), ".", Text$from_str(access->field)); + return Texts(must(termify_inline(access->fielded, comments)), ".", Text$from_str(access->field)); } /*inline*/ case Index: { DeclareMatch(index, ast, Index); - if (index->index) - return Texts(fmt_inline(index->indexed, comments), "[", fmt_inline(index->index, comments), "]"); - else return Texts(fmt_inline(index->indexed, comments), "[]"); + Text_t indexed = must(termify_inline(index->indexed, comments)); + if (index->index) return Texts(indexed, "[", fmt_inline(index->index, comments), "]"); + else return Texts(indexed, "[]"); } /*inline*/ case TextJoin: { // TODO: choose quotation mark more smartly @@ -231,8 +239,10 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } /*inline*/ case MethodCall: { DeclareMatch(call, ast, MethodCall); - return Texts(fmt_inline(call->self, comments), ".", Text$from_str(call->name), "(", - must(format_inline_args(call->args, comments)), ")"); + Text_t self = fmt_inline(call->self, comments); + if (is_binary_operation(call->self) || call->self->tag == Negative || call->self->tag == Not) + self = parenthesize(self, EMPTY_TEXT); + return Texts(self, ".", Text$from_str(call->name), "(", must(format_inline_args(call->args, comments)), ")"); } /*inline*/ case BINOP_CASES: { binary_operands_t operands = BINARY_OPERANDS(ast); @@ -488,35 +498,45 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { ast_t *value = Match(ast, Return)->value; return value ? Texts("return ", fmt(value, comments, indent)) : Text("return"); } + /*inline*/ case Not: { + ast_t *val = Match(ast, Not)->value; + if (is_binary_operation(val)) return Texts("not ", termify(val, comments, indent)); + else return Texts("not ", fmt(val, comments, indent)); + } + /*inline*/ case Negative: { + ast_t *val = Match(ast, Negative)->value; + if (is_binary_operation(val)) return Texts("-", termify(val, comments, indent)); + else return Texts("-", fmt(val, comments, indent)); + } /*multiline*/ case HeapAllocate: { if (inlined_fits) return inlined; ast_t *val = Match(ast, HeapAllocate)->value; - return Texts("@(", fmt(val, comments, indent), ")"); + return Texts("@", termify(val, comments, indent), ""); } /*multiline*/ case StackReference: { if (inlined_fits) return inlined; ast_t *val = Match(ast, StackReference)->value; - return Texts("&(", fmt(val, comments, indent), ")"); + return Texts("&(", termify(val, comments, indent), ")"); } /*multiline*/ case Optional: { if (inlined_fits) return inlined; ast_t *val = Match(ast, Optional)->value; - return Texts("(", fmt(val, comments, indent), ")?"); + return Texts(termify(val, comments, indent), "?"); } /*multiline*/ case NonOptional: { if (inlined_fits) return inlined; ast_t *val = Match(ast, NonOptional)->value; - return Texts("(", fmt(val, comments, indent), ")!"); + return Texts(termify(val, comments, indent), "!"); } /*multiline*/ case FieldAccess: { DeclareMatch(access, ast, FieldAccess); - return Texts(fmt(access->fielded, comments, indent), ".", Text$from_str(access->field)); + return Texts(termify(access->fielded, comments, indent), ".", Text$from_str(access->field)); } /*multiline*/ case Index: { DeclareMatch(index, ast, Index); if (index->index) - return Texts(fmt(index->indexed, comments, indent), "[", fmt(index->index, comments, indent), "]"); - else return Texts(fmt(index->indexed, comments, indent), "[]"); + return Texts(termify(index->indexed, comments, indent), "[", fmt(index->index, comments, indent), "]"); + else return Texts(termify(index->indexed, comments, indent), "[]"); } /*multiline*/ case TextJoin: { if (inlined_fits) return inlined; @@ -565,8 +585,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case MethodCall: { if (inlined_fits) return inlined; DeclareMatch(call, ast, MethodCall); - return Texts(fmt(call->self, comments, indent), ".", Text$from_str(call->name), "(\n", indent, single_indent, - format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); + return Texts(termify(call->self, comments, indent), ".", Text$from_str(call->name), "(\n", indent, + single_indent, format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); } /*multiline*/ case DocTest: { DeclareMatch(test, ast, DocTest); diff --git a/src/formatter/utils.c b/src/formatter/utils.c index 472829d9..37751377 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -9,6 +9,7 @@ #include "../stdlib/optionals.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" +#include "formatter.h" const Text_t single_indent = Text(" "); @@ -110,3 +111,22 @@ CONSTFUNC const char *binop_tomo_operator(ast_e tag) { default: return NULL; } } + +OptionalText_t termify_inline(ast_t *ast, Table_t comments) { + if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; + switch (ast->tag) { + case BINOP_CASES: + case Not: + case Negative: return parenthesize(format_inline_code(ast, comments), EMPTY_TEXT); + default: return format_inline_code(ast, comments); + } +} + +Text_t termify(ast_t *ast, Table_t comments, Text_t indent) { + switch (ast->tag) { + case BINOP_CASES: + case Not: + case Negative: return parenthesize(format_code(ast, comments, indent), indent); + default: return format_inline_code(ast, comments); + } +} diff --git a/src/formatter/utils.h b/src/formatter/utils.h index a8def627..df9c9b92 100644 --- a/src/formatter/utils.h +++ b/src/formatter/utils.h @@ -26,3 +26,5 @@ Text_t indent_code(Text_t code); Text_t parenthesize(Text_t code, Text_t indent); CONSTFUNC ast_t *unwrap_block(ast_t *ast); CONSTFUNC const char *binop_tomo_operator(ast_e tag); +OptionalText_t termify_inline(ast_t *ast, Table_t comments); +Text_t termify(ast_t *ast, Table_t comments, Text_t indent); -- cgit v1.2.3 From 4715487a4be9f03ad58cb3199e95573e262f6387 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 02:56:51 -0400 Subject: More termify fixes --- src/formatter/utils.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/formatter/utils.c b/src/formatter/utils.c index 37751377..a032ec2f 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -117,7 +117,9 @@ OptionalText_t termify_inline(ast_t *ast, Table_t comments) { switch (ast->tag) { case BINOP_CASES: case Not: - case Negative: return parenthesize(format_inline_code(ast, comments), EMPTY_TEXT); + case Negative: + case HeapAllocate: + case StackReference: return parenthesize(format_inline_code(ast, comments), EMPTY_TEXT); default: return format_inline_code(ast, comments); } } @@ -126,7 +128,9 @@ Text_t termify(ast_t *ast, Table_t comments, Text_t indent) { switch (ast->tag) { case BINOP_CASES: case Not: - case Negative: return parenthesize(format_code(ast, comments, indent), indent); + case Negative: + case HeapAllocate: + case StackReference: return parenthesize(format_code(ast, comments, indent), indent); default: return format_inline_code(ast, comments); } } -- cgit v1.2.3 From d25d5642392f93623d1eb4d11156d293fe6df546 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 14:39:11 -0400 Subject: Formatting for min/max and cleanup for ints/nums --- src/ast.c | 17 ++++++++--------- src/ast.h | 2 +- src/compile/expressions.c | 2 +- src/formatter/formatter.c | 37 +++++++++++++++++++++++++++++++------ src/formatter/utils.h | 1 + src/parse/binops.c | 2 +- 6 files changed, 43 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 314ff998..3b58b919 100644 --- a/src/ast.c +++ b/src/ast.c @@ -5,6 +5,7 @@ #include "ast.h" #include "stdlib/datatypes.h" +#include "stdlib/optionals.h" #include "stdlib/tables.h" #include "stdlib/text.h" @@ -202,8 +203,8 @@ Text_t ast_to_sexp(ast_t *ast) { T(None, "(None)"); T(Bool, "(Bool ", data.b ? "yes" : "no", ")"); T(Var, "(Var ", quoted_text(data.name), ")"); - T(Int, "(Int ", quoted_text(ast_source(ast)), ")"); - T(Num, "(Num ", quoted_text(ast_source(ast)), ")"); + T(Int, "(Int ", Text$quoted(ast_source(ast), false, Text("\"")), ")"); + T(Num, "(Num ", Text$quoted(ast_source(ast), false, Text("\"")), ")"); T(TextLiteral, Text$quoted(data.text, false, Text("\""))); T(TextJoin, "(Text", data.lang ? Texts(" :lang ", quoted_text(data.lang)) : EMPTY_TEXT, ast_list_to_sexp(data.children), ")"); @@ -312,13 +313,9 @@ Text_t ast_to_sexp(ast_t *ast) { const char *ast_to_sexp_str(ast_t *ast) { return Text$as_c_string(ast_to_sexp(ast)); } -const char *ast_source(ast_t *ast) { - if (!ast) return NULL; - size_t len = (size_t)(ast->end - ast->start); - char *source = GC_MALLOC_ATOMIC(len + 1); - memcpy(source, ast->start, len); - source[len] = '\0'; - return source; +OptionalText_t ast_source(ast_t *ast) { + if (ast == NULL || ast->start == NULL || ast->end == NULL) return NONE_TEXT; + return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); } PUREFUNC bool is_idempotent(ast_t *ast) { @@ -427,6 +424,8 @@ void visit_topologically(ast_list_t *asts, Closure_t fn) { CONSTFUNC bool is_binary_operation(ast_t *ast) { switch (ast->tag) { + case Min: + case Max: case BINOP_CASES: return true; default: return false; } diff --git a/src/ast.h b/src/ast.h index bdccb704..3ac24635 100644 --- a/src/ast.h +++ b/src/ast.h @@ -481,7 +481,7 @@ struct ast_s { extern const int op_tightness[NUM_AST_TAGS]; -const char *ast_source(ast_t *ast); +OptionalText_t ast_source(ast_t *ast); Text_t ast_to_sexp(ast_t *ast); const char *ast_to_sexp_str(ast_t *ast); diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 2320474a..58288c50 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -152,7 +152,7 @@ Text_t compile(env_t *env, ast_t *ast) { ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key; ast_t *lhs = ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs; ast_t *rhs = ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs; - const char *key_name = "$"; + const char *key_name = ast->tag == Min ? "_min_" : "_max_"; if (key == NULL) key = FakeAST(Var, key_name); env_t *expr_env = fresh_scope(env); diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 96bdf632..a48a1f33 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -216,6 +216,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { return Texts(code, quote); } /*inline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } + /*inline*/ case Path: { return Texts("(", Text$from_str(Match(ast, Path)->path), ")"); } /*inline*/ case Stop: { const char *target = Match(ast, Stop)->target; return target ? Texts("stop ", Text$from_str(target)) : Text("stop"); @@ -224,15 +225,27 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { const char *target = Match(ast, Skip)->target; return target ? Texts("skip ", Text$from_str(target)) : Text("skip"); } + /*inline*/ case Min: + /*inline*/ case Max: { + Text_t lhs = fmt_inline(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments); + Text_t rhs = fmt_inline(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments); + ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key; + return Texts(lhs, key ? fmt_inline(key, comments) : (ast->tag == Min ? Text(" _min_ ") : Text(" _max_ ")), rhs); + } /*inline*/ case None: + return Text("none"); /*inline*/ case Bool: - /*inline*/ case Int: - /*inline*/ case Num: - /*inline*/ 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; + return Match(ast, Bool)->b ? Text("yes") : Text("no"); + /*inline*/ case Int: { + OptionalText_t source = ast_source(ast); + return source.length > 0 ? source : Text$from_str(Match(ast, Int)->str); + } + /*inline*/ case Num: { + OptionalText_t source = ast_source(ast); + return source.length > 0 ? source : Text$from_str(String(Match(ast, Num)->n)); } + /*inline*/ case Var: + return Text$from_str(Match(ast, Var)->name); /*inline*/ case FunctionCall: { DeclareMatch(call, ast, FunctionCall); return Texts(fmt_inline(call->fn, comments), "(", must(format_inline_args(call->args, comments)), ")"); @@ -566,6 +579,18 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { return code; } /*multiline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } + /*multiline*/ case Path: { + assert(inlined.length > 0); + return inlined; + } + /*inline*/ case Min: + /*inline*/ case Max: { + Text_t lhs = termify(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments, indent); + Text_t rhs = termify(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments, indent); + ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key; + Text_t op = key ? fmt(key, comments, indent) : (ast->tag == Min ? Text("_min_") : Text("_max_")); + return Texts(lhs, " ", op, " ", rhs); + } /*multiline*/ case Stop: /*multiline*/ case Skip: /*multiline*/ case None: diff --git a/src/formatter/utils.h b/src/formatter/utils.h index df9c9b92..9dc230e4 100644 --- a/src/formatter/utils.h +++ b/src/formatter/utils.h @@ -6,6 +6,7 @@ #include "../ast.h" #include "../stdlib/datatypes.h" +#include "../stdlib/optionals.h" #define MAX_WIDTH 100 diff --git a/src/parse/binops.c b/src/parse/binops.c index 80bd7dd5..4676b249 100644 --- a/src/parse/binops.c +++ b/src/parse/binops.c @@ -67,7 +67,7 @@ ast_t *parse_infix_expr(parse_ctx_t *ctx, const char *pos, int min_tightness) { for (ast_e op; (op = match_binary_operator(&pos)) != Unknown && op_tightness[op] >= min_tightness; spaces(&pos)) { ast_t *key = NULL; if (op == Min || op == Max) { - key = NewAST(ctx->file, pos, pos, Var, .name = "$"); + key = NewAST(ctx->file, pos, pos, Var, .name = (op == Min ? "_min_" : "_max_")); for (bool progress = true; progress;) { ast_t *new_term; progress = -- cgit v1.2.3 From cb1d36c6d8bc84f3422c71ab3eb29606e80f7837 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 14:58:51 -0400 Subject: Formatting for reductions --- src/ast.c | 48 ++++++++++++++++++++++++++------------------- src/compile/comparisons.c | 14 ++++++++++++- src/compile/reductions.c | 7 ++++--- src/formatter/formatter.c | 50 +++++++++++++++++++++++++++++++++++++++++------ src/formatter/utils.c | 38 ----------------------------------- src/formatter/utils.h | 1 - src/parse/expressions.c | 4 +++- 7 files changed, 92 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 3b58b919..03843438 100644 --- a/src/ast.c +++ b/src/ast.c @@ -76,32 +76,40 @@ CONSTFUNC const char *binop_method_name(ast_e tag) { CONSTFUNC const char *binop_operator(ast_e tag) { switch (tag) { - case Multiply: - case MultiplyUpdate: return "*"; - case Divide: - case DivideUpdate: return "/"; - case Mod: - case ModUpdate: return "%"; - case Plus: - case PlusUpdate: return "+"; - case Minus: - case MinusUpdate: return "-"; - case LeftShift: - case LeftShiftUpdate: return "<<"; - case RightShift: - case RightShiftUpdate: return ">>"; - case And: - case AndUpdate: return "&"; - case Or: - case OrUpdate: return "|"; - case Xor: - case XorUpdate: return "^"; + case Power: return "^"; + case PowerUpdate: return "^="; + case Concat: return "++"; + case ConcatUpdate: return "++="; + case Multiply: return "*"; + case MultiplyUpdate: return "*="; + case Divide: return "/"; + case DivideUpdate: return "/="; + case Mod: return "mod"; + case ModUpdate: return "mod="; + case Mod1: return "mod1"; + case Mod1Update: return "mod1="; + case Plus: return "+"; + case PlusUpdate: return "+="; + case Minus: return "-"; + case MinusUpdate: return "-="; + case LeftShift: return "<<"; + case LeftShiftUpdate: return "<<="; + case RightShift: return ">>"; + case RightShiftUpdate: return ">>="; + case And: return "and"; + case AndUpdate: return "and="; + case Or: return "or"; + case OrUpdate: return "or="; + case Xor: return "xor"; + case XorUpdate: return "xor="; case Equals: return "=="; case NotEquals: return "!="; case LessThan: return "<"; case LessThanOrEquals: return "<="; case GreaterThan: return ">"; case GreaterThanOrEquals: return ">="; + case Min: return "_min_"; + case Max: return "_max_"; default: return NULL; } } diff --git a/src/compile/comparisons.c b/src/compile/comparisons.c index d73664de..d7531261 100644 --- a/src/compile/comparisons.c +++ b/src/compile/comparisons.c @@ -7,6 +7,18 @@ #include "../typecheck.h" #include "compilation.h" +static CONSTFUNC const char *comparison_operator(ast_e tag) { + switch (tag) { + case Equals: return "=="; + case NotEquals: return "!="; + case LessThan: return "<"; + case LessThanOrEquals: return "<="; + case GreaterThan: return ">"; + case GreaterThanOrEquals: return ">="; + default: return NULL; + } +} + Text_t compile_comparison(env_t *env, ast_t *ast) { switch (ast->tag) { @@ -75,7 +87,7 @@ Text_t compile_comparison(env_t *env, ast_t *ast) { if (ast->tag == Compare) return Texts("generic_compare(stack(", lhs, "), stack(", rhs, "), ", compile_type_info(operand_t), ")"); - const char *op = binop_operator(ast->tag); + const char *op = comparison_operator(ast->tag); switch (operand_t->tag) { case BigIntType: return Texts("(Int$compare_value(", lhs, ", ", rhs, ") ", op, " 0)"); case BoolType: diff --git a/src/compile/reductions.c b/src/compile/reductions.c index e0477a9c..1652384c 100644 --- a/src/compile/reductions.c +++ b/src/compile/reductions.c @@ -12,6 +12,7 @@ public Text_t compile_reduction(env_t *env, ast_t *ast) { DeclareMatch(reduction, ast, Reduction); ast_e op = reduction->op; + const char *op_str = binop_operator(op); type_t *iter_t = get_type(env, reduction->iter); type_t *item_t = get_iterated_type(iter_t); @@ -29,7 +30,7 @@ Text_t compile_reduction(env_t *env, ast_t *ast) { type_t *item_value_type = item_t; ast_t *item_value = item; if (reduction->key) { - set_binding(body_scope, "$", item_t, compile(body_scope, item)); + set_binding(body_scope, op_str, item_t, compile(body_scope, item)); item_value = reduction->key; item_value_type = get_type(body_scope, reduction->key); } @@ -67,7 +68,7 @@ Text_t compile_reduction(env_t *env, ast_t *ast) { ast_e cmp_op = op == Min ? LessThan : GreaterThan; if (reduction->key) { env_t *key_scope = fresh_scope(env); - set_binding(key_scope, "$", item_t, item_code); + set_binding(key_scope, op_str, item_t, item_code); type_t *key_type = get_type(key_scope, reduction->key); Text_t superlative_key = op == Min ? Text("min_key") : Text("max_key"); code = Texts(code, compile_declaration(key_type, superlative_key), ";\n"); @@ -111,7 +112,7 @@ Text_t compile_reduction(env_t *env, ast_t *ast) { type_t *reduction_type = Match(get_type(env, ast), OptionalType)->type; ast_t *item_value = item; if (reduction->key) { - set_binding(body_scope, "$", item_t, compile(body_scope, item)); + set_binding(body_scope, op_str, item_t, compile(body_scope, item)); item_value = reduction->key; } diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index a48a1f33..5dd65d04 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -44,6 +44,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { /*inline*/ case LangDef: /*inline*/ case Extend: /*inline*/ case FunctionDef: + /*inline*/ case ConvertDef: /*inline*/ case DocTest: return NONE_TEXT; /*inline*/ case Lambda: { @@ -232,6 +233,14 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key; return Texts(lhs, key ? fmt_inline(key, comments) : (ast->tag == Min ? Text(" _min_ ") : Text(" _max_ ")), rhs); } + /*inline*/ case Reduction: { + DeclareMatch(reduction, ast, Reduction); + if (reduction->key) { + return Texts("(", fmt_inline(reduction->key, comments), ": ", fmt_inline(reduction->iter, comments)); + } else { + return Texts("(", binop_operator(reduction->op), ": ", fmt_inline(reduction->iter, comments)); + } + } /*inline*/ case None: return Text("none"); /*inline*/ case Bool: @@ -259,7 +268,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } /*inline*/ case BINOP_CASES: { binary_operands_t operands = BINARY_OPERANDS(ast); - const char *op = binop_tomo_operator(ast->tag); + const char *op = binop_operator(ast->tag); Text_t lhs = fmt_inline(operands.lhs, comments); Text_t rhs = fmt_inline(operands.rhs, comments); @@ -274,7 +283,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { rhs = parenthesize(rhs, EMPTY_TEXT); Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); - return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); + return Texts(lhs, space, Text$from_str(binop_operator(ast->tag)), space, rhs); } default: { fail("Formatting not implemented for: ", ast_to_sexp(ast)); @@ -412,6 +421,16 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { code = Texts(code, ")\n", indent, single_indent, fmt(lambda->body, comments, Texts(indent, single_indent))); return Texts(code); } + /*multiline*/ case ConvertDef: { + DeclareMatch(convert, ast, ConvertDef); + Text_t code = Texts("convert (", format_args(convert->args, comments, indent)); + if (convert->ret_type) + code = Texts(code, convert->args ? Text(" -> ") : Text("-> "), format_type(convert->ret_type)); + if (convert->cache) code = Texts(code, "; cache=", fmt(convert->cache, comments, indent)); + if (convert->is_inline) code = Texts(code, "; inline"); + code = Texts(code, ")\n", indent, single_indent, fmt(convert->body, comments, Texts(indent, single_indent))); + return Texts(code); + } /*multiline*/ case StructDef: { DeclareMatch(def, ast, StructDef); Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); @@ -484,6 +503,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { return Texts(fmt(entry->key, comments, indent), "=", fmt(entry->value, comments, indent)); } /*multiline*/ case Declare: { + if (inlined_fits) return inlined; DeclareMatch(decl, ast, Declare); Text_t code = fmt(decl->var, comments, indent); if (decl->type) code = Texts(code, " : ", format_type(decl->type)); @@ -492,6 +512,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { return code; } /*multiline*/ case Assign: { + if (inlined_fits) return inlined; DeclareMatch(assign, ast, Assign); Text_t code = EMPTY_TEXT; for (ast_list_t *target = assign->targets; target; target = target->next) { @@ -508,15 +529,18 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case Pass: return Text("pass"); /*multiline*/ case Return: { + if (inlined_fits) return inlined; ast_t *value = Match(ast, Return)->value; return value ? Texts("return ", fmt(value, comments, indent)) : Text("return"); } /*inline*/ case Not: { + if (inlined_fits) return inlined; ast_t *val = Match(ast, Not)->value; if (is_binary_operation(val)) return Texts("not ", termify(val, comments, indent)); else return Texts("not ", fmt(val, comments, indent)); } /*inline*/ case Negative: { + if (inlined_fits) return inlined; ast_t *val = Match(ast, Negative)->value; if (is_binary_operation(val)) return Texts("-", termify(val, comments, indent)); else return Texts("-", fmt(val, comments, indent)); @@ -542,10 +566,12 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { return Texts(termify(val, comments, indent), "!"); } /*multiline*/ case FieldAccess: { + if (inlined_fits) return inlined; DeclareMatch(access, ast, FieldAccess); return Texts(termify(access->fielded, comments, indent), ".", Text$from_str(access->field)); } /*multiline*/ case Index: { + if (inlined_fits) return inlined; DeclareMatch(index, ast, Index); if (index->index) return Texts(termify(index->indexed, comments, indent), "[", fmt(index->index, comments, indent), "]"); @@ -583,14 +609,26 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { assert(inlined.length > 0); return inlined; } - /*inline*/ case Min: - /*inline*/ case Max: { + /*multiline*/ case Min: + /*multiline*/ case Max: { + if (inlined_fits) return inlined; Text_t lhs = termify(ast->tag == Min ? Match(ast, Min)->lhs : Match(ast, Max)->lhs, comments, indent); Text_t rhs = termify(ast->tag == Min ? Match(ast, Min)->rhs : Match(ast, Max)->rhs, comments, indent); ast_t *key = ast->tag == Min ? Match(ast, Min)->key : Match(ast, Max)->key; Text_t op = key ? fmt(key, comments, indent) : (ast->tag == Min ? Text("_min_") : Text("_max_")); return Texts(lhs, " ", op, " ", rhs); } + /*multiline*/ case Reduction: { + if (inlined_fits) return inlined; + DeclareMatch(reduction, ast, Reduction); + if (reduction->key) { + return Texts("(", fmt(reduction->key, comments, Texts(indent, single_indent)), ": ", + fmt(reduction->iter, comments, Texts(indent, single_indent))); + } else { + return Texts("(", binop_operator(reduction->op), ": ", + fmt(reduction->iter, comments, Texts(indent, single_indent))); + } + } /*multiline*/ case Stop: /*multiline*/ case Skip: /*multiline*/ case None: @@ -627,7 +665,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case BINOP_CASES: { if (inlined_fits) return inlined; binary_operands_t operands = BINARY_OPERANDS(ast); - const char *op = binop_tomo_operator(ast->tag); + const char *op = binop_operator(ast->tag); Text_t lhs = fmt(operands.lhs, comments, indent); Text_t rhs = fmt(operands.rhs, comments, indent); @@ -641,7 +679,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { rhs = parenthesize(rhs, indent); Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); - return Texts(lhs, space, Text$from_str(binop_tomo_operator(ast->tag)), space, rhs); + return Texts(lhs, space, Text$from_str(binop_operator(ast->tag)), space, rhs); } default: { if (inlined_fits) return inlined; diff --git a/src/formatter/utils.c b/src/formatter/utils.c index a032ec2f..fa4ec806 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -74,44 +74,6 @@ CONSTFUNC ast_t *unwrap_block(ast_t *ast) { return ast; } -CONSTFUNC const char *binop_tomo_operator(ast_e tag) { - switch (tag) { - case Power: return "^"; - case PowerUpdate: return "^="; - case Concat: return "++"; - case ConcatUpdate: return "++="; - case Multiply: return "*"; - case MultiplyUpdate: return "*="; - case Divide: return "/"; - case DivideUpdate: return "/="; - case Mod: return "mod"; - case ModUpdate: return "mod="; - case Mod1: return "mod1"; - case Mod1Update: return "mod1="; - case Plus: return "+"; - case PlusUpdate: return "+="; - case Minus: return "-"; - case MinusUpdate: return "-="; - case LeftShift: return "<<"; - case LeftShiftUpdate: return "<<="; - case RightShift: return ">>"; - case RightShiftUpdate: return ">>="; - case And: return "and"; - case AndUpdate: return "and="; - case Or: return "or"; - case OrUpdate: return "or="; - case Xor: return "xor"; - case XorUpdate: return "xor="; - case Equals: return "=="; - case NotEquals: return "!="; - case LessThan: return "<"; - case LessThanOrEquals: return "<="; - case GreaterThan: return ">"; - case GreaterThanOrEquals: return ">="; - default: return NULL; - } -} - OptionalText_t termify_inline(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { diff --git a/src/formatter/utils.h b/src/formatter/utils.h index 9dc230e4..d847c2fb 100644 --- a/src/formatter/utils.h +++ b/src/formatter/utils.h @@ -26,6 +26,5 @@ CONSTFUNC bool should_have_blank_line(ast_t *ast); Text_t indent_code(Text_t code); Text_t parenthesize(Text_t code, Text_t indent); CONSTFUNC ast_t *unwrap_block(ast_t *ast); -CONSTFUNC const char *binop_tomo_operator(ast_e tag); OptionalText_t termify_inline(ast_t *ast, Table_t comments); Text_t termify(ast_t *ast, Table_t comments, Text_t indent); diff --git a/src/parse/expressions.c b/src/parse/expressions.c index 6104b2d2..32133fe9 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -49,7 +49,9 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) { ast_e op = match_binary_operator(&pos); if (op == Unknown) return NULL; - ast_t *key = NewAST(ctx->file, pos, pos, Var, .name = "$"); + const char *op_str = binop_operator(op); + assert(op_str); + ast_t *key = NewAST(ctx->file, pos, pos, Var, .name = op_str); for (bool progress = true; progress;) { ast_t *new_term; progress = -- cgit v1.2.3 From e5677cb9a670ca9e1e4c5d47f3c130622c619772 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 15:51:52 -0400 Subject: Add formatting for asserts --- src/formatter/formatter.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 5dd65d04..6f0fc5aa 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -47,6 +47,13 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { /*inline*/ case ConvertDef: /*inline*/ case DocTest: return NONE_TEXT; + /*inline*/ case Assert: { + DeclareMatch(assert, ast, Assert); + Text_t expr = fmt_inline(assert->expr, comments); + if (!assert->message) return Texts("assert ", expr); + Text_t message = fmt_inline(assert->message, comments); + return Texts("assert ", expr, ", ", message); + } /*inline*/ case Lambda: { DeclareMatch(lambda, ast, Lambda); Text_t code = Texts("func(", format_inline_args(lambda->args, comments)); @@ -662,6 +669,13 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { } return code; } + /*multiline*/ case Assert: { + DeclareMatch(assert, ast, Assert); + Text_t expr = fmt(assert->expr, comments, indent); + if (!assert->message) return Texts("assert ", expr); + Text_t message = fmt(assert->message, comments, indent); + return Texts("assert ", expr, ", ", message); + } /*multiline*/ case BINOP_CASES: { if (inlined_fits) return inlined; binary_operands_t operands = BINARY_OPERANDS(ast); -- cgit v1.2.3 From 76aac20d8ed7482f4ee7f3b3a69e3e08138251fa Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 15:54:54 -0400 Subject: Add formatting for defer --- src/formatter/formatter.c | 4 ++++ src/formatter/utils.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 6f0fc5aa..64158876 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -54,6 +54,8 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { Text_t message = fmt_inline(assert->message, comments); return Texts("assert ", expr, ", ", message); } + /*inline*/ case Defer: + return Texts("defer ", fmt_inline(Match(ast, Defer)->body, comments)); /*inline*/ case Lambda: { DeclareMatch(lambda, ast, Lambda); Text_t code = Texts("func(", format_inline_args(lambda->args, comments)); @@ -459,6 +461,8 @@ 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)); } + /*inline*/ case Defer: + return Texts("defer ", format_namespace(Match(ast, Defer)->body, comments, indent)); /*multiline*/ case List: /*multiline*/ case Set: { if (inlined_fits) return inlined; diff --git a/src/formatter/utils.c b/src/formatter/utils.c index fa4ec806..084a6667 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -51,7 +51,8 @@ CONSTFUNC bool should_have_blank_line(ast_t *ast) { case EnumDef: case LangDef: case ConvertDef: - case Extend: return true; + case Extend: + case Defer: return true; default: return false; } } -- cgit v1.2.3 From bc619548d87c1a7c588998a76719f85a7c809306 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 16:57:13 -0400 Subject: Add extern formatter --- src/formatter/formatter.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 64158876..07974e3c 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -46,6 +46,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { /*inline*/ case FunctionDef: /*inline*/ case ConvertDef: /*inline*/ case DocTest: + /*inline*/ case Extern: return NONE_TEXT; /*inline*/ case Assert: { DeclareMatch(assert, ast, Assert); @@ -461,7 +462,11 @@ 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)); } - /*inline*/ case Defer: + /*multiline*/ case Extern: { + DeclareMatch(ext, ast, Extern); + return Texts("extern ", Text$from_str(ext->name), " : ", format_type(ext->type)); + } + /*multiline*/ case Defer: return Texts("defer ", format_namespace(Match(ast, Defer)->body, comments, indent)); /*multiline*/ case List: /*multiline*/ case Set: { -- cgit v1.2.3 From 442e841cfa89b3f3f7d3cf54c0c8fd5af4b48940 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 17:36:44 -0400 Subject: Inline C code formatting --- src/formatter/formatter.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 07974e3c..49670e0d 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -226,6 +226,20 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } return Texts(code, quote); } + /*inline*/ case InlineCCode: { + DeclareMatch(c_code, ast, InlineCCode); + Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast), "(") : Text("C_code{"); + for (ast_list_t *chunk = c_code->chunks; chunk; chunk = chunk->next) { + if (chunk->ast->tag == TextLiteral) { + Text_t literal = Match(chunk->ast, TextLiteral)->text; + if (Text$has(literal, Text("\n"))) return NONE_TEXT; + code = Texts(code, Text$slice(Text$quoted(literal, false, Text("`")), I(2), I(-2))); + } else { + code = Texts(code, "@(", fmt_inline(chunk->ast, comments), ")"); + } + } + return Texts(code, c_code->type_ast ? Text(")") : Text("}")); + } /*inline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } /*inline*/ case Path: { return Texts("(", Text$from_str(Match(ast, Path)->path), ")"); } /*inline*/ case Stop: { @@ -612,7 +626,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { current_line = *(Text_t *)(lines.data + i * lines.stride); } } else { - code = Texts(code, "$(", fmt_inline(chunk->ast, comments), ")"); + current_line = Texts(current_line, "$(", fmt_inline(chunk->ast, comments), ")"); } } add_line(&code, current_line, Texts(indent, single_indent)); @@ -620,6 +634,35 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { if (lang) code = Texts("$", Text$from_str(lang), code); return code; } + /*multiline*/ case InlineCCode: { + DeclareMatch(c_code, ast, InlineCCode); + if (inlined_fits && c_code->type_ast) return inlined; + + Text_t code = EMPTY_TEXT; + Text_t current_line = EMPTY_TEXT; + for (ast_list_t *chunk = c_code->chunks; 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 { + current_line = Texts(current_line, "@(", fmt_inline(chunk->ast, comments), ")"); + } + } + add_line(&code, current_line, Texts(indent, single_indent)); + + if (c_code->type_ast) { + return Texts("C_code:", format_type(c_code->type_ast), "(\n", indent, single_indent, code, "\n", indent, + ")"); + } else { + return Texts("C_code{\n", indent, single_indent, code, "\n", indent, "}"); + } + } /*multiline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } /*multiline*/ case Path: { assert(inlined.length > 0); -- cgit v1.2.3 From d8116c27f406ac4915aaa7cdae97fb73c9847c8a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 13:27:24 -0400 Subject: Fix for typechecking reductions --- src/typecheck.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/typecheck.c b/src/typecheck.c index 828d2509..5b7a453d 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1373,7 +1373,8 @@ type_t *get_type(env_t *env, ast_t *ast) { code_err(reduction->iter, "I don't know how to do a reduction over ", type_to_str(iter_t), " values"); if (reduction->key && !(reduction->op == Min || reduction->op == Max)) { env_t *item_scope = fresh_scope(env); - set_binding(item_scope, "$", iterated, EMPTY_TEXT); + const char *op_str = binop_operator(reduction->op); + set_binding(item_scope, op_str, iterated, EMPTY_TEXT); iterated = get_type(item_scope, reduction->key); } return iterated->tag == OptionalType ? iterated : Type(OptionalType, .type = iterated); -- cgit v1.2.3 From 43105107b9d1e985e9c182b904f2ac79b17fb460 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 13:32:17 -0400 Subject: Improvements to text and inline C code formatting/parsing --- src/ast.c | 2 +- src/compile/expressions.c | 3 +- src/formatter/formatter.c | 152 +++++++++++++++++++++++----------------------- src/parse/text.c | 20 +++--- src/stdlib/text.c | 43 +++++++------ src/stdlib/text.h | 1 + 6 files changed, 112 insertions(+), 109 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 03843438..486e0262 100644 --- a/src/ast.c +++ b/src/ast.c @@ -291,7 +291,7 @@ Text_t ast_to_sexp(ast_t *ast) { ")"); T(When, "(When ", ast_to_sexp(data.subject), when_clauses_to_sexp(data.clauses), optional_sexp("else", data.else_body), ")"); - T(Reduction, "(Reduction ", quoted_text(binop_method_name(data.op)), " ", ast_to_sexp(data.key), " ", + T(Reduction, "(Reduction ", quoted_text(binop_operator(data.op)), " ", ast_to_sexp(data.key), " ", ast_to_sexp(data.iter), ")"); T(Skip, "(Skip ", quoted_text(data.target), ")"); T(Stop, "(Stop ", quoted_text(data.target), ")"); diff --git a/src/compile/expressions.c b/src/compile/expressions.c index 58288c50..544b7723 100644 --- a/src/compile/expressions.c +++ b/src/compile/expressions.c @@ -237,7 +237,8 @@ Text_t compile(env_t *env, ast_t *ast) { case Index: return compile_indexing(env, ast); case InlineCCode: { type_t *t = get_type(env, ast); - if (t->tag == VoidType) return Texts("{\n", compile_statement(env, ast), "\n}"); + if (Match(ast, InlineCCode)->type_ast != NULL) return Texts("({", compile_statement(env, ast), "; })"); + else if (t->tag == VoidType) return Texts("{\n", compile_statement(env, ast), "\n}"); else return compile_statement(env, ast); } case Use: code_err(ast, "Compiling 'use' as expression!"); diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 49670e0d..2d88b486 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -10,7 +10,6 @@ #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/text.h" @@ -28,6 +27,66 @@ Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent) { return Texts("\n", indent, single_indent, fmt(namespace, comments, Texts(indent, single_indent))); } +typedef struct { + Text_t quote, unquote, interp; +} text_opts_t; + +text_opts_t choose_text_options(ast_list_t *chunks) { + int double_quotes = 0, single_quotes = 0, backticks = 0; + for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) { + if (chunk->ast->tag == TextLiteral) { + Text_t literal = Match(chunk->ast, TextLiteral)->text; + if (Text$has(literal, Text("\""))) double_quotes += 1; + if (Text$has(literal, Text("'"))) single_quotes += 1; + if (Text$has(literal, Text("`"))) backticks += 1; + } + } + Text_t quote; + if (double_quotes == 0) quote = Text("\""); + else if (single_quotes == 0) quote = Text("'"); + else if (backticks == 0) quote = Text("`"); + else quote = Text("\""); + + text_opts_t opts = {.quote = quote, .unquote = quote, .interp = Text("$")}; + return opts; +} + +static OptionalText_t format_inline_text(text_opts_t opts, ast_list_t *chunks, Table_t comments) { + Text_t code = opts.quote; + for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) { + if (chunk->ast->tag == TextLiteral) { + Text_t literal = Match(chunk->ast, TextLiteral)->text; + Text_t segment = Text$escaped(literal, false, Texts(opts.unquote, opts.interp)); + code = Texts(code, segment); + } else { + code = Texts(code, opts.interp, "(", fmt_inline(chunk->ast, comments), ")"); + } + } + return Texts(code, opts.unquote); +} + +static Text_t format_text(text_opts_t opts, ast_list_t *chunks, Table_t comments, Text_t indent) { + Text_t code = EMPTY_TEXT; + Text_t current_line = EMPTY_TEXT; + for (ast_list_t *chunk = chunks; 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$escaped(*(Text_t *)lines.data, false, opts.interp)); + for (int64_t i = 1; i < lines.length; i += 1) { + add_line(&code, current_line, Texts(indent, single_indent)); + current_line = Text$escaped(*(Text_t *)(lines.data + i * lines.stride), false, opts.interp); + } + } else { + current_line = Texts(current_line, opts.interp, "(", fmt(chunk->ast, comments, indent), ")"); + } + } + add_line(&code, current_line, Texts(indent, single_indent)); + code = Texts(opts.quote, "\n", indent, single_indent, code, "\n", indent, opts.unquote); + return code; +} + OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (range_has_comment(ast->start, ast->end, comments)) return NONE_TEXT; switch (ast->tag) { @@ -211,37 +270,21 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { else return Texts(indexed, "[]"); } /*inline*/ 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)); + text_opts_t opts = choose_text_options(Match(ast, TextJoin)->children); + Text_t ret = must(format_inline_text(opts, Match(ast, TextJoin)->children, comments)); 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, "$(", fmt_inline(chunk->ast, comments), ")"); - } - } - return Texts(code, quote); + return lang ? Texts("$", Text$from_str(lang), ret) : ret; } /*inline*/ case InlineCCode: { DeclareMatch(c_code, ast, InlineCCode); - Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast), "(") : Text("C_code{"); - for (ast_list_t *chunk = c_code->chunks; chunk; chunk = chunk->next) { - if (chunk->ast->tag == TextLiteral) { - Text_t literal = Match(chunk->ast, TextLiteral)->text; - if (Text$has(literal, Text("\n"))) return NONE_TEXT; - code = Texts(code, Text$slice(Text$quoted(literal, false, Text("`")), I(2), I(-2))); - } else { - code = Texts(code, "@(", fmt_inline(chunk->ast, comments), ")"); - } - } - return Texts(code, c_code->type_ast ? Text(")") : Text("}")); + Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast)) : Text("C_code"); + text_opts_t opts = {.quote = Text("`"), .unquote = Text("`"), .interp = Text("@")}; + return Texts(code, must(format_inline_text(opts, Match(ast, InlineCCode)->chunks, comments))); } /*inline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } - /*inline*/ case Path: { return Texts("(", Text$from_str(Match(ast, Path)->path), ")"); } + /*inline*/ case Path: { + return Texts("(", Text$escaped(Text$from_str(Match(ast, Path)->path), false, Text("()")), ")"); + } /*inline*/ case Stop: { const char *target = Match(ast, Stop)->target; return target ? Texts("stop ", Text$from_str(target)) : Text("stop"); @@ -609,59 +652,18 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { } /*multiline*/ 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)); + + text_opts_t opts = choose_text_options(Match(ast, TextJoin)->children); + Text_t ret = format_text(opts, Match(ast, TextJoin)->children, comments, indent); 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 { - current_line = Texts(current_line, "$(", fmt_inline(chunk->ast, comments), ")"); - } - } - add_line(&code, current_line, Texts(indent, single_indent)); - code = Texts(quote, "\n", indent, single_indent, code, "\n", indent, quote); - if (lang) code = Texts("$", Text$from_str(lang), code); - return code; + return lang ? Texts("$", Text$from_str(lang), ret) : ret; } /*multiline*/ case InlineCCode: { DeclareMatch(c_code, ast, InlineCCode); - if (inlined_fits && c_code->type_ast) return inlined; - - Text_t code = EMPTY_TEXT; - Text_t current_line = EMPTY_TEXT; - for (ast_list_t *chunk = c_code->chunks; 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 { - current_line = Texts(current_line, "@(", fmt_inline(chunk->ast, comments), ")"); - } - } - add_line(&code, current_line, Texts(indent, single_indent)); - - if (c_code->type_ast) { - return Texts("C_code:", format_type(c_code->type_ast), "(\n", indent, single_indent, code, "\n", indent, - ")"); - } else { - return Texts("C_code{\n", indent, single_indent, code, "\n", indent, "}"); - } + if (inlined_fits && c_code->type != NULL) return inlined; + Text_t code = c_code->type_ast ? Texts("C_code:", format_type(c_code->type_ast)) : Text("C_code"); + text_opts_t opts = {.quote = Text("`"), .unquote = Text("`"), .interp = Text("@")}; + return Texts(code, format_text(opts, Match(ast, InlineCCode)->chunks, comments, indent)); } /*multiline*/ case TextLiteral: { fail("Something went wrong, we shouldn't be formatting text literals directly"); } /*multiline*/ case Path: { diff --git a/src/parse/text.c b/src/parse/text.c index c554273f..6e7201bb 100644 --- a/src/parse/text.c +++ b/src/parse/text.c @@ -162,23 +162,19 @@ ast_t *parse_inline_c(parse_ctx_t *ctx, const char *pos) { spaces(&pos); type_ast_t *type = NULL; - ast_list_t *chunks; if (match(&pos, ":")) { type = expect(ctx, start, &pos, parse_type, "I couldn't parse the type for this C_code code"); spaces(&pos); - if (!match(&pos, "(")) parser_err(ctx, start, pos, "I expected a '(' here"); - chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("({")), - .next = _parse_text_helper(ctx, &pos, '(', ')', '@', false)); - if (type) { - REVERSE_LIST(chunks); - chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("; })")), .next = chunks); - REVERSE_LIST(chunks); - } - } else { - if (!match(&pos, "{")) parser_err(ctx, start, pos, "I expected a '{' here"); - chunks = _parse_text_helper(ctx, &pos, '{', '}', '@', false); } + static const char *quote_chars = "\"'`|/;([{<"; + if (!strchr(quote_chars, *pos)) + parser_err(ctx, pos, pos + 1, + "This is not a valid string quotation character. Valid characters are: \"'`|/;([{<"); + + char quote = *(pos++); + char unquote = closing[(int)quote] ? closing[(int)quote] : quote; + ast_list_t *chunks = _parse_text_helper(ctx, &pos, quote, unquote, '@', false); return NewAST(ctx->file, start, pos, InlineCCode, .chunks = chunks, .type_ast = type); } diff --git a/src/stdlib/text.c b/src/stdlib/text.c index ed4023a4..57465034 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1390,17 +1390,8 @@ Text_t Text$title(Text_t text, Text_t language) { } public -Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) { - if (quotation_mark.length != 1) fail("Invalid quote text: ", quotation_mark, " (must have length == 1)"); - +Text_t Text$escaped(Text_t text, bool colorize, Text_t extra_escapes) { Text_t ret = colorize ? Text("\x1b[35m") : EMPTY_TEXT; - if (!Text$equal_values(quotation_mark, Text("\"")) && !Text$equal_values(quotation_mark, Text("'")) - && !Text$equal_values(quotation_mark, Text("`"))) - ret = concat2_assuming_safe(ret, Text("$")); - - ret = concat2_assuming_safe(ret, quotation_mark); - int32_t quote_char = Text$get_grapheme(quotation_mark, 0); - #define flush_unquoted() \ ({ \ if (unquoted_span > 0) { \ @@ -1454,15 +1445,18 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) { break; } default: { - if (g == quote_char) { - flush_unquoted(); - if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[34;1m")); - ret = concat2_assuming_safe(ret, Text("\\")); - ret = concat2_assuming_safe(ret, quotation_mark); - if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[0;35m")); - } else { - unquoted_span += 1; + TextIter_t esc_state = NEW_TEXT_ITER_STATE(extra_escapes); + for (int64_t j = 0; j < extra_escapes.length; j++) { + int32_t esc = Text$get_grapheme_fast(&esc_state, j); + if (g == esc) { + flush_unquoted(); + if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[34;1m")); + ret = concat2_assuming_safe(ret, Text("\\")); + if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[0;35m")); + break; + } } + unquoted_span += 1; break; } } @@ -1470,10 +1464,19 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) { flush_unquoted(); #undef add_escaped #undef flush_unquoted - - ret = concat2_assuming_safe(ret, quotation_mark); if (colorize) ret = concat2_assuming_safe(ret, Text("\x1b[m")); + return ret; +} + +public +Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) { + if (quotation_mark.length != 1) fail("Invalid quote text: ", quotation_mark, " (must have length == 1)"); + Text_t ret = Text$escaped(text, colorize, quotation_mark); + if (!(Text$equal_values(quotation_mark, Text("\"")) || Text$equal_values(quotation_mark, Text("'")) + || Text$equal_values(quotation_mark, Text("`")))) + ret = Texts("$", quotation_mark, ret, quotation_mark); + else ret = Texts(quotation_mark, ret, quotation_mark); return ret; } diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 5fa95675..d118cffd 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -54,6 +54,7 @@ Text_t Text$upper(Text_t text, Text_t language); Text_t Text$lower(Text_t text, Text_t language); Text_t Text$title(Text_t text, Text_t language); Text_t Text$as_text(const void *text, bool colorize, const TypeInfo_t *info); +Text_t Text$escaped(Text_t text, bool colorize, Text_t extra_escapes); Text_t Text$quoted(Text_t str, bool colorize, Text_t quotation_mark); PUREFUNC bool Text$starts_with(Text_t text, Text_t prefix, Text_t *remainder); PUREFUNC bool Text$ends_with(Text_t text, Text_t suffix, Text_t *remainder); -- cgit v1.2.3 From 190810fcb1144cd29a676e15dd8d1a414072862d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 13:38:32 -0400 Subject: Add formatting for the last few cases --- src/formatter/formatter.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 2d88b486..89535071 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -352,6 +352,22 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); return Texts(lhs, space, Text$from_str(binop_operator(ast->tag)), space, rhs); } + /*inline*/ case Deserialize: { + DeclareMatch(deserialize, ast, Deserialize); + return Texts("deserialize(", fmt_inline(deserialize->value, comments), " -> ", format_type(deserialize->type), + ")"); + } + /*inline*/ case Use: { + DeclareMatch(use, ast, Use); + // struct { + // ast_t *var; + // const char *path; + // enum { USE_LOCAL, USE_MODULE, USE_SHARED_OBJECT, USE_HEADER, USE_C_CODE, USE_ASM } what; + // } Use; + return Texts("use ", use->path); + } + /*inline*/ case ExplicitlyTyped: + fail("Explicitly typed AST nodes are only meant to be used internally."); default: { fail("Formatting not implemented for: ", ast_to_sexp(ast)); } @@ -749,6 +765,18 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); return Texts(lhs, space, Text$from_str(binop_operator(ast->tag)), space, rhs); } + /*multiline*/ case Deserialize: { + if (inlined_fits) return inlined; + DeclareMatch(deserialize, ast, Deserialize); + return Texts("deserialize(", fmt(deserialize->value, comments, indent), " -> ", format_type(deserialize->type), + ")"); + } + /*multiline*/ case Use: { + assert(inlined.length > 0); + return inlined; + } + /*multiline*/ case ExplicitlyTyped: + fail("Explicitly typed AST nodes are only meant to be used internally."); default: { if (inlined_fits) return inlined; fail("Formatting not implemented for: ", ast_to_sexp(ast)); -- cgit v1.2.3 From 5ab9adf6e57c516220a594f9e8b39e5214f1af2d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 13:45:13 -0400 Subject: Fix repeat issue --- src/parse/controlflow.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c index f50c84d7..2f07d925 100644 --- a/src/parse/controlflow.c +++ b/src/parse/controlflow.c @@ -137,7 +137,7 @@ ast_t *parse_while(parse_ctx_t *ctx, const char *pos) { if (match_word(&tmp, "when")) { ast_t *when = expect(ctx, start, &pos, parse_when, "I expected a 'when' block after this"); if (!when->__data.When.else_body) when->__data.When.else_body = NewAST(ctx->file, pos, pos, Stop); - return NewAST(ctx->file, start, pos, While, .body = when); + return NewAST(ctx->file, start, pos, Repeat, .body = when); } (void)match_word(&pos, "do"); // Optional 'do' -- cgit v1.2.3 From 59f0311087099d567fa0e75b3b03f6b2a5f2eca7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 13:52:50 -0400 Subject: Remove special case for `while when` --- src/parse/controlflow.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'src') diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c index 2f07d925..1087e20e 100644 --- a/src/parse/controlflow.c +++ b/src/parse/controlflow.c @@ -131,18 +131,8 @@ ast_t *parse_while(parse_ctx_t *ctx, const char *pos) { // while condition ["do"] [] body const char *start = pos; if (!match_word(&pos, "while")) return NULL; - - const char *tmp = pos; - // Shorthand form: `while when ...` - if (match_word(&tmp, "when")) { - ast_t *when = expect(ctx, start, &pos, parse_when, "I expected a 'when' block after this"); - if (!when->__data.When.else_body) when->__data.When.else_body = NewAST(ctx->file, pos, pos, Stop); - return NewAST(ctx->file, start, pos, Repeat, .body = when); - } - - (void)match_word(&pos, "do"); // Optional 'do' - ast_t *condition = expect(ctx, start, &pos, parse_expr, "I don't see a viable condition for this 'while'"); + (void)match_word(&pos, "do"); // Optional 'do' ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a body for this 'while'"); return NewAST(ctx->file, start, pos, While, .condition = condition, .body = body); } -- cgit v1.2.3 From 0e67c79f6fc05e79749c2a007df459f926d3dba4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 13:58:24 -0400 Subject: Add --format-inplace option --- src/tomo.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index 25a92889..5cdb3f9d 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -75,7 +75,7 @@ static const char *paths_str(List_t paths) { static OptionalList_t files = NONE_LIST, args = NONE_LIST, uninstall = NONE_LIST, libraries = NONE_LIST; static OptionalBool_t verbose = false, quiet = false, show_version = false, show_parse_tree = false, - do_format_code = false, show_prefix = false, stop_at_transpile = false, + do_format_code = false, format_inplace = true, show_prefix = false, stop_at_transpile = false, stop_at_obj_compilation = false, compile_exe = false, should_install = false, clean_build = false, source_mapping = true, show_changelog = false; @@ -195,6 +195,7 @@ int main(int argc, char *argv[]) { {"parse", false, &Bool$info, &show_parse_tree}, // {"p", false, &Bool$info, &show_parse_tree}, // {"format", false, &Bool$info, &do_format_code}, // + {"format-inplace", false, &Bool$info, &format_inplace}, // {"prefix", false, &Bool$info, &show_prefix}, // {"quiet", false, &Bool$info, &quiet}, // {"q", false, &Bool$info, &quiet}, // @@ -315,9 +316,14 @@ int main(int argc, char *argv[]) { continue; } - if (do_format_code) { + if (do_format_code || format_inplace) { Text_t formatted = format_file(Path$as_c_string(path)); - print(formatted); + if (format_inplace) { + print("Formatted ", path); + Path$write(path, formatted, 0644); + } else { + print(formatted); + } continue; } -- cgit v1.2.3 From 2d3021728bf5c74f8bf6854c1ae4951a6ebbf9c7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 29 Aug 2025 14:04:55 -0400 Subject: Formatting tweaks/fixes --- src/formatter/formatter.c | 11 +++++++---- src/formatter/utils.c | 13 +++++++------ src/formatter/utils.h | 2 +- src/tomo.c | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 89535071..161c70d4 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -386,7 +386,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 (int blanks = suggested_blank_lines(stmt->ast); blanks > 0; blanks--) + add_line(&code, Text(""), indent); for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, stmt->ast->start)).length > 0;) { @@ -400,9 +401,11 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { add_line(&code, fmt(stmt->ast, comments, indent), indent); comment_pos = stmt->ast->end; - 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; + if (stmt->next) { + for (int blanks = suggested_blank_lines(stmt->ast) - suggested_blank_lines(stmt->next->ast); blanks > 0; + blanks--) + add_line(&code, Text(""), indent); + } else gap_before_comment = true; } for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, ast->end)).length > 0;) { diff --git a/src/formatter/utils.c b/src/formatter/utils.c index 084a6667..1f4e64d8 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -38,7 +38,7 @@ bool range_has_comment(const char *start, const char *end, Table_t comments) { return (comment.length >= 0); } -CONSTFUNC bool should_have_blank_line(ast_t *ast) { +CONSTFUNC int suggested_blank_lines(ast_t *ast) { switch (ast->tag) { case If: case When: @@ -46,14 +46,15 @@ CONSTFUNC bool should_have_blank_line(ast_t *ast) { case While: case For: case Block: - case FunctionDef: + case Defer: return 1; + case Use: return 1; + case ConvertDef: + case FunctionDef: return 1; case StructDef: case EnumDef: case LangDef: - case ConvertDef: - case Extend: - case Defer: return true; - default: return false; + case Extend: return 2; + default: return 0; } } diff --git a/src/formatter/utils.h b/src/formatter/utils.h index d847c2fb..0a00be6c 100644 --- a/src/formatter/utils.h +++ b/src/formatter/utils.h @@ -22,7 +22,7 @@ extern const Text_t single_indent; void add_line(Text_t *code, Text_t line, Text_t indent); OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); bool range_has_comment(const char *start, const char *end, Table_t comments); -CONSTFUNC bool should_have_blank_line(ast_t *ast); +CONSTFUNC int suggested_blank_lines(ast_t *ast); Text_t indent_code(Text_t code); Text_t parenthesize(Text_t code, Text_t indent); CONSTFUNC ast_t *unwrap_block(ast_t *ast); diff --git a/src/tomo.c b/src/tomo.c index 5cdb3f9d..a8bba555 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -75,7 +75,7 @@ static const char *paths_str(List_t paths) { static OptionalList_t files = NONE_LIST, args = NONE_LIST, uninstall = NONE_LIST, libraries = NONE_LIST; static OptionalBool_t verbose = false, quiet = false, show_version = false, show_parse_tree = false, - do_format_code = false, format_inplace = true, show_prefix = false, stop_at_transpile = false, + do_format_code = false, format_inplace = false, show_prefix = false, stop_at_transpile = false, stop_at_obj_compilation = false, compile_exe = false, should_install = false, clean_build = false, source_mapping = true, show_changelog = false; -- cgit v1.2.3 From a4c8409944b686f214c79c299831839589b559c5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 12:16:57 -0400 Subject: Update docs --- src/tomo.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index a8bba555..7c4b556c 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -180,10 +180,22 @@ int main(int argc, char *argv[]) { "\x1b[1mUninstall libraries:\x1b[m tomo -u lib...\n" "\x1b[1mOther flags:\x1b[m\n" " --verbose|-v: verbose output\n" + " --prefix: print the Tomo prefix directory\n" " --quiet|-q: quiet output\n" " --parse|-p: show parse tree\n" + " --transpile|-t: transpile C code without compiling\n" + " --show-codegen|-c : show generated code\n" + " --compile-obj|-c: compile C code for object file\n" + " --compile-exe|-e: compile to standalone executable without running\n" + " --format: print formatted code\n" + " --format-inplace: format the code in a file (in place)\n" + " --library|-L: build a folder as a library\n" " --install|-I: install the executable or library\n" + " --uninstall|-u: uninstall an executable or library\n" " --optimization|-O : set optimization level\n" + " --force-rebuild|-f: force rebuilding\n" + " --source-mapping|-m : toggle source mapping in generated code\n" + " --changelog: show the Tomo changelog\n" " --run|-r: run a program from " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed\n"); Text_t help = Texts(Text("\x1b[1mtomo\x1b[m: a compiler for the Tomo programming language"), Text("\n\n"), usage); tomo_parse_args(argc, argv, usage, help, TOMO_VERSION, // @@ -215,7 +227,8 @@ int main(int argc, char *argv[]) { {"I", false, &Bool$info, &should_install}, // {"optimization", false, &Text$info, &optimization}, // {"O", false, &Text$info, &optimization}, // - {"force-rebuild", false, &Bool$info, &clean_build}, {"f", false, &Bool$info, &clean_build}, // + {"force-rebuild", false, &Bool$info, &clean_build}, // + {"f", false, &Bool$info, &clean_build}, // {"source-mapping", false, &Bool$info, &source_mapping}, {"m", false, &Bool$info, &source_mapping}, // {"changelog", false, &Bool$info, &show_changelog}, ); -- cgit v1.2.3 From 16abb9016866782840127240279c42122a60d5d8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 13:52:59 -0400 Subject: More compact multi-line lists/sets/tables --- src/formatter/formatter.c | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 161c70d4..a120b9de 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -374,6 +374,17 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } } +static int64_t trailing_line_len(Text_t text) { + TextIter_t state = NEW_TEXT_ITER_STATE(text); + int64_t len = 0; + for (int64_t i = text.length - 1; i >= 0; i--) { + int32_t g = Text$get_grapheme_fast(&state, i); + if (g == '\n' || g == '\r') break; + len += 1; + } + return len; +} + Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { OptionalText_t inlined = format_inline_code(ast, comments); bool inlined_fits = (inlined.length >= 0 && indent.length + inlined.length <= MAX_WIDTH); @@ -548,34 +559,49 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ 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; + Text_t code = ast->tag == List ? Text("[") : 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(fmt(item->ast, comments, Texts(indent, single_indent)), ","), - Texts(indent, single_indent)); + Text_t item_text = fmt(item->ast, comments, Texts(indent, single_indent)); + if (Text$ends_with(code, Text(","), NULL)) { + if (!Text$has(item_text, Text("\n")) && trailing_line_len(code) + 1 + item_text.length + 1 <= MAX_WIDTH) + code = Texts(code, " ", item_text, ","); + else code = Texts(code, "\n", indent, single_indent, item_text, ","); + } else { + add_line(&code, Texts(item_text, ","), 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, "|"); + return ast->tag == List ? Texts(code, "\n", indent, "]") : Texts(code, "\n", indent, "|"); } /*multiline*/ case Table: { if (inlined_fits) return inlined; DeclareMatch(table, ast, Table); - Text_t code = EMPTY_TEXT; + Text_t code = Texts("{"); const char *comment_pos = ast->start; for (ast_list_t *entry = table->entries; entry; entry = entry->next) { for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, entry->ast->start)).length > 0;) { add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), Texts(indent, single_indent)); } - add_line(&code, Texts(fmt(entry->ast, comments, Texts(indent, single_indent)), ","), - Texts(indent, single_indent)); + + Text_t entry_text = fmt(entry->ast, comments, Texts(indent, single_indent)); + if (Text$ends_with(code, Text(","), NULL)) { + if (!Text$has(entry_text, Text("\n")) + && trailing_line_len(code) + 1 + entry_text.length + 1 <= MAX_WIDTH) + code = Texts(code, " ", entry_text, ","); + else code = Texts(code, "\n", indent, single_indent, entry_text, ","); + } else { + add_line(&code, Texts(entry_text, ","), Texts(indent, single_indent)); + } + + add_line(&code, Texts(entry_text, ","), 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)); @@ -587,7 +613,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { if (table->default_value) code = Texts(code, ";\n", indent, single_indent, "default=", fmt(table->default_value, comments, indent)); - return Texts("{\n", indent, single_indent, code, "\n", indent, "}"); + return Texts(code, "\n", indent, "}"); } /*multiline*/ case TableEntry: { if (inlined_fits) return inlined; -- cgit v1.2.3 From 565527c9a6c5aa0161f421066ac2e07357b34571 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 13:54:42 -0400 Subject: Avoid inline ifs --- src/formatter/formatter.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index a120b9de..5e34960f 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -430,8 +430,6 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { } /*multiline*/ case If: { DeclareMatch(if_, ast, If); - if (inlined_fits && if_->else_body == NULL) return inlined; - Text_t code = Texts("if ", fmt(if_->condition, comments, indent), "\n", indent, single_indent, fmt(if_->body, comments, Texts(indent, single_indent))); if (if_->else_body) { -- cgit v1.2.3 From db4048734179c047d64284281f72a531f166f11e Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 13:57:48 -0400 Subject: Tweak arg formatting --- src/formatter/args.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/formatter/args.c b/src/formatter/args.c index 6bd07b28..8bbbbd58 100644 --- a/src/formatter/args.c +++ b/src/formatter/args.c @@ -13,7 +13,7 @@ OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments) { if (arg->name == NULL && arg->value) return must(format_inline_code(arg->value, comments)); Text_t code = Text$from_str(arg->name); if (arg->type) code = Texts(code, ":", must(format_type(arg->type))); - if (arg->value) code = Texts(code, " = ", must(format_inline_code(arg->value, comments))); + if (arg->value) code = Texts(code, "=", must(format_inline_code(arg->value, comments))); return code; } @@ -23,7 +23,7 @@ Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { if (arg->name == NULL && arg->value) return format_code(arg->value, comments, indent); Text_t code = Text$from_str(arg->name); if (arg->type) code = Texts(code, ":", format_type(arg->type)); - if (arg->value) code = Texts(code, " = ", format_code(arg->value, comments, indent)); + if (arg->value) code = Texts(code, "=", format_code(arg->value, comments, indent)); return code; } -- cgit v1.2.3 From 351a79b5d7c20f806b66fed19407677402ad0805 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 13:59:51 -0400 Subject: Formatting tweak for enums --- src/formatter/enums.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/formatter/enums.c b/src/formatter/enums.c index 7a89705d..893f055b 100644 --- a/src/formatter/enums.c +++ b/src/formatter/enums.c @@ -9,18 +9,25 @@ OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments) { if (range_has_comment(tag->start, tag->end, comments)) return NONE_TEXT; - Text_t code = Texts(Text$from_str(tag->name), "(", must(format_inline_args(tag->fields, comments))); - if (tag->secret) code = Texts(code, "; secret"); - return Texts(code, ")"); + Text_t code = Text$from_str(tag->name); + if (tag->fields || tag->secret) { + code = Texts(code, "(", must(format_inline_args(tag->fields, comments))); + if (tag->secret) code = Texts(code, "; secret"); + code = Texts(code, ")"); + } + return code; } Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent) { OptionalText_t inline_tag = format_inline_tag(tag, comments); if (inline_tag.length >= 0) return inline_tag; - Text_t code = - Texts(Text$from_str(tag->name), "(", format_args(tag->fields, comments, Texts(indent, single_indent))); - if (tag->secret) code = Texts(code, "; secret"); - return Texts(code, ")"); + Text_t code = Text$from_str(tag->name); + if (tag->fields || tag->secret) { + code = Texts(code, "(", format_args(tag->fields, comments, Texts(indent, single_indent))); + if (tag->secret) code = Texts(code, "; secret"); + code = Texts(code, ")"); + } + return code; } OptionalText_t format_inline_tags(tag_ast_t *tags, Table_t comments) { -- cgit v1.2.3 From 08a85f2450d9ccaced45322b63c8ac234c2df9e4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:11:02 -0400 Subject: Use `"$var"` for interps instead of `"$(var)"` when possible --- src/formatter/formatter.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 5e34960f..e22fc609 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -4,12 +4,14 @@ #include #include #include +#include #include "../ast.h" #include "../parse/context.h" #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/text.h" @@ -51,6 +53,11 @@ text_opts_t choose_text_options(ast_list_t *chunks) { return opts; } +static bool starts_with_id(Text_t text) { + List_t codepoints = Text$utf32_codepoints(Text$slice(text, I_small(1), I_small(1))); + return uc_is_property_xid_continue(*(ucs4_t *)codepoints.data); +} + static OptionalText_t format_inline_text(text_opts_t opts, ast_list_t *chunks, Table_t comments) { Text_t code = opts.quote; for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) { @@ -59,7 +66,13 @@ static OptionalText_t format_inline_text(text_opts_t opts, ast_list_t *chunks, T Text_t segment = Text$escaped(literal, false, Texts(opts.unquote, opts.interp)); code = Texts(code, segment); } else { - code = Texts(code, opts.interp, "(", fmt_inline(chunk->ast, comments), ")"); + if (chunk->ast->tag == Var + && (!chunk->next || chunk->next->ast->tag != TextLiteral + || !starts_with_id(Match(chunk->next->ast, TextLiteral)->text))) { + code = Texts(code, opts.interp, fmt_inline(chunk->ast, comments)); + } else { + code = Texts(code, opts.interp, "(", fmt_inline(chunk->ast, comments), ")"); + } } } return Texts(code, opts.unquote); -- cgit v1.2.3 From 9f17271a07f10f05e63e0064d8ab4a92267f5f66 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:14:42 -0400 Subject: Use `unless` instead of `if not` --- src/formatter/formatter.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index e22fc609..8b8aeb1b 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -140,17 +140,21 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { /*inline*/ case If: { DeclareMatch(if_, ast, If); + Text_t if_condition = if_->condition->tag == Not + ? Texts("unless ", fmt_inline(Match(if_->condition, Not)->value, comments)) + : Texts("if ", fmt_inline(if_->condition, comments)); + if (if_->else_body == NULL && if_->condition->tag != Declare) { ast_t *stmt = unwrap_block(if_->body); switch (stmt->tag) { case Return: case Skip: - case Stop: return Texts(fmt_inline(stmt, comments), " if ", fmt_inline(if_->condition, comments)); + case Stop: return Texts(fmt_inline(stmt, comments), " ", if_condition); default: break; } } - Text_t code = Texts("if ", fmt_inline(if_->condition, comments), " then ", fmt_inline(if_->body, comments)); + Text_t code = Texts(if_condition, " then ", fmt_inline(if_->body, comments)); if (if_->else_body) code = Texts(code, " else ", fmt_inline(if_->else_body, comments)); return code; } @@ -443,8 +447,11 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { } /*multiline*/ case If: { DeclareMatch(if_, ast, If); - Text_t code = Texts("if ", fmt(if_->condition, comments, indent), "\n", indent, single_indent, - fmt(if_->body, comments, Texts(indent, single_indent))); + Text_t code = if_->condition->tag == Not + ? Texts("unless ", fmt(Match(if_->condition, Not)->value, comments, indent)) + : Texts("if ", fmt(if_->condition, comments, indent)); + + code = Texts(code, "\n", indent, single_indent, fmt(if_->body, comments, Texts(indent, single_indent))); if (if_->else_body) { if (if_->else_body->tag != If) { code = Texts(code, "\n", indent, "else\n", indent, single_indent, -- cgit v1.2.3 From f2ad722780f96dc5fb7d93f536c7267741be3f6f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:29:50 -0400 Subject: Fix line gaps --- src/formatter/formatter.c | 6 +----- src/formatter/utils.c | 36 +++++++++++++++++++++++++++++------- src/formatter/utils.h | 2 +- 3 files changed, 31 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 8b8aeb1b..98f0f59b 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -414,9 +414,6 @@ 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) { - for (int blanks = suggested_blank_lines(stmt->ast); blanks > 0; blanks--) - add_line(&code, Text(""), indent); - for (OptionalText_t comment; (comment = next_comment(comments, &comment_pos, stmt->ast->start)).length > 0;) { if (gap_before_comment) { @@ -430,8 +427,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { comment_pos = stmt->ast->end; if (stmt->next) { - for (int blanks = suggested_blank_lines(stmt->ast) - suggested_blank_lines(stmt->next->ast); blanks > 0; - blanks--) + for (int blanks = suggested_blank_lines(stmt->ast, stmt->next->ast); blanks > 0; blanks--) add_line(&code, Text(""), indent); } else gap_before_comment = true; } diff --git a/src/formatter/utils.c b/src/formatter/utils.c index 1f4e64d8..5619e6ce 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -38,24 +38,45 @@ bool range_has_comment(const char *start, const char *end, Table_t comments) { return (comment.length >= 0); } -CONSTFUNC int suggested_blank_lines(ast_t *ast) { - switch (ast->tag) { +CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second) { + if (second == NULL) return 0; + + if (first->tag == Use && second->tag != Use) return 1; + + switch (first->tag) { + case If: + case When: + case Repeat: + case While: + case For: + case Block: + case Defer: + case ConvertDef: + case FunctionDef: + case StructDef: + case EnumDef: + case LangDef: + case Extend: return 1; + default: break; + } + + switch (second->tag) { case If: case When: case Repeat: case While: case For: case Block: - case Defer: return 1; - case Use: return 1; + case Defer: case ConvertDef: - case FunctionDef: return 1; + case FunctionDef: case StructDef: case EnumDef: case LangDef: - case Extend: return 2; - default: return 0; + case Extend: return 1; + default: break; } + return 0; } Text_t indent_code(Text_t code) { @@ -73,6 +94,7 @@ CONSTFUNC ast_t *unwrap_block(ast_t *ast) { while (ast->tag == Block && Match(ast, Block)->statements && Match(ast, Block)->statements->next == NULL) { ast = Match(ast, Block)->statements->ast; } + if (ast->tag == Block && Match(ast, Block)->statements == NULL) return NULL; return ast; } diff --git a/src/formatter/utils.h b/src/formatter/utils.h index 0a00be6c..880da0a9 100644 --- a/src/formatter/utils.h +++ b/src/formatter/utils.h @@ -22,7 +22,7 @@ extern const Text_t single_indent; void add_line(Text_t *code, Text_t line, Text_t indent); OptionalText_t next_comment(Table_t comments, const char **pos, const char *end); bool range_has_comment(const char *start, const char *end, Table_t comments); -CONSTFUNC int suggested_blank_lines(ast_t *ast); +CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second); Text_t indent_code(Text_t code); Text_t parenthesize(Text_t code, Text_t indent); CONSTFUNC ast_t *unwrap_block(ast_t *ast); -- cgit v1.2.3 From 430b1f0a7df8055027fbfe006a7285cc9438e5ac Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:31:25 -0400 Subject: Fix indent for doctests --- src/formatter/formatter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 98f0f59b..675a88a9 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -774,11 +774,11 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case DocTest: { DeclareMatch(test, ast, DocTest); Text_t expr = fmt(test->expr, comments, indent); - Text_t code = Texts(">> ", Text$replace(expr, Texts("\n", indent), Texts("\n", indent, ".. "))); + Text_t code = Texts(">> ", Text$replace(expr, Texts("\n", indent), Texts("\n", indent, single_indent))); if (test->expected) { Text_t expected = fmt(test->expected, comments, indent); code = Texts(code, "\n", indent, "= ", - Text$replace(expected, Texts("\n", indent), Texts("\n", indent, ".. "))); + Text$replace(expected, Texts("\n", indent), Texts("\n", indent, single_indent))); } return code; } -- cgit v1.2.3 From a9df553f3765710c6833fe9705832b1a3bc5b64c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:32:25 -0400 Subject: Further fix for doctests --- src/formatter/formatter.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 675a88a9..88eb210a 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -774,11 +774,10 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case DocTest: { DeclareMatch(test, ast, DocTest); Text_t expr = fmt(test->expr, comments, indent); - Text_t code = Texts(">> ", Text$replace(expr, Texts("\n", indent), Texts("\n", indent, single_indent))); + Text_t code = Texts(">> ", expr); if (test->expected) { Text_t expected = fmt(test->expected, comments, indent); - code = Texts(code, "\n", indent, "= ", - Text$replace(expected, Texts("\n", indent), Texts("\n", indent, single_indent))); + code = Texts(code, "\n", indent, "= ", expected); } return code; } -- cgit v1.2.3 From 1824f840c1564c59eaecfaa25556d4e830d867bc Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:36:28 -0400 Subject: Add 'do' wrapper on blocks --- src/formatter/formatter.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 88eb210a..266525e8 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -423,7 +423,13 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { add_line(&code, Text$trim(comment, Text(" \t\r\n"), false, true), indent); } - add_line(&code, fmt(stmt->ast, comments, indent), indent); + if (stmt->ast->tag == Block) { + add_line(&code, + Texts("do\n", indent, single_indent, fmt(stmt->ast, comments, Texts(indent, single_indent))), + indent); + } else { + add_line(&code, fmt(stmt->ast, comments, indent), indent); + } comment_pos = stmt->ast->end; if (stmt->next) { -- cgit v1.2.3 From ca3b72d1f5a230cc5b25edd55918864c2df248b5 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 14:44:13 -0400 Subject: Tweaks for blank lines --- src/formatter/utils.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/utils.c b/src/formatter/utils.c index 5619e6ce..80445ec3 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -41,7 +41,21 @@ bool range_has_comment(const char *start, const char *end, Table_t comments) { CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second) { if (second == NULL) return 0; - if (first->tag == Use && second->tag != Use) return 1; + for (;;) { + if (first->tag == Declare && Match(first, Declare)->value) { + first = Match(first, Declare)->value; + } else if (first->tag == DocTest && Match(first, DocTest)->expr && Match(first, DocTest)->expected == NULL) { + first = Match(first, DocTest)->expr; + } else break; + } + + for (;;) { + if (second->tag == Declare && Match(second, Declare)->value) { + second = Match(second, Declare)->value; + } else if (second->tag == DocTest && Match(second, DocTest)->expr && Match(second, DocTest)->expected == NULL) { + second = Match(second, DocTest)->expr; + } else break; + } switch (first->tag) { case If: @@ -53,10 +67,27 @@ CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second) { case Defer: case ConvertDef: case FunctionDef: + case Lambda: case StructDef: case EnumDef: case LangDef: case Extend: return 1; + case Use: { + if (second->tag != Use) return 1; + break; + } + case Declare: { + DeclareMatch(decl, first, Declare); + if (decl->value) return suggested_blank_lines(decl->value, second); + break; + } + case Assign: { + DeclareMatch(assign, first, Assign); + for (ast_list_t *val = assign->values; val; val = val->next) { + if (suggested_blank_lines(val->ast, second) > 0) return 1; + } + break; + } default: break; } @@ -70,6 +101,7 @@ CONSTFUNC int suggested_blank_lines(ast_t *first, ast_t *second) { case Defer: case ConvertDef: case FunctionDef: + case Lambda: case StructDef: case EnumDef: case LangDef: -- cgit v1.2.3 From 688b0ae7bd433c4d7daf96505581f90e719f033c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 15:04:44 -0400 Subject: Formatting tweaks to argument lists --- src/formatter/args.c | 3 ++- src/formatter/formatter.c | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/formatter/args.c b/src/formatter/args.c index 8bbbbd58..34d234df 100644 --- a/src/formatter/args.c +++ b/src/formatter/args.c @@ -49,7 +49,8 @@ Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { code = Texts(code, Text$from_str(args->name), ","); } else { - add_line(&code, Texts(format_arg(args, comments, indent), ","), indent); + code = Texts(code, "\n", indent, single_indent, + format_arg(args, comments, Texts(indent, single_indent, single_indent)), ","); } } return code; diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 266525e8..addf8445 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -526,7 +526,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { if (func->ret_type) code = Texts(code, func->args ? Text(" -> ") : Text("-> "), format_type(func->ret_type)); if (func->cache) code = Texts(code, "; cache=", fmt(func->cache, comments, indent)); if (func->is_inline) code = Texts(code, "; inline"); - code = Texts(code, ")\n", indent, single_indent, fmt(func->body, comments, Texts(indent, single_indent))); + code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent, + single_indent, fmt(func->body, comments, Texts(indent, single_indent))); return Texts(code); } /*multiline*/ case Lambda: { @@ -535,7 +536,8 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { Text_t code = Texts("func(", format_args(lambda->args, comments, indent)); if (lambda->ret_type) code = Texts(code, lambda->args ? Text(" -> ") : Text("-> "), format_type(lambda->ret_type)); - code = Texts(code, ")\n", indent, single_indent, fmt(lambda->body, comments, Texts(indent, single_indent))); + code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent, + single_indent, fmt(lambda->body, comments, Texts(indent, single_indent))); return Texts(code); } /*multiline*/ case ConvertDef: { @@ -545,21 +547,25 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { code = Texts(code, convert->args ? Text(" -> ") : Text("-> "), format_type(convert->ret_type)); if (convert->cache) code = Texts(code, "; cache=", fmt(convert->cache, comments, indent)); if (convert->is_inline) code = Texts(code, "; inline"); - code = Texts(code, ")\n", indent, single_indent, fmt(convert->body, comments, Texts(indent, single_indent))); + code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), "\n", indent, + single_indent, fmt(convert->body, comments, Texts(indent, single_indent))); return Texts(code); } /*multiline*/ case StructDef: { DeclareMatch(def, ast, StructDef); - Text_t code = Texts("struct ", Text$from_str(def->name), "(", format_args(def->fields, comments, indent)); + Text_t args = format_args(def->fields, comments, indent); + Text_t code = Texts("struct ", Text$from_str(def->name), "(", args); if (def->secret) code = Texts(code, "; secret"); if (def->external) code = Texts(code, "; external"); if (def->opaque) code = Texts(code, "; opaque"); - return Texts(code, ")", format_namespace(def->namespace, comments, indent)); + code = Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")")); + return Texts(code, format_namespace(def->namespace, comments, indent)); } /*multiline*/ case EnumDef: { DeclareMatch(def, ast, EnumDef); Text_t code = Texts("enum ", Text$from_str(def->name), "(", format_tags(def->tags, comments, indent)); - return Texts(code, ")", format_namespace(def->namespace, comments, indent)); + return Texts(code, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"), + format_namespace(def->namespace, comments, indent)); } /*multiline*/ case LangDef: { DeclareMatch(def, ast, LangDef); @@ -769,13 +775,13 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { if (inlined_fits) return inlined; DeclareMatch(call, ast, FunctionCall); return Texts(fmt(call->fn, comments, indent), "(\n", indent, single_indent, - format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); + format_args(call->args, comments, indent), "\n", Texts(indent, single_indent), ")"); } /*multiline*/ case MethodCall: { if (inlined_fits) return inlined; DeclareMatch(call, ast, MethodCall); return Texts(termify(call->self, comments, indent), ".", Text$from_str(call->name), "(\n", indent, - single_indent, format_args(call->args, comments, Texts(indent, single_indent)), "\n", indent, ")"); + single_indent, format_args(call->args, comments, indent), "\n", Texts(indent, single_indent), ")"); } /*multiline*/ case DocTest: { DeclareMatch(test, ast, DocTest); -- cgit v1.2.3 From f38fa3389c881ef8341011c05d7b2435686068e7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 15:54:15 -0400 Subject: Arg indentation tweak --- src/formatter/args.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/formatter/args.c b/src/formatter/args.c index 34d234df..9809645f 100644 --- a/src/formatter/args.c +++ b/src/formatter/args.c @@ -49,8 +49,8 @@ Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { code = Texts(code, Text$from_str(args->name), ","); } else { - code = Texts(code, "\n", indent, single_indent, - format_arg(args, comments, Texts(indent, single_indent, single_indent)), ","); + code = + Texts(code, "\n", indent, single_indent, format_arg(args, comments, Texts(indent, single_indent)), ","); } } return code; -- cgit v1.2.3 From 9090a2a09f119510f40c5385f3790cc6b6539abc Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 15:59:57 -0400 Subject: Arg formatting tweaks --- src/formatter/args.c | 22 +++++++++++----------- src/formatter/formatter.c | 10 ++++++---- 2 files changed, 17 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/formatter/args.c b/src/formatter/args.c index 9809645f..997a1e39 100644 --- a/src/formatter/args.c +++ b/src/formatter/args.c @@ -29,14 +29,14 @@ Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent) { OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments) { Text_t code = EMPTY_TEXT; - for (; args; args = args->next) { - if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { - code = Texts(code, Text$from_str(args->name), ","); + for (arg_ast_t *arg = args; arg; arg = arg->next) { + if (arg->name && arg->next && arg->type == arg->next->type && arg->value == arg->next->value) { + code = Texts(code, Text$from_str(arg->name), ","); } else { - code = Texts(code, must(format_inline_arg(args, comments))); - if (args->next) code = Texts(code, ", "); + code = Texts(code, must(format_inline_arg(arg, comments))); + if (arg->next) code = Texts(code, ", "); } - if (args->next && range_has_comment(args->end, args->next->start, comments)) return NONE_TEXT; + if (arg->next && range_has_comment(arg->end, arg->next->start, comments)) return NONE_TEXT; } return code; } @@ -45,12 +45,12 @@ Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent) { OptionalText_t inline_args = format_inline_args(args, comments); if (inline_args.length >= 0 && inline_args.length <= MAX_WIDTH) return inline_args; Text_t code = EMPTY_TEXT; - for (; args; args = args->next) { - if (args->name && args->next && args->type == args->next->type && args->value == args->next->value) { - code = Texts(code, Text$from_str(args->name), ","); + for (arg_ast_t *arg = args; arg; arg = arg->next) { + if (arg->name && arg->next && arg->type == arg->next->type && arg->value == arg->next->value) { + code = Texts(code, Text$from_str(arg->name), ","); } else { - code = - Texts(code, "\n", indent, single_indent, format_arg(args, comments, Texts(indent, single_indent)), ","); + code = Texts(code, "\n", indent, single_indent, format_arg(arg, comments, Texts(indent, single_indent))); + if (args->next) code = Texts(code, ","); } } return code; diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index addf8445..1719a294 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -774,14 +774,16 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case FunctionCall: { if (inlined_fits) return inlined; DeclareMatch(call, ast, FunctionCall); - return Texts(fmt(call->fn, comments, indent), "(\n", indent, single_indent, - format_args(call->args, comments, indent), "\n", Texts(indent, single_indent), ")"); + Text_t args = format_args(call->args, comments, indent); + return Texts(fmt(call->fn, comments, indent), "(", args, + Text$has(args, Text("\n")) ? Texts("\n", indent) : EMPTY_TEXT, ")"); } /*multiline*/ case MethodCall: { if (inlined_fits) return inlined; DeclareMatch(call, ast, MethodCall); - return Texts(termify(call->self, comments, indent), ".", Text$from_str(call->name), "(\n", indent, - single_indent, format_args(call->args, comments, indent), "\n", Texts(indent, single_indent), ")"); + Text_t args = format_args(call->args, comments, indent); + return Texts(termify(call->self, comments, indent), ".", Text$from_str(call->name), "(", args, + Text$has(args, Text("\n")) ? Texts("\n", indent) : EMPTY_TEXT, ")"); } /*multiline*/ case DocTest: { DeclareMatch(test, ast, DocTest); -- cgit v1.2.3 From b025a65b45e8f2e8a56eb087743f9e9e7109b9f8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 16:02:17 -0400 Subject: Tweak empty gaps --- src/formatter/formatter.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 1719a294..824a936a 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -433,8 +433,10 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { comment_pos = stmt->ast->end; if (stmt->next) { - for (int blanks = suggested_blank_lines(stmt->ast, stmt->next->ast); blanks > 0; blanks--) + int suggested_blanks = suggested_blank_lines(stmt->ast, stmt->next->ast); + for (int blanks = suggested_blanks; blanks > 0; blanks--) add_line(&code, Text(""), indent); + gap_before_comment = (suggested_blanks == 0); } else gap_before_comment = true; } -- cgit v1.2.3 From 500ea505a88233b81a0e22a99efe62a868f5d449 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 31 Aug 2025 15:36:02 -0400 Subject: Merge fixes --- src/parse/functions.c | 2 +- src/tomo.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/parse/functions.c b/src/parse/functions.c index 6cb085b7..826fdbef 100644 --- a/src/parse/functions.c +++ b/src/parse/functions.c @@ -41,7 +41,7 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { const char *alias = NULL; if (match(pos, "|")) { - whitespace(pos); + whitespace(ctx, pos); alias = get_id(pos); if (!alias) parser_err(ctx, *pos, *pos, "I expected an argument alias after `|`"); } diff --git a/src/tomo.c b/src/tomo.c index 433597b0..cbb8a1b5 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -25,6 +25,7 @@ #include "parse/files.h" #include "stdlib/bools.h" #include "stdlib/bytes.h" +#include "stdlib/cli.h" #include "stdlib/datatypes.h" #include "stdlib/integers.h" #include "stdlib/lists.h" -- cgit v1.2.3 From db6107c33df6a4fdb592ad29074d1159c37dc048 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 31 Aug 2025 16:22:29 -0400 Subject: Bugfix for argument aliases (merge issue) --- src/parse/functions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/parse/functions.c b/src/parse/functions.c index 826fdbef..ceb0a8bc 100644 --- a/src/parse/functions.c +++ b/src/parse/functions.c @@ -76,7 +76,7 @@ arg_ast_t *parse_args(parse_ctx_t *ctx, const char **pos) { REVERSE_LIST(names); for (; names; names = names->next) - args = new (arg_ast_t, .start = names->start, .end = names->end, .name = names->name, .alias = names->name, + args = new (arg_ast_t, .start = names->start, .end = names->end, .name = names->name, .alias = names->alias, .type = type, .value = default_val, .next = args); if (!match_separator(ctx, pos)) break; -- cgit v1.2.3 From 0eae0e70b3475ab2cf3d8486dc3a609a22faa75a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 17:41:55 -0400 Subject: Fixes --- src/ast.c | 2 +- src/formatter/args.h | 2 -- src/formatter/formatter.c | 5 +++-- src/formatter/utils.c | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 486e0262..5c55930e 100644 --- a/src/ast.c +++ b/src/ast.c @@ -323,7 +323,7 @@ const char *ast_to_sexp_str(ast_t *ast) { return Text$as_c_string(ast_to_sexp(as OptionalText_t ast_source(ast_t *ast) { if (ast == NULL || ast->start == NULL || ast->end == NULL) return NONE_TEXT; - return Text$from_strn(ast->start, (int64_t)(ast->end - ast->start)); + return Text$from_strn(ast->start, (size_t)(ast->end - ast->start)); } PUREFUNC bool is_idempotent(ast_t *ast) { diff --git a/src/formatter/args.h b/src/formatter/args.h index 42722f32..c902684b 100644 --- a/src/formatter/args.h +++ b/src/formatter/args.h @@ -9,5 +9,3 @@ OptionalText_t format_inline_arg(arg_ast_t *arg, Table_t comments); Text_t format_arg(arg_ast_t *arg, Table_t comments, Text_t indent); OptionalText_t format_inline_args(arg_ast_t *args, Table_t comments); Text_t format_args(arg_ast_t *args, Table_t comments, Text_t indent); -OptionalText_t format_inline_tag(tag_ast_t *tag, Table_t comments); -Text_t format_tag(tag_ast_t *tag, Table_t comments, Text_t indent); diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 824a936a..60714945 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -33,7 +33,7 @@ typedef struct { Text_t quote, unquote, interp; } text_opts_t; -text_opts_t choose_text_options(ast_list_t *chunks) { +PUREFUNC text_opts_t choose_text_options(ast_list_t *chunks) { int double_quotes = 0, single_quotes = 0, backticks = 0; for (ast_list_t *chunk = chunks; chunk; chunk = chunk->next) { if (chunk->ast->tag == TextLiteral) { @@ -146,6 +146,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (if_->else_body == NULL && if_->condition->tag != Declare) { ast_t *stmt = unwrap_block(if_->body); + if (!stmt) return Texts("pass ", if_condition); switch (stmt->tag) { case Return: case Skip: @@ -391,7 +392,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } } -static int64_t trailing_line_len(Text_t text) { +PUREFUNC static int64_t trailing_line_len(Text_t text) { TextIter_t state = NEW_TEXT_ITER_STATE(text); int64_t len = 0; for (int64_t i = text.length - 1; i >= 0; i--) { diff --git a/src/formatter/utils.c b/src/formatter/utils.c index 80445ec3..bbe74d7f 100644 --- a/src/formatter/utils.c +++ b/src/formatter/utils.c @@ -27,7 +27,7 @@ OptionalText_t next_comment(Table_t comments, const char **pos, const char *end) const char **comment_end = Table$get(comments, &p, parse_comments_info); if (comment_end) { *pos = *comment_end; - return Text$from_strn(p, (int64_t)(*comment_end - p)); + return Text$from_strn(p, (size_t)(*comment_end - p)); } } return NONE_TEXT; -- cgit v1.2.3 From 2811873bd7778c923238b916ed94419e50945d88 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 19:40:08 -0400 Subject: Explicit type casting for size --- src/tomo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tomo.c b/src/tomo.c index e7c943d7..e6636cfc 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -244,7 +244,7 @@ int main(int argc, char *argv[]) { } if (show_changelog) { - print_inline(string_slice((const char *)CHANGES_md, CHANGES_md_len)); + print_inline(string_slice((const char *)CHANGES_md, (size_t)CHANGES_md_len)); return 0; } -- cgit v1.2.3 From 94ff047dd74cd3ad793f68503729a0fe004c10f4 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 20:15:02 -0400 Subject: Bugfix for Int.parse() --- src/stdlib/integers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/stdlib/integers.c b/src/stdlib/integers.c index 5dc9ac55..863bb42d 100644 --- a/src/stdlib/integers.c +++ b/src/stdlib/integers.c @@ -430,7 +430,7 @@ OptionalInt_t Int$parse(Text_t text, Text_t *remainder) { else if (*end != '\0') return NONE_INT; result = mpz_init_set_str(i, str + 2, 2); } else { - const char *end = str + 2 + strspn(str + 2, "0123456789"); + const char *end = str + strspn(str, "0123456789"); if (remainder) *remainder = Text$from_str(end); else if (*end != '\0') return NONE_INT; result = mpz_init_set_str(i, str, 10); -- cgit v1.2.3 From aa15f0f78214cefa9fabace61c119e01812a3050 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Sep 2025 13:51:18 -0400 Subject: Refactor a bit in order to catch issue with ConvertDefs creating infinite loops --- src/compile/files.c | 6 +-- src/compile/functions.c | 20 ++-------- src/compile/statements.c | 22 +++++++---- src/environment.h | 2 +- src/typecheck.c | 95 +++++++++++++++++++++++++++--------------------- src/typecheck.h | 3 +- 6 files changed, 79 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/src/compile/files.c b/src/compile/files.c index a6af2300..c250e6cc 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -95,13 +95,13 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) { return compile_function(env, name_code, ast, &env->code->staticdefs); } case ConvertDef: { - type_t *type = get_function_def_type(env, ast); - const char *name = get_type_name(Match(type, FunctionType)->ret); + type_t *type = get_function_return_type(env, ast); + const char *name = get_type_name(type); if (!name) code_err(ast, "Conversions are only supported for text, struct, and enum " "types, not ", - type_to_str(Match(type, FunctionType)->ret)); + type_to_str(type)); Text_t name_code = namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start))); return compile_function(env, name_code, ast, &env->code->staticdefs); diff --git a/src/compile/functions.c b/src/compile/functions.c index 6caefa8b..f04a3b59 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -256,18 +256,7 @@ Text_t compile_lambda(env_t *env, ast_t *ast) { set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); } - type_t *ret_t = get_type(body_scope, lambda->body); - if (ret_t->tag == ReturnType) ret_t = Match(ret_t, ReturnType)->ret; - - if (lambda->ret_type) { - type_t *declared = parse_type_ast(env, lambda->ret_type); - if (can_promote(ret_t, declared)) ret_t = declared; - else - code_err(ast, "This function was declared to return a value of type ", type_to_str(declared), - ", but actually returns a value of type ", type_to_str(ret_t)); - } - - body_scope->fn_ret = ret_t; + body_scope->fn = ast; Table_t closed_vars = get_closed_vars(env, lambda->args, ast); if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata @@ -289,6 +278,7 @@ Text_t compile_lambda(env_t *env, ast_t *ast) { env->code->local_typedefs = Texts(env->code->local_typedefs, def); } + type_t *ret_t = get_function_return_type(env, ast); Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { type_t *arg_type = get_arg_ast_type(env, arg); @@ -623,7 +613,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static bool is_private = false; const char *function_name; arg_ast_t *args; - type_t *ret_t; + type_t *ret_t = get_function_return_type(env, ast); ast_t *body; ast_t *cache; bool is_inline; @@ -632,14 +622,12 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static function_name = Match(fndef->name, Var)->name; is_private = function_name[0] == '_'; args = fndef->args; - ret_t = fndef->ret_type ? parse_type_ast(env, fndef->ret_type) : Type(VoidType); body = fndef->body; cache = fndef->cache; is_inline = fndef->is_inline; } else { DeclareMatch(convertdef, ast, ConvertDef); args = convertdef->args; - ret_t = convertdef->ret_type ? parse_type_ast(env, convertdef->ret_type) : Type(VoidType); function_name = get_type_name(ret_t); if (!function_name) code_err(ast, @@ -689,7 +677,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); } - body_scope->fn_ret = ret_t; + body_scope->fn = ast; type_t *body_type = get_type(body_scope, body); if (ret_t->tag == AbortType) { diff --git a/src/compile/statements.c b/src/compile/statements.c index a7c5214a..4e37838c 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -131,7 +131,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { return code; } case Return: { - if (!env->fn_ret) code_err(ast, "This return statement is not inside any function"); + if (!env->fn) code_err(ast, "This return statement is not inside any function"); ast_t *ret = Match(ast, Return)->value; Text_t code = EMPTY_TEXT; @@ -139,22 +139,30 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { code = Texts(code, compile_statement(deferred->defer_env, deferred->block)); } + type_t *ret_type = get_function_return_type(env, env->fn); if (ret) { - if (env->fn_ret->tag == VoidType || env->fn_ret->tag == AbortType) + if (ret_type->tag == VoidType || ret_type->tag == AbortType) code_err(ast, "This function is not supposed to return any values, " "according to its type signature"); - env = with_enum_scope(env, env->fn_ret); - Text_t value = compile_to_type(env, ret, env->fn_ret); + env = with_enum_scope(env, ret_type); + if (env->fn->tag == ConvertDef) { + type_t *value_type = get_type(env, ret); + if (!type_eq(value_type, ret_type)) { + code_err(ret, "This value is a ", type_to_text(value_type), + " but this conversion needs an explicit ", type_to_text(ret_type)); + } + } + Text_t value = compile_to_type(env, ret, ret_type); if (env->deferred) { - code = Texts(compile_declaration(env->fn_ret, Text("ret")), " = ", value, ";\n", code); + code = Texts(compile_declaration(ret_type, Text("ret")), " = ", value, ";\n", code); value = Text("ret"); } return Texts(code, "return ", value, ";"); } else { - if (env->fn_ret->tag != VoidType) - code_err(ast, "This function expects you to return a ", type_to_str(env->fn_ret), " value"); + if (ret_type->tag != VoidType) + code_err(ast, "This function expects you to return a ", type_to_text(ret_type), " value"); return Texts(code, "return;"); } } diff --git a/src/environment.h b/src/environment.h index 1ef9c1f9..c726508d 100644 --- a/src/environment.h +++ b/src/environment.h @@ -43,7 +43,7 @@ typedef struct env_s { Text_t id_suffix; Table_t *imports; compilation_unit_t *code; - type_t *fn_ret; + ast_t *fn; loop_ctx_t *loop_ctx; deferral_t *deferred; Closure_t *comprehension_action; diff --git a/src/typecheck.c b/src/typecheck.c index eff88d40..2d805cdb 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -336,12 +336,12 @@ void bind_statement(env_t *env, ast_t *statement) { case FunctionDef: { DeclareMatch(def, statement, FunctionDef); const char *name = Match(def->name, Var)->name; - type_t *type = get_function_def_type(env, statement); + type_t *type = get_function_type(env, statement); set_binding(env, name, type, namespace_name(env, env->namespace, Text$from_str(name))); break; } case ConvertDef: { - type_t *type = get_function_def_type(env, statement); + type_t *type = get_function_type(env, statement); type_t *ret_t = Match(type, FunctionType)->ret; const char *name = get_type_name(ret_t); if (!name) @@ -577,10 +577,24 @@ void bind_statement(env_t *env, ast_t *statement) { } } -type_t *get_function_def_type(env_t *env, ast_t *ast) { - arg_ast_t *arg_asts = ast->tag == FunctionDef ? Match(ast, FunctionDef)->args : Match(ast, ConvertDef)->args; - type_ast_t *ret_type = - ast->tag == FunctionDef ? Match(ast, FunctionDef)->ret_type : Match(ast, ConvertDef)->ret_type; +type_t *get_function_type(env_t *env, ast_t *ast) { + arg_ast_t *arg_asts; + type_ast_t *ret_ast; + switch (ast->tag) { + case FunctionDef: + arg_asts = Match(ast, FunctionDef)->args; + ret_ast = Match(ast, FunctionDef)->ret_type; + break; + case ConvertDef: + arg_asts = Match(ast, ConvertDef)->args; + ret_ast = Match(ast, ConvertDef)->ret_type; + break; + case Lambda: + arg_asts = Match(ast, Lambda)->args; + ret_ast = Match(ast, Lambda)->ret_type; + break; + default: code_err(ast, "This was expected to be a function definition of some sort"); + } arg_t *args = NULL; env_t *scope = fresh_scope(env); for (arg_ast_t *arg = arg_asts; arg; arg = arg->next) { @@ -590,10 +604,38 @@ type_t *get_function_def_type(env_t *env, ast_t *ast) { } REVERSE_LIST(args); - type_t *ret = ret_type ? parse_type_ast(scope, ret_type) : Type(VoidType); - if (has_stack_memory(ret)) - code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame."); - return Type(FunctionType, .args = args, .ret = ret); + if (ast->tag == Lambda) { + ast_t *body = Match(ast, Lambda)->body; + type_t *ret_t = get_type(scope, body); + if (ret_t->tag == ReturnType) ret_t = Match(ret_t, ReturnType)->ret; + if (ret_t->tag == AbortType) ret_t = Type(VoidType); + + if (ret_t->tag == OptionalType && !Match(ret_t, OptionalType)->type) + code_err(body, "This function doesn't return a specific optional type"); + + if (ret_ast) { + type_t *declared = parse_type_ast(env, ret_ast); + if (can_promote(ret_t, declared)) ret_t = declared; + else + code_err(ast, "This function was declared to return a value of type ", type_to_str(declared), + ", but actually returns a value of type ", type_to_str(ret_t)); + } + + if (has_stack_memory(ret_t)) + code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame."); + return Type(ClosureType, Type(FunctionType, .args = args, .ret = ret_t)); + } else { + type_t *ret_t = ret_ast ? parse_type_ast(scope, ret_ast) : Type(VoidType); + if (has_stack_memory(ret_t)) + code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame."); + return Type(FunctionType, .args = args, .ret = ret_t); + } +} + +type_t *get_function_return_type(env_t *env, ast_t *ast) { + type_t *fn_t = get_function_type(env, ast); + if (fn_t->tag == ClosureType) fn_t = Match(fn_t, ClosureType)->fn; + return Match(fn_t, FunctionType)->ret; } type_t *get_method_type(env_t *env, ast_t *self, const char *name) { @@ -1085,7 +1127,7 @@ type_t *get_type(env_t *env, ast_t *ast) { } case Return: { ast_t *val = Match(ast, Return)->value; - if (env->fn_ret) env = with_enum_scope(env, env->fn_ret); + if (env->fn) env = with_enum_scope(env, get_function_return_type(env, env->fn)); return Type(ReturnType, .ret = (val ? get_type(env, val) : Type(VoidType))); } case Stop: @@ -1394,36 +1436,7 @@ type_t *get_type(env_t *env, ast_t *ast) { return t; } - case Lambda: { - DeclareMatch(lambda, ast, Lambda); - arg_t *args = NULL; - env_t *scope = fresh_scope(env); // For now, just use closed variables in scope normally - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *t = get_arg_ast_type(env, arg); - args = new (arg_t, .name = arg->name, .alias = arg->alias, .type = t, .next = args); - set_binding(scope, arg->name, t, EMPTY_TEXT); - } - REVERSE_LIST(args); - - type_t *ret = get_type(scope, lambda->body); - if (ret->tag == ReturnType) ret = Match(ret, ReturnType)->ret; - if (ret->tag == AbortType) ret = Type(VoidType); - - if (ret->tag == OptionalType && !Match(ret, OptionalType)->type) - code_err(lambda->body, "This function doesn't return a specific optional type"); - - if (lambda->ret_type) { - type_t *declared = parse_type_ast(env, lambda->ret_type); - if (can_promote(ret, declared)) ret = declared; - else - code_err(ast, "This function was declared to return a value of type ", type_to_str(declared), - ", but actually returns a value of type ", type_to_str(ret)); - } - - if (has_stack_memory(ret)) - code_err(ast, "Functions can't return stack references because the reference may outlive its stack frame."); - return Type(ClosureType, Type(FunctionType, .args = args, .ret = ret)); - } + case Lambda: return get_function_type(env, ast); case FunctionDef: case ConvertDef: diff --git a/src/typecheck.h b/src/typecheck.h index 8fc30333..d64bb316 100644 --- a/src/typecheck.h +++ b/src/typecheck.h @@ -16,7 +16,8 @@ void prebind_statement(env_t *env, ast_t *statement); void bind_statement(env_t *env, ast_t *statement); PUREFUNC type_t *get_math_type(env_t *env, ast_t *ast, type_t *lhs_t, type_t *rhs_t); PUREFUNC bool is_discardable(env_t *env, ast_t *ast); -type_t *get_function_def_type(env_t *env, ast_t *ast); +type_t *get_function_type(env_t *env, ast_t *ast); +type_t *get_function_return_type(env_t *env, ast_t *ast); type_t *get_arg_type(env_t *env, arg_t *arg); type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg); env_t *when_clause_scope(env_t *env, type_t *subject_t, when_clause_t *clause); -- cgit v1.2.3 From cdb326a6e74e80bea9bffbeaaff1c5d1f4c9e733 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Sep 2025 14:34:38 -0400 Subject: Code cleanup --- src/ast.c | 121 ++++++++++++++++------------------------------ src/ast.h | 9 +++- src/compile/binops.c | 2 +- src/compile/files.c | 41 +++++++++------- src/compile/reductions.c | 2 +- src/compile/statements.c | 49 ++++++------------- src/environment.c | 6 ++- src/formatter/formatter.c | 13 ++--- src/parse/expressions.c | 2 +- src/typecheck.c | 4 +- 10 files changed, 104 insertions(+), 145 deletions(-) (limited to 'src') diff --git a/src/ast.c b/src/ast.c index 5c55930e..37c5a514 100644 --- a/src/ast.c +++ b/src/ast.c @@ -36,83 +36,46 @@ const int op_tightness[NUM_AST_TAGS] = { [Xor] = 1, }; -static Text_t quoted_text(const char *text) { return Text$quoted(Text$from_str(text), false, Text("\"")); } - -CONSTFUNC const char *binop_method_name(ast_e tag) { - switch (tag) { - case Power: - case PowerUpdate: return "power"; - case Multiply: - case MultiplyUpdate: return "times"; - case Divide: - case DivideUpdate: return "divided_by"; - case Mod: - case ModUpdate: return "modulo"; - case Mod1: - case Mod1Update: return "modulo1"; - case Plus: - case PlusUpdate: return "plus"; - case Minus: - case MinusUpdate: return "minus"; - case Concat: - case ConcatUpdate: return "concatenated_with"; - case LeftShift: - case LeftShiftUpdate: return "left_shifted"; - case RightShift: - case RightShiftUpdate: return "right_shifted"; - case UnsignedLeftShift: - case UnsignedLeftShiftUpdate: return "unsigned_left_shifted"; - case UnsignedRightShift: - case UnsignedRightShiftUpdate: return "unsigned_right_shifted"; - case And: - case AndUpdate: return "bit_and"; - case Or: - case OrUpdate: return "bit_or"; - case Xor: - case XorUpdate: return "bit_xor"; - default: return NULL; - } -} - -CONSTFUNC const char *binop_operator(ast_e tag) { - switch (tag) { - case Power: return "^"; - case PowerUpdate: return "^="; - case Concat: return "++"; - case ConcatUpdate: return "++="; - case Multiply: return "*"; - case MultiplyUpdate: return "*="; - case Divide: return "/"; - case DivideUpdate: return "/="; - case Mod: return "mod"; - case ModUpdate: return "mod="; - case Mod1: return "mod1"; - case Mod1Update: return "mod1="; - case Plus: return "+"; - case PlusUpdate: return "+="; - case Minus: return "-"; - case MinusUpdate: return "-="; - case LeftShift: return "<<"; - case LeftShiftUpdate: return "<<="; - case RightShift: return ">>"; - case RightShiftUpdate: return ">>="; - case And: return "and"; - case AndUpdate: return "and="; - case Or: return "or"; - case OrUpdate: return "or="; - case Xor: return "xor"; - case XorUpdate: return "xor="; - case Equals: return "=="; - case NotEquals: return "!="; - case LessThan: return "<"; - case LessThanOrEquals: return "<="; - case GreaterThan: return ">"; - case GreaterThanOrEquals: return ">="; - case Min: return "_min_"; - case Max: return "_max_"; - default: return NULL; - } -} +const binop_info_t binop_info[NUM_AST_TAGS] = { + [Power] = {"power", "^"}, + [PowerUpdate] = {"power", "^="}, + [Multiply] = {"times", "*"}, + [MultiplyUpdate] = {"times", "*="}, + [Divide] = {"divided_by", "/"}, + [DivideUpdate] = {"divided_by", "/="}, + [Mod] = {"modulo", "mod"}, + [ModUpdate] = {"modulo", "mod="}, + [Mod1] = {"modulo1", "mod1"}, + [Mod1Update] = {"modulo1", "mod1="}, + [Plus] = {"plus", "+"}, + [PlusUpdate] = {"plus", "+="}, + [Minus] = {"minus", "-"}, + [MinusUpdate] = {"minus", "-="}, + [Concat] = {"concatenated_with", "++"}, + [ConcatUpdate] = {"concatenated_with", "++="}, + [LeftShift] = {"left_shifted", "<<"}, + [LeftShiftUpdate] = {"left_shifted", "<<="}, + [RightShift] = {"right_shifted", ">>"}, + [RightShiftUpdate] = {"right_shifted", ">>="}, + [UnsignedLeftShift] = {"unsigned_left_shifted", NULL}, + [UnsignedLeftShiftUpdate] = {"unsigned_left_shifted", NULL}, + [UnsignedRightShift] = {"unsigned_right_shifted", NULL}, + [UnsignedRightShiftUpdate] = {"unsigned_right_shifted", NULL}, + [And] = {"bit_and", "and"}, + [AndUpdate] = {"bit_and", "and="}, + [Or] = {"bit_or", "or"}, + [OrUpdate] = {"bit_or", "or="}, + [Xor] = {"bit_xor", "xor"}, + [XorUpdate] = {"bit_xor", "xor="}, + [Equals] = {NULL, "=="}, + [NotEquals] = {NULL, "!="}, + [LessThan] = {NULL, "<"}, + [LessThanOrEquals] = {NULL, "<="}, + [GreaterThan] = {NULL, ">"}, + [GreaterThanOrEquals] = {NULL, ">="}, + [Min] = {NULL, "_min_"}, + [Max] = {NULL, "_max_"}, +}; static Text_t ast_list_to_sexp(ast_list_t *asts); static Text_t arg_list_to_sexp(arg_ast_t *args); @@ -122,6 +85,8 @@ static Text_t tags_to_sexp(tag_ast_t *tags); static Text_t optional_sexp(const char *tag, ast_t *ast); static Text_t optional_type_sexp(const char *tag, type_ast_t *ast); +static Text_t quoted_text(const char *text) { return Text$quoted(Text$from_str(text), false, Text("\"")); } + Text_t ast_list_to_sexp(ast_list_t *asts) { Text_t c = EMPTY_TEXT; for (; asts; asts = asts->next) { @@ -291,7 +256,7 @@ Text_t ast_to_sexp(ast_t *ast) { ")"); T(When, "(When ", ast_to_sexp(data.subject), when_clauses_to_sexp(data.clauses), optional_sexp("else", data.else_body), ")"); - T(Reduction, "(Reduction ", quoted_text(binop_operator(data.op)), " ", ast_to_sexp(data.key), " ", + T(Reduction, "(Reduction ", quoted_text(binop_info[data.op].operator), " ", ast_to_sexp(data.key), " ", ast_to_sexp(data.iter), ")"); T(Skip, "(Skip ", quoted_text(data.target), ")"); T(Stop, "(Stop ", quoted_text(data.target), ")"); diff --git a/src/ast.h b/src/ast.h index 02c5ad74..7396555e 100644 --- a/src/ast.h +++ b/src/ast.h @@ -481,6 +481,13 @@ struct ast_s { extern const int op_tightness[NUM_AST_TAGS]; +typedef struct { + const char *method_name; + const char *operator; +} binop_info_t; + +extern const binop_info_t binop_info[NUM_AST_TAGS]; + OptionalText_t ast_source(ast_t *ast); Text_t ast_to_sexp(ast_t *ast); @@ -490,7 +497,5 @@ Text_t type_ast_to_sexp(type_ast_t *ast); PUREFUNC bool is_idempotent(ast_t *ast); void visit_topologically(ast_list_t *ast, Closure_t fn); CONSTFUNC bool is_update_assignment(ast_t *ast); -CONSTFUNC const char *binop_method_name(ast_e tag); -CONSTFUNC const char *binop_operator(ast_e tag); CONSTFUNC ast_e binop_tag(ast_e tag); CONSTFUNC bool is_binary_operation(ast_t *ast); diff --git a/src/compile/binops.c b/src/compile/binops.c index 87fd2c7a..ed4aaeba 100644 --- a/src/compile/binops.c +++ b/src/compile/binops.c @@ -67,7 +67,7 @@ Text_t compile_binary_op(env_t *env, ast_t *ast) { } } } else if ((ast->tag == Divide || ast->tag == Mod || ast->tag == Mod1) && is_numeric_type(rhs_t)) { - b = get_namespace_binding(env, binop.lhs, binop_method_name(ast->tag)); + b = get_namespace_binding(env, binop.lhs, binop_info[ast->tag].method_name); if (b && b->type->tag == FunctionType) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { diff --git a/src/compile/files.c b/src/compile/files.c index c250e6cc..3e91976b 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -12,7 +12,16 @@ #include "../types.h" #include "compilation.h" -static void initialize_vars_and_statics(env_t *env, ast_t *ast) { +static void initialize_vars_and_statics(env_t *env, ast_t *ast); +static void initialize_namespace(env_t *env, const char *name, ast_t *namespace); +static Text_t compile_top_level_code(env_t *env, ast_t *ast); +static Text_t compile_namespace(env_t *env, const char *name, ast_t *namespace); + +void initialize_namespace(env_t *env, const char *name, ast_t *namespace) { + initialize_vars_and_statics(namespace_env(env, name), namespace); +} + +void initialize_vars_and_statics(env_t *env, ast_t *ast) { if (!ast) return; for (ast_list_t *stmt = Match(ast, Block)->statements; stmt; stmt = stmt->next) { @@ -27,24 +36,20 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast) { if (t->tag == FunctionType) t = Type(ClosureType, t); Text_t val_code = compile_declared_value(env, stmt->ast); if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) { - Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); + Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$initialized")); env->code->variable_initializers = Texts(env->code->variable_initializers, with_source_info(env, stmt->ast, Texts(full_name, " = ", val_code, ",\n", initialized_name, " = true;\n"))); } } else if (stmt->ast->tag == StructDef) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, StructDef)->name), - Match(stmt->ast, StructDef)->namespace); + initialize_namespace(env, Match(stmt->ast, StructDef)->name, Match(stmt->ast, StructDef)->namespace); } else if (stmt->ast->tag == EnumDef) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, EnumDef)->name), - Match(stmt->ast, EnumDef)->namespace); + initialize_namespace(env, Match(stmt->ast, EnumDef)->name, Match(stmt->ast, EnumDef)->namespace); } else if (stmt->ast->tag == LangDef) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, LangDef)->name), - Match(stmt->ast, LangDef)->namespace); + initialize_namespace(env, Match(stmt->ast, LangDef)->name, Match(stmt->ast, LangDef)->namespace); } else if (stmt->ast->tag == Extend) { - initialize_vars_and_statics(namespace_env(env, Match(stmt->ast, Extend)->name), - Match(stmt->ast, Extend)->body); + initialize_namespace(env, Match(stmt->ast, Extend)->name, Match(stmt->ast, Extend)->body); } else if (stmt->ast->tag == Use) { continue; } else { @@ -54,7 +59,12 @@ static void initialize_vars_and_statics(env_t *env, ast_t *ast) { } } -static Text_t compile_top_level_code(env_t *env, ast_t *ast) { +Text_t compile_namespace(env_t *env, const char *name, ast_t *namespace) { + env_t *ns_env = namespace_env(env, name); + return namespace ? compile_top_level_code(ns_env, namespace) : EMPTY_TEXT; +} + +Text_t compile_top_level_code(env_t *env, ast_t *ast) { if (!ast) return EMPTY_TEXT; switch (ast->tag) { @@ -111,15 +121,13 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) { type_t *t = Table$str_get(*env->types, def->name); assert(t && t->tag == StructType); Text_t code = compile_struct_typeinfo(env, t, def->name, def->fields, def->secret, def->opaque); - env_t *ns_env = namespace_env(env, def->name); - return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); + return Texts(code, compile_namespace(env, def->name, def->namespace)); } case EnumDef: { DeclareMatch(def, ast, EnumDef); Text_t code = compile_enum_typeinfo(env, ast); code = Texts(code, compile_enum_constructors(env, ast)); - env_t *ns_env = namespace_env(env, def->name); - return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); + return Texts(code, compile_namespace(env, def->name, def->namespace)); } case LangDef: { DeclareMatch(def, ast, LangDef); @@ -127,8 +135,7 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) { Texts("public const TypeInfo_t ", namespace_name(env, env->namespace, Texts(def->name, "$$info")), " = {", (int64_t)sizeof(Text_t), ", ", (int64_t)__alignof__(Text_t), ", .metamethods=Text$metamethods, .tag=TextInfo, .TextInfo={", quoted_str(def->name), "}};\n"); - env_t *ns_env = namespace_env(env, def->name); - return Texts(code, def->namespace ? compile_top_level_code(ns_env, def->namespace) : EMPTY_TEXT); + return Texts(code, compile_namespace(env, def->name, def->namespace)); } case Extend: { DeclareMatch(extend, ast, Extend); diff --git a/src/compile/reductions.c b/src/compile/reductions.c index 1652384c..438e072b 100644 --- a/src/compile/reductions.c +++ b/src/compile/reductions.c @@ -12,7 +12,7 @@ public Text_t compile_reduction(env_t *env, ast_t *ast) { DeclareMatch(reduction, ast, Reduction); ast_e op = reduction->op; - const char *op_str = binop_operator(op); + const char *op_str = binop_info[op].operator; type_t *iter_t = get_type(env, reduction->iter); type_t *item_t = get_iterated_type(iter_t); diff --git a/src/compile/statements.c b/src/compile/statements.c index 4e37838c..bde9ae36 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -25,6 +25,14 @@ Text_t with_source_info(env_t *env, ast_t *ast, Text_t code) { return Texts("\n#line ", line, "\n", code); } +static Text_t compile_simple_update_assignment(env_t *env, ast_t *ast, const char *op) { + binary_operands_t update = BINARY_OPERANDS(ast); + type_t *lhs_t = get_type(env, update.lhs); + if (is_idempotent(update.lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) + return Texts(compile_lvalue(env, update.lhs), " ", op, "= ", compile_to_type(env, update.rhs, lhs_t), ";"); + return compile_update_assignment(env, ast); +} + static Text_t _compile_statement(env_t *env, ast_t *ast) { switch (ast->tag) { case When: return compile_when_statement(env, ast); @@ -47,41 +55,12 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { } } case Assign: return compile_assignment_statement(env, ast); - case PlusUpdate: { - DeclareMatch(update, ast, PlusUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " += ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case MinusUpdate: { - DeclareMatch(update, ast, MinusUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " -= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case MultiplyUpdate: { - DeclareMatch(update, ast, MultiplyUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " *= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case DivideUpdate: { - DeclareMatch(update, ast, DivideUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " /= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } - case ModUpdate: { - DeclareMatch(update, ast, ModUpdate); - type_t *lhs_t = get_type(env, update->lhs); - if (is_idempotent(update->lhs) && (lhs_t->tag == IntType || lhs_t->tag == NumType || lhs_t->tag == ByteType)) - return Texts(compile_lvalue(env, update->lhs), " %= ", compile_to_type(env, update->rhs, lhs_t), ";"); - return compile_update_assignment(env, ast); - } + case PlusUpdate: return compile_simple_update_assignment(env, ast, "+"); + case MinusUpdate: return compile_simple_update_assignment(env, ast, "-"); + case MultiplyUpdate: return compile_simple_update_assignment(env, ast, "*"); + case DivideUpdate: return compile_simple_update_assignment(env, ast, "/"); + case ModUpdate: return compile_simple_update_assignment(env, ast, "%"); + case PowerUpdate: case Mod1Update: case ConcatUpdate: diff --git a/src/environment.c b/src/environment.c index 5efedfbe..7ac54a7a 100644 --- a/src/environment.c +++ b/src/environment.c @@ -73,7 +73,9 @@ env_t *global_env(bool source_mapping) { } ns_entry_t; #define MAKE_TYPE(name, type, type_name, type_info, ...) \ - {name, type, type_name, type_info, TypedList(ns_entry_t, __VA_ARGS__)} + { \ + name, type, type_name, type_info, TypedList(ns_entry_t, __VA_ARGS__) \ + } struct { const char *name; type_t *type; @@ -736,7 +738,7 @@ PUREFUNC binding_t *get_constructor(env_t *env, type_t *t, arg_ast_t *args, bool } PUREFUNC binding_t *get_metamethod_binding(env_t *env, ast_e tag, ast_t *lhs, ast_t *rhs, type_t *ret) { - const char *method_name = binop_method_name(tag); + const char *method_name = binop_info[tag].method_name; if (!method_name) return NULL; binding_t *b = get_namespace_binding(env, lhs, method_name); if (!b || b->type->tag != FunctionType) return NULL; diff --git a/src/formatter/formatter.c b/src/formatter/formatter.c index 60714945..b68b3874 100644 --- a/src/formatter/formatter.c +++ b/src/formatter/formatter.c @@ -323,7 +323,8 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { if (reduction->key) { return Texts("(", fmt_inline(reduction->key, comments), ": ", fmt_inline(reduction->iter, comments)); } else { - return Texts("(", binop_operator(reduction->op), ": ", fmt_inline(reduction->iter, comments)); + return Texts("(", Text$from_str(binop_info[reduction->op].operator), ": ", + fmt_inline(reduction->iter, comments)); } } /*inline*/ case None: @@ -353,7 +354,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { } /*inline*/ case BINOP_CASES: { binary_operands_t operands = BINARY_OPERANDS(ast); - const char *op = binop_operator(ast->tag); + const char *op = binop_info[ast->tag].operator; Text_t lhs = fmt_inline(operands.lhs, comments); Text_t rhs = fmt_inline(operands.rhs, comments); @@ -368,7 +369,7 @@ OptionalText_t format_inline_code(ast_t *ast, Table_t comments) { rhs = parenthesize(rhs, EMPTY_TEXT); Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); - return Texts(lhs, space, Text$from_str(binop_operator(ast->tag)), space, rhs); + return Texts(lhs, space, Text$from_str(binop_info[ast->tag].operator), space, rhs); } /*inline*/ case Deserialize: { DeclareMatch(deserialize, ast, Deserialize); @@ -760,7 +761,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { return Texts("(", fmt(reduction->key, comments, Texts(indent, single_indent)), ": ", fmt(reduction->iter, comments, Texts(indent, single_indent))); } else { - return Texts("(", binop_operator(reduction->op), ": ", + return Texts("(", binop_info[reduction->op].operator, ": ", fmt(reduction->iter, comments, Texts(indent, single_indent))); } } @@ -808,7 +809,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { /*multiline*/ case BINOP_CASES: { if (inlined_fits) return inlined; binary_operands_t operands = BINARY_OPERANDS(ast); - const char *op = binop_operator(ast->tag); + const char *op = binop_info[ast->tag].operator; Text_t lhs = fmt(operands.lhs, comments, indent); Text_t rhs = fmt(operands.rhs, comments, indent); @@ -822,7 +823,7 @@ Text_t format_code(ast_t *ast, Table_t comments, Text_t indent) { rhs = parenthesize(rhs, indent); Text_t space = op_tightness[ast->tag] >= op_tightness[Multiply] ? EMPTY_TEXT : Text(" "); - return Texts(lhs, space, Text$from_str(binop_operator(ast->tag)), space, rhs); + return Texts(lhs, space, Text$from_str(binop_info[ast->tag].operator), space, rhs); } /*multiline*/ case Deserialize: { if (inlined_fits) return inlined; diff --git a/src/parse/expressions.c b/src/parse/expressions.c index 32133fe9..df0a10a7 100644 --- a/src/parse/expressions.c +++ b/src/parse/expressions.c @@ -49,7 +49,7 @@ ast_t *parse_reduction(parse_ctx_t *ctx, const char *pos) { ast_e op = match_binary_operator(&pos); if (op == Unknown) return NULL; - const char *op_str = binop_operator(op); + const char *op_str = binop_info[op].operator; assert(op_str); ast_t *key = NewAST(ctx->file, pos, pos, Var, .name = op_str); for (bool progress = true; progress;) { diff --git a/src/typecheck.c b/src/typecheck.c index 2d805cdb..e34a85de 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1356,7 +1356,7 @@ type_t *get_type(env_t *env, ast_t *ast) { } } } else if ((ast->tag == Divide || ast->tag == Mod || ast->tag == Mod1) && is_numeric_type(rhs_t)) { - binding_t *b = get_namespace_binding(env, binop.lhs, binop_method_name(ast->tag)); + binding_t *b = get_namespace_binding(env, binop.lhs, binop_info[ast->tag].method_name); if (b && b->type->tag == FunctionType) { DeclareMatch(fn, b->type, FunctionType); if (type_eq(fn->ret, lhs_t)) { @@ -1415,7 +1415,7 @@ type_t *get_type(env_t *env, ast_t *ast) { code_err(reduction->iter, "I don't know how to do a reduction over ", type_to_str(iter_t), " values"); if (reduction->key && !(reduction->op == Min || reduction->op == Max)) { env_t *item_scope = fresh_scope(env); - const char *op_str = binop_operator(reduction->op); + const char *op_str = binop_info[reduction->op].operator; set_binding(item_scope, op_str, iterated, EMPTY_TEXT); iterated = get_type(item_scope, reduction->key); } -- cgit v1.2.3 From 73246764f88f6f652316ee0c138a990d836698a7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 6 Sep 2025 14:38:58 -0400 Subject: Fixes for prior changes --- src/compile/files.c | 2 +- src/typecheck.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/compile/files.c b/src/compile/files.c index 3e91976b..4d6fb1a8 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -36,7 +36,7 @@ void initialize_vars_and_statics(env_t *env, ast_t *ast) { if (t->tag == FunctionType) t = Type(ClosureType, t); Text_t val_code = compile_declared_value(env, stmt->ast); if ((decl->value && !is_constant(env, decl->value)) || (!decl->value && has_heap_memory(t))) { - Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$initialized")); + Text_t initialized_name = namespace_name(env, env->namespace, Texts(decl_name, "$$initialized")); env->code->variable_initializers = Texts(env->code->variable_initializers, with_source_info(env, stmt->ast, diff --git a/src/typecheck.c b/src/typecheck.c index e34a85de..07f8aac4 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -606,6 +606,8 @@ type_t *get_function_type(env_t *env, ast_t *ast) { if (ast->tag == Lambda) { ast_t *body = Match(ast, Lambda)->body; + + scope->fn = NULL; type_t *ret_t = get_type(scope, body); if (ret_t->tag == ReturnType) ret_t = Match(ret_t, ReturnType)->ret; if (ret_t->tag == AbortType) ret_t = Type(VoidType); -- cgit v1.2.3