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 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(-) 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(-) 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(-) 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(-) 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(-) 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(+) 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(-) 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(-) 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 --- Makefile | 2 +- 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 +- 12 files changed, 769 insertions(+), 701 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 diff --git a/Makefile b/Makefile index 95f07128..58a8d01f 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ else endif EXE_FILE=tomo_$(TOMO_VERSION) -COMPILER_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/compile/*.c src/parse/*.c)) +COMPILER_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/compile/*.c src/parse/*.c src/formatter/*.c)) STDLIB_OBJS=$(patsubst %.c,%.o,$(wildcard src/stdlib/*.c)) TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/*.tm)) API_YAML=$(wildcard api/*.yaml) 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 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(-) 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(-) 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(-) 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(-) 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(-) 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 278d31e234e230f38f58d73dc267e6868c323ee1 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 26 Aug 2025 01:06:06 -0400 Subject: Add changelog message --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 965a1aea..766138ce 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ - Added a versioning system based on `CHANGES.md` files and `modules.ini` configuration for module aliases. +- Added a `--format` flag to the `tomo` binary that autoformats your code. - When attempting to run a program with a module that is not installed, Tomo can prompt the user to automatically install it. - Programs can use `--version` as a CLI flag to print a Tomo program's version -- 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(-) 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 --- Makefile | 2 +- src/formatter/formatter.c | 58 +++++++++++++++++++++++++++++++---------------- src/formatter/utils.c | 20 ++++++++++++++++ src/formatter/utils.h | 2 ++ 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 58a8d01f..adcffed2 100644 --- a/Makefile +++ b/Makefile @@ -129,7 +129,7 @@ build/lib/$(AR_FILE): $(STDLIB_OBJS) ar -rcs $@ $^ tags: - ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} + ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} src/format/*.{c,h} config.mk: configure.sh bash ./configure.sh 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(-) 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(-) 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(-) 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(+) 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(-) 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(-) 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(-) 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(-) 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(-) 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(+) 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(-) 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 +----------- test/enums.tm | 3 ++- 2 files changed, 3 insertions(+), 12 deletions(-) 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); } diff --git a/test/enums.tm b/test/enums.tm index 080e5d97..1d00cd1a 100644 --- a/test/enums.tm +++ b/test/enums.tm @@ -61,9 +61,10 @@ func main() i := 1 cases := [Foo.One(1), Foo.One(2), Foo.Zero] - while when cases[i] is One(x) + repeat when cases[i] is One(x) >> x i += 1 + else stop >> [ ( -- 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(-) 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(-) 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 --- docs/tomo.1.md | 60 ++++++++++++++++++++++++++++++++++++++-------------------- src/tomo.c | 15 ++++++++++++++- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/docs/tomo.1.md b/docs/tomo.1.md index 3bf5e779..6fefad69 100644 --- a/docs/tomo.1.md +++ b/docs/tomo.1.md @@ -32,41 +32,59 @@ C code, which is then compiled using a C compiler of your choice. # OPTIONS -`-h`, `--help` -: Print the usage and exit. +`--changelog` +: Print the compiler change log and exit. -`-t`, `--transpile` -: Transpile the input files to C code without compiling them. +`--compile-exe`, `-e` +: Compile the input file to an executable. -`-c`, `--compile-obj` +`--compile-obj`, `-c` : Compile the input files to static objects, rather than running them. -`-e`, `--compile-exe` -: Compile the input file to an executable. - -`-L`, `--library` -: Compile the input files to a shared library file and header. +`--help`, `-h` +: Print the usage and exit. -`-I`, `--install` +`--install`, `-I` : Install the compiled executable or library. -`-C` **, `--show-codegen` ** +`--library`, `-L` +: Compile the input files to a shared library file and header. + +`--show-codegen` **, `-C` ** : Set a program (e.g. `cat` or `bat`) to display the generated code -`-O` **level**, `--optimization` **level** -: Set the optimization level. +`--force-rebuild`, `-f` +: Force rebuilding/recompiling. -`-v`, `--verbose` -: Print extra verbose output. +`--format` +: Autoformat a file and print it to standard output. -`--version` -: Print the compiler version and exit. +`--format-inplace` +: Autoformat a file in-place. -`--changelog` -: Print the compiler change log and exit. +`--optimization` **level**, `-O` **level** +: Set the optimization level. `--prefix` : Print the Tomo installation prefix and exit. -`-r`, `--run` +`--quiet`, `-q` +: Run in quiet mode. + +`--run`, `-r` : Run an installed tomo program from `~/.local/share/tomo_vX.Y/installed`. + +`--source-mapping=`, `-m=` **** +: Toggle whether source mapping should be enabled or disabled. + +`--transpile`, `-t` +: Transpile the input files to C code without compiling them. + +`--uninstall`, `-u` +: Uninstall a compiled executable or library. + +`--verbose`, `-v` +: Print extra verbose output. + +`--version` +: Print the compiler version and exit. 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(-) 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(-) 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 a6cce3d83b7098ef746824ef07ce19145483c912 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sat, 30 Aug 2025 13:55:48 -0400 Subject: Fix tags rule --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index adcffed2..3ce12038 100644 --- a/Makefile +++ b/Makefile @@ -129,7 +129,7 @@ build/lib/$(AR_FILE): $(STDLIB_OBJS) ar -rcs $@ $^ tags: - ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} src/format/*.{c,h} + ctags src/*.{c,h} src/stdlib/*.{c,h} src/compile/*.{c,h} src/parse/*.{c,h} src/formatter/*.{c,h} config.mk: configure.sh bash ./configure.sh -- 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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(-) 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 30a5c322155c544c80af7b92cad4d2e6b5f7ead8 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 31 Aug 2025 15:41:49 -0400 Subject: Update manpage --- man/man1/tomo.1 | 62 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/man/man1/tomo.1 b/man/man1/tomo.1 index c3330175..60760d24 100644 --- a/man/man1/tomo.1 +++ b/man/man1/tomo.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pandoc 3.1.12.1 +.\" Automatically generated by Pandoc 3.1.13 .\" .TH "TOMO" "1" "June 11, 2024" "" "" .SH NAME @@ -29,45 +29,63 @@ It compiles by first outputting C code, which is then compiled using a C compiler of your choice. .SH OPTIONS .TP -\f[B]\-h\f[R], \f[B]\-\-help\f[R] -Print the usage and exit. +\f[B]\-\-changelog\f[R] +Print the compiler change log and exit. .TP -\f[B]\-t\f[R], \f[B]\-\-transpile\f[R] -Transpile the input files to C code without compiling them. +\f[B]\-\-compile\-exe\f[R], \f[B]\-e\f[R] +Compile the input file to an executable. .TP -\f[B]\-c\f[R], \f[B]\-\-compile\-obj\f[R] +\f[B]\-\-compile\-obj\f[R], \f[B]\-c\f[R] Compile the input files to static objects, rather than running them. .TP -\f[B]\-e\f[R], \f[B]\-\-compile\-exe\f[R] -Compile the input file to an executable. -.TP -\f[B]\-L\f[R], \f[B]\-\-library\f[R] -Compile the input files to a shared library file and header. +\f[B]\-\-help\f[R], \f[B]\-h\f[R] +Print the usage and exit. .TP -\f[B]\-I\f[R], \f[B]\-\-install\f[R] +\f[B]\-\-install\f[R], \f[B]\-I\f[R] Install the compiled executable or library. .TP -\f[B]\-C\f[R] \f[I]\f[R], \f[B]\-\-show\-codegen\f[R] \f[I]\f[R] +\f[B]\-\-library\f[R], \f[B]\-L\f[R] +Compile the input files to a shared library file and header. +.TP +\f[B]\-\-show\-codegen\f[R] \f[I]\f[R], \f[B]\-C\f[R] \f[I]\f[R] Set a program (e.g.\ \f[B]cat\f[R] or \f[B]bat\f[R]) to display the generated code .TP -\f[B]\-O\f[R] \f[B]level\f[R], \f[B]\-\-optimization\f[R] \f[B]level\f[R] -Set the optimization level. +\f[B]\-\-force\-rebuild\f[R], \f[B]\-f\f[R] +Force rebuilding/recompiling. .TP -\f[B]\-v\f[R], \f[B]\-\-verbose\f[R] -Print extra verbose output. +\f[B]\-\-format\f[R] +Autoformat a file and print it to standard output. .TP -\f[B]\-\-version\f[R] -Print the compiler version and exit. +\f[B]\-\-format\-inplace\f[R] +Autoformat a file in\-place. .TP -\f[B]\-\-changelog\f[R] -Print the compiler change log and exit. +\f[B]\-\-optimization\f[R] \f[B]level\f[R], \f[B]\-O\f[R] \f[B]level\f[R] +Set the optimization level. .TP \f[B]\-\-prefix\f[R] Print the Tomo installation prefix and exit. .TP -\f[B]\-r\f[R], \f[B]\-\-run\f[R] +\f[B]\-\-quiet\f[R], \f[B]\-q\f[R] +Run in quiet mode. +.TP +\f[B]\-\-run\f[R], \f[B]\-r\f[R] Run an installed tomo program from \f[B]\[ti]/.local/share/tomo_vX.Y/installed\f[R]. +.TP +\f[B]\-\-source\-mapping=\f[R], \f[B]\-m=\f[R] \f[B]\f[R] +Toggle whether source mapping should be enabled or disabled. +.TP +\f[B]\-\-transpile\f[R], \f[B]\-t\f[R] +Transpile the input files to C code without compiling them. +.TP +\f[B]\-\-uninstall\f[R], \f[B]\-u\f[R] +Uninstall a compiled executable or library. +.TP +\f[B]\-\-verbose\f[R], \f[B]\-v\f[R] +Print extra verbose output. +.TP +\f[B]\-\-version\f[R] +Print the compiler version and exit. .SH AUTHORS Bruce Hill (\f[I]bruce\[at]bruce\-hill.com\f[R]). -- 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(-) 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 c0c6fe863f8e074cbe8297b5da2a476f455b6518 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 13:08:11 -0400 Subject: Make Texts() macro a bit more flexible. --- src/stdlib/integers.c | 2 ++ src/stdlib/integers.h | 1 + src/stdlib/nums.c | 36 ++++++++++++++++++++++-------------- src/stdlib/nums.h | 10 ++++++---- src/stdlib/pointers.c | 2 +- src/stdlib/text.c | 7 +++---- src/stdlib/text.h | 14 +++++++++++++- 7 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/stdlib/integers.c b/src/stdlib/integers.c index 7dda77bd..5dc9ac55 100644 --- a/src/stdlib/integers.c +++ b/src/stdlib/integers.c @@ -617,6 +617,8 @@ void Int32$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_ Text_t text = _int64_to_text((int64_t)(*(c_type *)i)); \ return colorize ? Texts(Text("\033[35m"), text, Text("\033[m")) : text; \ } \ + public \ + Text_t KindOfInt##$value_as_text(c_type i) { return _int64_to_text((int64_t)i); } \ public \ PUREFUNC int32_t KindOfInt##$compare(const void *x, const void *y, const TypeInfo_t *info) { \ (void)info; \ diff --git a/src/stdlib/integers.h b/src/stdlib/integers.h index 40c40754..34195d23 100644 --- a/src/stdlib/integers.h +++ b/src/stdlib/integers.h @@ -22,6 +22,7 @@ bool is_none : 1; \ } Optional##type_name##_t; \ Text_t type_name##$as_text(const void *i, bool colorize, const TypeInfo_t *type); \ + Text_t type_name##$value_as_text(c_type i); \ PUREFUNC int32_t type_name##$compare(const void *x, const void *y, const TypeInfo_t *type); \ PUREFUNC bool type_name##$equal(const void *x, const void *y, const TypeInfo_t *type); \ Text_t type_name##$hex(c_type i, Int_t digits, bool uppercase, bool prefix); \ diff --git a/src/stdlib/nums.c b/src/stdlib/nums.c index 55131cfd..4bbb1f6a 100644 --- a/src/stdlib/nums.c +++ b/src/stdlib/nums.c @@ -14,13 +14,18 @@ #include "types.h" public -PUREFUNC Text_t Num$as_text(const void *f, bool colorize, const TypeInfo_t *info) { - (void)info; - if (!f) return Text("Num"); +PUREFUNC Text_t Num$value_as_text(double x) { char *str = GC_MALLOC_ATOMIC(24); - int len = fpconv_dtoa(*(double *)f, str); + int len = fpconv_dtoa(x, str); + return Text$from_strn(str, (size_t)len); +} + +public +PUREFUNC Text_t Num$as_text(const void *x, bool colorize, const TypeInfo_t *info) { + (void)info; + if (!x) return Text("Num"); static const Text_t color_prefix = Text("\x1b[35m"), color_suffix = Text("\x1b[m"); - Text_t text = Text$from_strn(str, (size_t)len); + Text_t text = Num$value_as_text(*(double *)x); return colorize ? Texts(color_prefix, text, color_suffix) : text; } @@ -60,10 +65,10 @@ CONSTFUNC bool Num$near(double a, double b, double ratio, double absolute) { } public -Text_t Num$percent(double f, double precision) { - double d = 100. * f; +Text_t Num$percent(double x, double precision) { + double d = 100. * x; d = Num$with_precision(d, precision); - return Texts(Num$as_text(&d, false, &Num$info), Text("%")); + return Texts(Num$value_as_text(d), Text("%")); } public @@ -142,10 +147,13 @@ const TypeInfo_t Num$info = { }; public -PUREFUNC Text_t Num32$as_text(const void *f, bool colorize, const TypeInfo_t *info) { +PUREFUNC Text_t Num32$value_as_text(float x) { return Num$value_as_text((double)x); } + +public +PUREFUNC Text_t Num32$as_text(const void *x, bool colorize, const TypeInfo_t *info) { (void)info; - if (!f) return Text("Num32"); - double d = (double)(*(float *)f); + if (!x) return Text("Num32"); + double d = (double)(*(float *)x); return Num$as_text(&d, colorize, &Num$info); } @@ -178,10 +186,10 @@ CONSTFUNC bool Num32$near(float a, float b, float ratio, float absolute) { } public -Text_t Num32$percent(float f, float precision) { - double d = 100. * (double)f; +Text_t Num32$percent(float x, float precision) { + double d = 100. * (double)x; d = Num$with_precision(d, (double)precision); - return Texts(Num$as_text(&d, false, &Num$info), Text("%")); + return Texts(Num$value_as_text(d), Text("%")); } public diff --git a/src/stdlib/nums.h b/src/stdlib/nums.h index db051ed2..303aa362 100644 --- a/src/stdlib/nums.h +++ b/src/stdlib/nums.h @@ -15,11 +15,12 @@ #define N32(n) ((float)(n)) #define N64(n) ((double)(n)) -Text_t Num$as_text(const void *f, bool colorize, const TypeInfo_t *type); +Text_t Num$as_text(const void *x, bool colorize, const TypeInfo_t *type); +Text_t Num$value_as_text(double x); PUREFUNC int32_t Num$compare(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC bool Num$equal(const void *x, const void *y, const TypeInfo_t *type); CONSTFUNC bool Num$near(double a, double b, double ratio, double absolute); -Text_t Num$percent(double f, double precision); +Text_t Num$percent(double x, double precision); double CONSTFUNC Num$with_precision(double num, double precision); double Num$mod(double num, double modulus); double Num$mod1(double num, double modulus); @@ -70,11 +71,12 @@ MACROLIKE CONSTFUNC double Num$from_byte(Byte_t i) { return (double)i; } extern const TypeInfo_t Num$info; -Text_t Num32$as_text(const void *f, bool colorize, const TypeInfo_t *type); +Text_t Num32$as_text(const void *x, bool colorize, const TypeInfo_t *type); +Text_t Num32$value_as_text(float x); PUREFUNC int32_t Num32$compare(const void *x, const void *y, const TypeInfo_t *type); PUREFUNC bool Num32$equal(const void *x, const void *y, const TypeInfo_t *type); CONSTFUNC bool Num32$near(float a, float b, float ratio, float absolute); -Text_t Num32$percent(float f, float precision); +Text_t Num32$percent(float x, float precision); float CONSTFUNC Num32$with_precision(float num, float precision); float Num32$mod(float num, float modulus); float Num32$mod1(float num, float modulus); diff --git a/src/stdlib/pointers.c b/src/stdlib/pointers.c index b5e6400f..0a1623a0 100644 --- a/src/stdlib/pointers.c +++ b/src/stdlib/pointers.c @@ -44,7 +44,7 @@ Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *type) { TypeInfo_t rec_table = *Table$info(type, &Int64$info); int64_t *id = Table$get(pending, x, &rec_table); if (id) { - Text_t text = Texts(Text$from_str(ptr_info.sigil), Int64$as_text(id, false, &Int64$info)); + Text_t text = Texts(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id)); return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; } int64_t next_id = pending.entries.length + 2; diff --git a/src/stdlib/text.c b/src/stdlib/text.c index ed4023a4..3bff0356 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -1726,10 +1726,9 @@ Int_t Text$memory_size(Text_t text) { public Text_t Text$layout(Text_t text) { switch (text.tag) { - case TEXT_ASCII: return Texts(Text("ASCII("), Int64$as_text((int64_t[1]){text.length}, false, NULL), Text(")")); - case TEXT_GRAPHEMES: - return Texts(Text("Graphemes("), Int64$as_text((int64_t[1]){text.length}, false, NULL), Text(")")); - case TEXT_BLOB: return Texts(Text("Blob("), Int64$as_text((int64_t[1]){text.length}, false, NULL), Text(")")); + case TEXT_ASCII: return Texts(Text("ASCII("), Int64$value_as_text(text.length), Text(")")); + case TEXT_GRAPHEMES: return Texts(Text("Graphemes("), Int64$value_as_text(text.length), Text(")")); + case TEXT_BLOB: return Texts(Text("Blob("), Int64$value_as_text(text.length), Text(")")); case TEXT_CONCAT: return Texts(Text("Concat("), Text$layout(*text.left), Text(", "), Text$layout(*text.right), Text(")")); default: errx(1, "Invalid text tag: %d", text.tag); diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 5fa95675..23ceefea 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -7,7 +7,9 @@ #include #include "datatypes.h" +#include "integers.h" // IWYU pragma: export #include "mapmacro.h" +#include "nums.h" // IWYU pragma: export #include "types.h" #include "util.h" @@ -31,7 +33,17 @@ static inline Text_t Text_from_str_literal(const char *str) { static inline Text_t Text_from_text(Text_t t) { return t; } -#define convert_to_text(x) _Generic(x, Text_t: Text_from_text, char *: Text$from_str, const char *: Text$from_str)(x) +#define convert_to_text(x) \ + _Generic(x, \ + Text_t: Text_from_text, \ + char *: Text$from_str, \ + const char *: Text$from_str, \ + int8_t: Int8$value_as_text, \ + int16_t: Int16$value_as_text, \ + int32_t: Int32$value_as_text, \ + int64_t: Int64$value_as_text, \ + double: Num$value_as_text, \ + float: Num32$value_as_text)(x) Text_t Text$_concat(int n, Text_t items[n]); #define Text$concat(...) Text$_concat(sizeof((Text_t[]){__VA_ARGS__}) / sizeof(Text_t), (Text_t[]){__VA_ARGS__}) -- cgit v1.2.3 From ee020c72d92c3807fa4afcd1e170d3df81688b97 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 13:17:43 -0400 Subject: Switch to using Texts(x) instead of Texts(String(x)) when possible --- src/compile/assertions.c | 35 +++++++++++++++++------------------ src/compile/assignments.c | 7 +++---- src/compile/conditionals.c | 2 +- src/compile/doctests.c | 17 ++++++++--------- src/compile/enums.c | 20 ++++++++++---------- src/compile/files.c | 4 ++-- src/compile/functions.c | 24 +++++++++++------------- src/compile/indexing.c | 4 ++-- src/compile/lists.c | 2 +- src/compile/loops.c | 4 ++-- src/compile/optionals.c | 11 +++++------ src/compile/promotions.c | 12 ++++++------ src/compile/sets.c | 2 +- src/compile/statements.c | 4 ++-- src/compile/structs.c | 9 ++++----- src/compile/tables.c | 2 +- src/compile/types.c | 4 ++-- src/typecheck.c | 4 ++-- src/types.c | 5 ++--- 19 files changed, 82 insertions(+), 90 deletions(-) diff --git a/src/compile/assertions.c b/src/compile/assertions.c index ce9abcbc..0f1d27b6 100644 --- a/src/compile/assertions.c +++ b/src/compile/assertions.c @@ -53,27 +53,26 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { ast_t *var_comparison = new (ast_t, .file = expr->file, .start = expr->start, .end = expr->end, .tag = expr->tag, .__data.Equals = {.lhs = lhs_var, .rhs = rhs_var}); int64_t line = get_line_number(ast->file, ast->start); - return Texts("{ // assertion\n", compile_declaration(operand_t, Text("_lhs")), " = ", - compile_to_type(env, cmp.lhs, operand_t), ";\n", "\n#line ", String(line), "\n", - compile_declaration(operand_t, Text("_rhs")), " = ", compile_to_type(env, cmp.rhs, operand_t), - ";\n", "\n#line ", String(line), "\n", "if (!(", compile_condition(env, var_comparison), "))\n", - "#line ", String(line), "\n", - Texts("fail_source(", quoted_str(ast->file->filename), ", ", - String((int64_t)(expr->start - expr->file->text)), ", ", - String((int64_t)(expr->end - expr->file->text)), ", ", - message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") - : Text("\"This assertion failed!\""), - ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")), - ", " - "\" ", - failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"), - "}\n"); + return Texts( + "{ // assertion\n", compile_declaration(operand_t, Text("_lhs")), " = ", + compile_to_type(env, cmp.lhs, operand_t), ";\n", "\n#line ", line, "\n", + compile_declaration(operand_t, Text("_rhs")), " = ", compile_to_type(env, cmp.rhs, operand_t), ";\n", + "\n#line ", line, "\n", "if (!(", compile_condition(env, var_comparison), "))\n", "#line ", line, "\n", + Texts("fail_source(", quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text), + ", ", (int64_t)(expr->end - expr->file->text), ", ", + message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") + : Text("\"This assertion failed!\""), + ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")), + ", " + "\" ", + failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"), + "}\n"); } default: { int64_t line = get_line_number(ast->file, ast->start); - return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", String(line), "\n", "fail_source(", - quoted_str(ast->file->filename), ", ", String((int64_t)(expr->start - expr->file->text)), ", ", - String((int64_t)(expr->end - expr->file->text)), ", ", + return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", line, "\n", "fail_source(", + quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text), ", ", + (int64_t)(expr->end - expr->file->text), ", ", message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") : Text("\"This assertion failed!\""), ");\n"); diff --git a/src/compile/assignments.c b/src/compile/assignments.c index ab28b972..3cb60fd5 100644 --- a/src/compile/assignments.c +++ b/src/compile/assignments.c @@ -119,12 +119,12 @@ Text_t compile_assignment_statement(env_t *env, ast_t *ast) { "stack memory."); env_t *val_env = with_enum_scope(env, lhs_t); Text_t val = compile_to_type(val_env, value->ast, lhs_t); - code = Texts(code, compile_type(lhs_t), " $", String(i), " = ", val, ";\n"); + code = Texts(code, compile_type(lhs_t), " $", i, " = ", val, ";\n"); i += 1; } i = 1; for (ast_list_t *target = assign->targets; target; target = target->next) { - code = Texts(code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n"); + code = Texts(code, compile_assignment(env, target->ast, Texts("$", i)), ";\n"); i += 1; } return Texts(code, "\n}"); @@ -171,8 +171,7 @@ Text_t compile_lvalue(env_t *env, ast_t *ast) { ")"); } else { return Texts("List_lvalue(", compile_type(item_type), ", ", target_code, ", ", index_code, ", ", - String((int)(ast->start - ast->file->text)), ", ", - String((int)(ast->end - ast->file->text)), ")"); + (int64_t)(ast->start - ast->file->text), ", ", (int64_t)(ast->end - ast->file->text), ")"); } } else if (container_t->tag == TableType) { DeclareMatch(table_type, container_t, TableType); diff --git a/src/compile/conditionals.c b/src/compile/conditionals.c index 23d2a177..f9dfa751 100644 --- a/src/compile/conditionals.c +++ b/src/compile/conditionals.c @@ -59,7 +59,7 @@ Text_t compile_if_statement(env_t *env, ast_t *ast) { code = Texts(code, compile_block(nonnull_scope, if_->body)); if (if_->else_body) { - Text_t label = Texts("_falsey_", String((int64_t)(ast->start - ast->file->text))); + Text_t label = Texts("_falsey_", (int64_t)(ast->start - ast->file->text)); code = Texts(code, "else goto ", label, ";\n" "} else {\n", diff --git a/src/compile/doctests.c b/src/compile/doctests.c index 872684ed..20081ae7 100644 --- a/src/compile/doctests.c +++ b/src/compile/doctests.c @@ -4,7 +4,6 @@ #include "../config.h" #include "../environment.h" #include "../stdlib/datatypes.h" -#include "../stdlib/print.h" #include "../stdlib/text.h" #include "../stdlib/util.h" #include "../typecheck.h" @@ -69,12 +68,12 @@ Text_t compile_doctest(env_t *env, ast_t *ast) { if (target == assign->targets) expr_t = lhs_t; env_t *val_scope = with_enum_scope(env, lhs_t); Text_t val_code = compile_to_type(val_scope, value->ast, lhs_t); - test_code = Texts(test_code, compile_type(lhs_t), " $", String(i), " = ", val_code, ";\n"); + test_code = Texts(test_code, compile_type(lhs_t), " $", i, " = ", val_code, ";\n"); i += 1; } i = 1; for (ast_list_t *target = assign->targets; target; target = target->next) { - test_code = Texts(test_code, compile_assignment(env, target->ast, Texts("$", String(i))), ";\n"); + test_code = Texts(test_code, compile_assignment(env, target->ast, Texts("$", i)), ";\n"); i += 1; } @@ -104,16 +103,16 @@ Text_t compile_doctest(env_t *env, ast_t *ast) { if (test->expected) { return Texts(setup, "test(", compile_type(expr_t), ", ", test_code, ", ", compile_to_type(env, test->expected, expr_t), ", ", compile_type_info(expr_t), ", ", - String((int64_t)(test->expr->start - test->expr->file->text)), ", ", - String((int64_t)(test->expr->end - test->expr->file->text)), ");"); + (int64_t)(test->expr->start - test->expr->file->text), ", ", + (int64_t)(test->expr->end - test->expr->file->text), ");"); } else { if (expr_t->tag == VoidType || expr_t->tag == AbortType) { return Texts(setup, "inspect_void(", test_code, ", ", compile_type_info(expr_t), ", ", - String((int64_t)(test->expr->start - test->expr->file->text)), ", ", - String((int64_t)(test->expr->end - test->expr->file->text)), ");"); + (int64_t)(test->expr->start - test->expr->file->text), ", ", + (int64_t)(test->expr->end - test->expr->file->text), ");"); } return Texts(setup, "inspect(", compile_type(expr_t), ", ", test_code, ", ", compile_type_info(expr_t), ", ", - String((int64_t)(test->expr->start - test->expr->file->text)), ", ", - String((int64_t)(test->expr->end - test->expr->file->text)), ");"); + (int64_t)(test->expr->start - test->expr->file->text), ", ", + (int64_t)(test->expr->end - test->expr->file->text), ");"); } } diff --git a/src/compile/enums.c b/src/compile/enums.c index d9c29f26..f5500831 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -30,12 +30,12 @@ Text_t compile_enum_typeinfo(env_t *env, ast_t *ast) { type_t *t = Table$str_get(*env->types, def->name); const char *metamethods = is_packed_data(t) ? "PackedDataEnum$metamethods" : "Enum$metamethods"; Text_t info = namespace_name(env, env->namespace, Texts(def->name, "$$info")); - Text_t typeinfo = Texts("public const TypeInfo_t ", info, " = {", String((int64_t)type_size(t)), "u, ", - String((int64_t)type_align(t)), "u, .metamethods=", metamethods, - ", {.tag=EnumInfo, .EnumInfo={.name=\"", def->name, - "\", " - ".num_tags=", - String((int64_t)num_tags), ", .tags=(NamedType_t[]){"); + Text_t typeinfo = + Texts("public const TypeInfo_t ", info, " = {", (int64_t)type_size(t), "u, ", (int64_t)type_align(t), + "u, .metamethods=", metamethods, ", {.tag=EnumInfo, .EnumInfo={.name=\"", def->name, + "\", " + ".num_tags=", + (int64_t)num_tags, ", .tags=(NamedType_t[]){"); for (tag_ast_t *tag = def->tags; tag; tag = tag->next) { const char *tag_type_name = String(def->name, "$", tag->name); @@ -144,10 +144,10 @@ Text_t compile_empty_enum(type_t *t) { assert(tag); assert(tag->type); if (Match(tag->type, StructType)->fields) - return Texts("((", compile_type(t), "){.$tag=", String(tag->tag_value), ", .", tag->name, "=", - compile_empty(tag->type), "})"); - else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", String(tag->tag_value), "})"); - else return Texts("((", compile_type(t), ")", String(tag->tag_value), ")"); + return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, ", .", tag->name, "=", compile_empty(tag->type), + "})"); + else if (enum_has_fields(t)) return Texts("((", compile_type(t), "){.$tag=", tag->tag_value, "})"); + else return Texts("((", compile_type(t), ")", tag->tag_value, ")"); } public diff --git a/src/compile/files.c b/src/compile/files.c index 2b0ac4bf..a6af2300 100644 --- a/src/compile/files.c +++ b/src/compile/files.c @@ -103,7 +103,7 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) { "types, not ", type_to_str(Match(type, FunctionType)->ret)); Text_t name_code = - namespace_name(env, env->namespace, Texts(name, "$", String(get_line_number(ast->file, ast->start)))); + namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start))); return compile_function(env, name_code, ast, &env->code->staticdefs); } case StructDef: { @@ -125,7 +125,7 @@ static Text_t compile_top_level_code(env_t *env, ast_t *ast) { DeclareMatch(def, ast, LangDef); Text_t code = Texts("public const TypeInfo_t ", namespace_name(env, env->namespace, Texts(def->name, "$$info")), " = {", - String((int64_t)sizeof(Text_t)), ", ", String((int64_t)__alignof__(Text_t)), + (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); diff --git a/src/compile/functions.c b/src/compile/functions.c index 01de26e3..6caefa8b 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -32,7 +32,7 @@ Text_t compile_function_declaration(env_t *env, ast_t *ast) { if (ret_t->tag == AbortType) ret_type_code = Texts("__attribute__((noreturn)) _Noreturn ", ret_type_code); Text_t name = namespace_name(env, env->namespace, Text$from_str(decl_name)); if (env->namespace && env->namespace->parent && env->namespace->name && streq(decl_name, env->namespace->name)) - name = namespace_name(env, env->namespace, Text$from_str(String(get_line_number(ast->file, ast->start)))); + name = namespace_name(env, env->namespace, Texts(get_line_number(ast->file, ast->start))); return Texts(ret_type_code, " ", name, arg_signature, ";\n"); } @@ -56,8 +56,7 @@ Text_t compile_convert_declaration(env_t *env, ast_t *ast) { "Conversions are only supported for text, struct, and enum " "types, not ", type_to_str(ret_t)); - Text_t name_code = - namespace_name(env, env->namespace, Texts(name, "$", String(get_line_number(ast->file, ast->start)))); + Text_t name_code = namespace_name(env, env->namespace, Texts(name, "$", get_line_number(ast->file, ast->start))); return Texts(ret_type_code, " ", name_code, arg_signature, ";\n"); } @@ -248,7 +247,7 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { public Text_t compile_lambda(env_t *env, ast_t *ast) { DeclareMatch(lambda, ast, Lambda); - Text_t name = namespace_name(env, env->namespace, Texts("lambda$", String(lambda->id))); + Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); env_t *body_scope = fresh_scope(env); body_scope->deferred = NULL; @@ -736,7 +735,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static // FIXME: this currently just deletes the first entry, but this // should be more like a least-recently-used cache eviction policy // or least-frequently-used - pop_code = Texts("if (cache.entries.length > ", String(cache_size.value), + pop_code = Texts("if (cache.entries.length > ", cache_size.value, ") Table$remove(&cache, cache.entries.data + " "cache.entries.stride*0, table_type);\n"); } @@ -772,14 +771,13 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static int64_t num_fields = used_names.entries.length; const char *metamethods = is_packed_data(t) ? "PackedData$metamethods" : "Struct$metamethods"; - Text_t args_typeinfo = - Texts("((TypeInfo_t[1]){{.size=sizeof(args), " - ".align=__alignof__(args), .metamethods=", - metamethods, - ", .tag=StructInfo, " - ".StructInfo.name=\"FunctionArguments\", " - ".StructInfo.num_fields=", - String(num_fields), ", .StructInfo.fields=(NamedType_t[", String(num_fields), "]){"); + Text_t args_typeinfo = Texts("((TypeInfo_t[1]){{.size=sizeof(args), " + ".align=__alignof__(args), .metamethods=", + metamethods, + ", .tag=StructInfo, " + ".StructInfo.name=\"FunctionArguments\", " + ".StructInfo.num_fields=", + num_fields, ", .StructInfo.fields=(NamedType_t[", num_fields, "]){"); Text_t args_type = Text("struct { "); for (arg_t *f = fields; f; f = f->next) { args_typeinfo = Texts(args_typeinfo, "{\"", f->name, "\", ", compile_type_info(f->type), "}"); diff --git a/src/compile/indexing.c b/src/compile/indexing.c index e99feeb2..39af1160 100644 --- a/src/compile/indexing.c +++ b/src/compile/indexing.c @@ -44,8 +44,8 @@ Text_t compile_indexing(env_t *env, ast_t *ast) { return Texts("List_get_unchecked(", compile_type(item_type), ", ", list, ", ", index_code, ")"); else return Texts("List_get(", compile_type(item_type), ", ", list, ", ", index_code, ", ", - String((int64_t)(indexing->index->start - f->text)), ", ", - String((int64_t)(indexing->index->end - f->text)), ")"); + (int64_t)(indexing->index->start - f->text), ", ", (int64_t)(indexing->index->end - f->text), + ")"); } else if (container_t->tag == TableType) { DeclareMatch(table_type, container_t, TableType); if (indexing->unchecked) code_err(ast, "Table indexes cannot be unchecked"); diff --git a/src/compile/lists.c b/src/compile/lists.c index 5df39863..d9d71278 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -33,7 +33,7 @@ Text_t compile_typed_list(env_t *env, ast_t *ast, type_t *list_type) { { env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env; if (is_incomplete_type(item_type)) code_err(ast, "This list's type can't be inferred!"); - Text_t code = Texts("TypedListN(", compile_type(item_type), ", ", String(n)); + Text_t code = Texts("TypedListN(", compile_type(item_type), ", ", n); for (ast_list_t *item = list->items; item; item = item->next) { code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type)); } diff --git a/src/compile/loops.c b/src/compile/loops.c index c742589a..332024f4 100644 --- a/src/compile/loops.c +++ b/src/compile/loops.c @@ -405,7 +405,7 @@ Text_t compile_skip(env_t *env, ast_t *ast) { if (matched) { if (ctx->skip_label.length == 0) { static int64_t skip_label_count = 1; - ctx->skip_label = Texts("skip_", String(skip_label_count)); + ctx->skip_label = Texts("skip_", skip_label_count); ++skip_label_count; } Text_t code = EMPTY_TEXT; @@ -431,7 +431,7 @@ Text_t compile_stop(env_t *env, ast_t *ast) { if (matched) { if (ctx->stop_label.length == 0) { static int64_t stop_label_count = 1; - ctx->stop_label = Texts("stop_", String(stop_label_count)); + ctx->stop_label = Texts("stop_", stop_label_count); ++stop_label_count; } Text_t code = EMPTY_TEXT; diff --git a/src/compile/optionals.c b/src/compile/optionals.c index b3d94005..cd50b1bf 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -125,10 +125,9 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { type_t *t = get_type(env, value); Text_t value_code = compile(env, value); int64_t line = get_line_number(ast->file, ast->start); - return Texts("({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (", - check_none(t, Text("opt")), ")\n", "#line ", String(line), "\n", "fail_source(", - quoted_str(ast->file->filename), ", ", String((int64_t)(value->start - value->file->text)), ", ", - String((int64_t)(value->end - value->file->text)), ", ", - "\"This was expected to be a value, but it's none\");\n", optional_into_nonnone(t, Text("opt")), - "; })"); + return Texts( + "({ ", compile_declaration(t, Text("opt")), " = ", value_code, "; ", "if unlikely (", + check_none(t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ", + (int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ", + "\"This was expected to be a value, but it's none\");\n", optional_into_nonnone(t, Text("opt")), "; })"); } diff --git a/src/compile/promotions.c b/src/compile/promotions.c index fcedce3f..6595e5aa 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -43,12 +43,12 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede // Automatic optional checking for nums: if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) { int64_t line = get_line_number(ast->file, ast->start); - *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", - check_none(actual, Text("opt")), ")\n", "#line ", String(line), "\n", "fail_source(", - quoted_str(ast->file->filename), ", ", String((int64_t)(ast->start - ast->file->text)), ", ", - String((int64_t)(ast->end - ast->file->text)), ", ", - "\"This was expected to be a value, but it's none\");\n", - optional_into_nonnone(actual, Text("opt")), "; })"); + *code = + Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", + check_none(actual, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", + quoted_str(ast->file->filename), ", ", (int64_t)(ast->start - ast->file->text), ", ", + (int64_t)(ast->end - ast->file->text), ", ", "\"This was expected to be a value, but it's none\");\n", + optional_into_nonnone(actual, Text("opt")), "; })"); return true; } diff --git a/src/compile/sets.c b/src/compile/sets.c index d33677cc..3346a9aa 100644 --- a/src/compile/sets.c +++ b/src/compile/sets.c @@ -25,7 +25,7 @@ Text_t compile_typed_set(env_t *env, ast_t *ast, type_t *set_type) { } { // No comprehension: - Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", String(n)); + Text_t code = Texts("Set(", compile_type(item_type), ", ", compile_type_info(item_type), ", ", n); env_t *scope = item_type->tag == EnumType ? with_enum_scope(env, item_type) : env; for (ast_list_t *item = set->items; item; item = item->next) { code = Texts(code, ", ", compile_to_type(scope, item->ast, item_type)); diff --git a/src/compile/statements.c b/src/compile/statements.c index 7c58559d..e296795b 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -22,7 +22,7 @@ public Text_t with_source_info(env_t *env, ast_t *ast, Text_t code) { if (code.length == 0 || !ast || !ast->file || !env->do_source_mapping) return code; int64_t line = get_line_number(ast->file, ast->start); - return Texts("\n#line ", String(line), "\n", code); + return Texts("\n#line ", line, "\n", code); } static Text_t _compile_statement(env_t *env, ast_t *ast) { @@ -121,7 +121,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { if (Text$starts_with(entry->b->code, Text("userdata->"), NULL)) { Table$str_set(defer_env->locals, entry->name, entry->b); } else { - Text_t defer_name = Texts("defer$", String(++defer_id), "$", entry->name); + Text_t defer_name = Texts("defer$", ++defer_id, "$", entry->name); defer_id += 1; code = Texts(code, compile_declaration(entry->b->type, defer_name), " = ", entry->b->code, ";\n"); set_binding(defer_env, entry->name, entry->b->type, defer_name); diff --git a/src/compile/structs.c b/src/compile/structs.c index 526c11c7..8560a790 100644 --- a/src/compile/structs.c +++ b/src/compile/structs.c @@ -18,7 +18,7 @@ Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_ ? Text$from_str(name) : Texts("struct ", namespace_name(env, env->namespace, Texts(name, "$$struct"))); - int num_fields = 0; + int64_t num_fields = 0; for (arg_ast_t *f = fields; f; f = f->next) num_fields += 1; const char *short_name = name; @@ -33,9 +33,9 @@ Text_t compile_struct_typeinfo(env_t *env, type_t *t, const char *name, arg_ast_ ", " ".tag=StructInfo, .StructInfo.name=\"", short_name, "\"", is_secret ? Text(", .StructInfo.is_secret=true") : EMPTY_TEXT, - is_opaque ? Text(", .StructInfo.is_opaque=true") : EMPTY_TEXT, ", .StructInfo.num_fields=", String(num_fields)); + is_opaque ? Text(", .StructInfo.is_opaque=true") : EMPTY_TEXT, ", .StructInfo.num_fields=", num_fields); if (fields) { - typeinfo = Texts(typeinfo, ", .StructInfo.fields=(NamedType_t[", String(num_fields), "]){"); + typeinfo = Texts(typeinfo, ", .StructInfo.fields=(NamedType_t[", num_fields, "]){"); for (arg_ast_t *f = fields; f; f = f->next) { type_t *field_type = get_arg_ast_type(env, f); typeinfo = Texts(typeinfo, "{\"", f->name, "\", ", compile_type_info(field_type), "}"); @@ -70,8 +70,7 @@ Text_t compile_struct_header(env_t *env, ast_t *ast) { Text_t struct_code = def->external ? EMPTY_TEXT : Texts(type_code, " {\n", fields, "};\n"); type_t *t = Table$str_get(*env->types, def->name); - Text_t unpadded_size = - def->opaque ? Texts("sizeof(", type_code, ")") : Text$from_str(String((int64_t)unpadded_struct_size(t))); + Text_t unpadded_size = def->opaque ? Texts("sizeof(", type_code, ")") : Texts((int64_t)unpadded_struct_size(t)); Text_t typeinfo_code = Texts("extern const TypeInfo_t ", typeinfo_name, ";\n"); Text_t optional_code = EMPTY_TEXT; if (!def->opaque) { diff --git a/src/compile/tables.c b/src/compile/tables.c index b955178e..dde8669a 100644 --- a/src/compile/tables.c +++ b/src/compile/tables.c @@ -43,7 +43,7 @@ Text_t compile_typed_table(env_t *env, ast_t *ast, type_t *table_type) { size_t n = 0; for (ast_list_t *entry = table->entries; entry; entry = entry->next) ++n; - code = Texts(code, ", ", String((int64_t)n)); + code = Texts(code, ", ", (int64_t)n); for (ast_list_t *entry = table->entries; entry; entry = entry->next) { DeclareMatch(e, entry->ast, TableEntry); diff --git a/src/compile/types.c b/src/compile/types.c index aa06e2fd..f6f276a5 100644 --- a/src/compile/types.c +++ b/src/compile/types.c @@ -23,10 +23,10 @@ Text_t compile_type(type_t *t) { case ByteType: return Text("Byte_t"); case CStringType: return Text("const char*"); case BigIntType: return Text("Int_t"); - case IntType: return Texts("Int", String(Match(t, IntType)->bits), "_t"); + case IntType: return Texts("Int", (int32_t)Match(t, IntType)->bits, "_t"); case NumType: return Match(t, NumType)->bits == TYPE_NBITS64 ? Text("Num_t") - : Texts("Num", String(Match(t, NumType)->bits), "_t"); + : Texts("Num", (int32_t)Match(t, NumType)->bits, "_t"); case TextType: { DeclareMatch(text, t, TextType); if (!text->lang || streq(text->lang, "Text")) return Text("Text_t"); diff --git a/src/typecheck.c b/src/typecheck.c index 50df9327..07970bd9 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -349,8 +349,8 @@ void bind_statement(env_t *env, ast_t *statement) { code_err(statement, "Conversions are only supported for text, struct, and enum types, not ", type_to_str(ret_t)); - Text_t code = namespace_name(env, env->namespace, - Texts(name, "$", String(get_line_number(statement->file, statement->start)))); + Text_t code = + namespace_name(env, env->namespace, Texts(name, "$", get_line_number(statement->file, statement->start))); binding_t binding = {.type = type, .code = code}; env_t *type_ns = get_namespace_by_type(env, ret_t); List$insert(&type_ns->namespace->constructors, &binding, I(0), sizeof(binding)); diff --git a/src/types.c b/src/types.c index a4dcc5e5..b0caca1a 100644 --- a/src/types.c +++ b/src/types.c @@ -8,7 +8,6 @@ #include "environment.h" #include "stdlib/integers.h" -#include "stdlib/print.h" #include "stdlib/text.h" #include "stdlib/util.h" #include "types.h" @@ -30,7 +29,7 @@ Text_t type_to_text(type_t *t) { case CStringType: return Text("CString"); case TextType: return Match(t, TextType)->lang ? Text$from_str(Match(t, TextType)->lang) : Text("Text"); case BigIntType: return Text("Int"); - case IntType: return Texts("Int", String(Match(t, IntType)->bits)); + case IntType: return Texts("Int", (int32_t)Match(t, IntType)->bits); case NumType: return Match(t, NumType)->bits == TYPE_NBITS32 ? Text("Num32") : Text("Num"); case ListType: { DeclareMatch(list, t, ListType); @@ -84,7 +83,7 @@ Text_t type_to_text(type_t *t) { } default: { raise(SIGABRT); - return Texts("Unknown type: ", String(t->tag)); + return Texts("Unknown type: ", (int32_t)t->tag); } } } -- cgit v1.2.3 From e01383da05d5cf03d080854ac048df37df4d1b9a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 15:11:52 -0400 Subject: Change module install directory to PREFIX/lib/tomo_vX.Y/* --- CHANGES.md | 3 ++- Makefile | 2 +- docs/libraries.md | 4 ++-- docs/tomo.1.md | 2 +- docs/versions.md | 2 +- man/man1/tomo.1 | 4 ++-- src/compile/headers.c | 4 ++-- src/compile/statements.c | 4 ++-- src/modules.c | 44 +++++++++++++++++++------------------------- src/stdlib/stacktrace.c | 2 +- src/tomo.c | 28 +++++++++++++--------------- src/typecheck.c | 4 ++-- 12 files changed, 48 insertions(+), 55 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 965a1aea..97c80bdb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,8 @@ ## v0.3 - Added a versioning system based on `CHANGES.md` files and `modules.ini` - configuration for module aliases. + configuration for module aliases (now installed to + `$TOMO_PREFIX/lib/tomo_vX.Y/module_vZ.W`). - When attempting to run a program with a module that is not installed, Tomo can prompt the user to automatically install it. - Programs can use `--version` as a CLI flag to print a Tomo program's version diff --git a/Makefile b/Makefile index 983f41ae..ce05f76a 100644 --- a/Makefile +++ b/Makefile @@ -232,7 +232,7 @@ uninstall: exit 0; \ fi; \ rm -rvf "$(PREFIX)/bin/tomo" "$(PREFIX)/bin/tomo"[0-9]* "$(PREFIX)/bin/tomo_v"* "$(PREFIX)/include/tomo_v"* \ - "$(PREFIX)/lib/libtomo_v*" "$(PREFIX)/share/tomo_$(TOMO_VERSION)"; \ + "$(PREFIX)/lib/libtomo_v*" "$(PREFIX)/lib/tomo_$(TOMO_VERSION)"; \ sh link_versions.sh endif diff --git a/docs/libraries.md b/docs/libraries.md index 69608ae3..79477070 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -151,7 +151,7 @@ that can be used by other Tomo projects. You can build a library by running If you additionally add the `-I` flag, Tomo will copy the entire directory (excluding files and directories that begin with `.` such as `.git`) into -`~/.local/share/tomo_vX.Y/installed/` (where `X` and `Y` are the major/minor +`~/.local/lib/tomo_vX.Y/` (where `X` and `Y` are the major/minor version of the compiler). ### Using Shared Libraries @@ -169,7 +169,7 @@ When you build and install a library, its version is determined from a [Versions](versions.md)). The library's version number is added to the file path where the library is installed, so if the library `foo` has version `v1.2`, then it will be installed to -`~/.local/share/tomo_vX.Y/installed/foo_v1.2/`. When using a library, you must +`~/.local/lib/tomo_vX.Y/foo_v1.2/`. When using a library, you must explicitly supply either the exact version in the `use` statement like this: `use foo_v1.2`, or provide a `modules.ini` file that lists version information and other details about modules being used. For each module, you should provide diff --git a/docs/tomo.1.md b/docs/tomo.1.md index 3bf5e779..feed98af 100644 --- a/docs/tomo.1.md +++ b/docs/tomo.1.md @@ -69,4 +69,4 @@ C code, which is then compiled using a C compiler of your choice. : Print the Tomo installation prefix and exit. `-r`, `--run` -: Run an installed tomo program from `~/.local/share/tomo_vX.Y/installed`. +: Run an installed tomo program from `~/.local/lib/tomo_vX.Y/`. diff --git a/docs/versions.md b/docs/versions.md index c68d1f81..eb617f43 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -49,7 +49,7 @@ The version for the Tomo language itself will come into play in a few ways: (e.g. `~/.local/lib/libtomo_v1.2.so`) and headers (e.g. `~/.local/include/tomo_v1.2/tomo.h`). 4. Tomo libraries will be installed to a separate subdirectory for each version - of the compiler (e.g. `~/.local/share/tomo_v1.2/installed`). + of the compiler (e.g. `~/.local/lib/tomo_v1.2/`). ## Tomo Program Versions diff --git a/man/man1/tomo.1 b/man/man1/tomo.1 index c3330175..ae2b08d2 100644 --- a/man/man1/tomo.1 +++ b/man/man1/tomo.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pandoc 3.1.12.1 +.\" Automatically generated by Pandoc 3.1.13 .\" .TH "TOMO" "1" "June 11, 2024" "" "" .SH NAME @@ -68,6 +68,6 @@ Print the Tomo installation prefix and exit. .TP \f[B]\-r\f[R], \f[B]\-\-run\f[R] Run an installed tomo program from -\f[B]\[ti]/.local/share/tomo_vX.Y/installed\f[R]. +\f[B]\[ti]/.local/lib/tomo_vX.Y/\f[R]. .SH AUTHORS Bruce Hill (\f[I]bruce\[at]bruce\-hill.com\f[R]). diff --git a/src/compile/headers.c b/src/compile/headers.c index 8c0863ee..be564144 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -174,8 +174,8 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) module_info_t mod = get_module_info(ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, - NULL, &tm_files) + if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + &tm_files) != 0) { if (!try_install_module(mod)) code_err(ast, "Could not find library"); } diff --git a/src/compile/statements.c b/src/compile/statements.c index e296795b..82a91a5e 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -204,8 +204,8 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { module_info_t mod = get_module_info(ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, - NULL, &tm_files) + if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + &tm_files) != 0) { if (!try_install_module(mod)) code_err(ast, "Could not find library"); } diff --git a/src/modules.c b/src/modules.c index 40b3daf3..67b09fdb 100644 --- a/src/modules.c +++ b/src/modules.c @@ -50,34 +50,25 @@ module_info_t get_module_info(ast_t *use) { if (cached) return **cached; const char *name = Match(use, Use)->path; module_info_t *info = new (module_info_t, .name = name); - if (streq(name, "commands")) info->version = "v1.0"; - else if (streq(name, "random")) info->version = "v1.0"; - else if (streq(name, "base64")) info->version = "v1.0"; - else if (streq(name, "core")) info->version = "v1.0"; - else if (streq(name, "patterns")) info->version = "v1.1"; - else if (streq(name, "json")) info->version = "v1.0"; - else if (streq(name, "pthreads")) info->version = "v1.0"; - else if (streq(name, "shell")) info->version = "v1.0"; - else if (streq(name, "time")) info->version = "v1.0"; - else if (streq(name, "uuid")) info->version = "v1.0"; - else { - read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info); - read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info); - } + read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info); + read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info); Table$set(&cache, &use, &info, cache_type); return *info; } bool try_install_module(module_info_t mod) { if (mod.git) { - OptionalText_t answer = ask(Texts(Text("The module \""), Text$from_str(mod.name), - Text("\" is not installed.\nDo you want to install it from git URL "), - Text$from_str(mod.git), Text("? [Y/n] ")), + OptionalText_t answer = ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), + " is not installed.\nDo you want to install it from git URL ", + Text$from_str(mod.git), "? [Y/n] "), true, true); if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) return false; print("Installing ", mod.name, " from git..."); Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); + tmpdir = Path$child(tmpdir, Text$from_str(mod.name)); + Path$create_directory(tmpdir, 0755); + if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", tmpdir); else xsystem("git clone --depth=1 ", mod.git, " ", tmpdir); if (mod.path) xsystem("tomo -IL ", tmpdir, "/", mod.path); @@ -85,10 +76,10 @@ bool try_install_module(module_info_t mod) { Path$remove(tmpdir, true); return true; } else if (mod.url) { - OptionalText_t answer = ask(Texts(Text("The module "), Text$from_str(mod.name), - Text(" is not installed.\nDo you want to install it from URL "), - Text$from_str(mod.url), Text("? [Y/n] ")), - true, true); + OptionalText_t answer = + ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), + " is not installed.\nDo you want to install it from URL ", Text$from_str(mod.url), "? [Y/n] "), + true, true); if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) return false; @@ -101,6 +92,9 @@ bool try_install_module(module_info_t mod) { if (!p) return false; const char *extension = p + 1; Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); + tmpdir = Path$child(tmpdir, Text$from_str(mod.name)); + Path$create_directory(tmpdir, 0755); + xsystem("curl ", mod.url, " -o ", tmpdir); if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename); else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) xsystem("tar xf ", tmpdir, "/", filename); @@ -111,10 +105,10 @@ bool try_install_module(module_info_t mod) { Path$remove(tmpdir, true); return true; } else if (mod.path) { - OptionalText_t answer = ask(Texts(Text("The module "), Text$from_str(mod.name), - Text(" is not installed.\nDo you want to install it from path "), - Text$from_str(mod.path), Text("? [Y/n] ")), - true, true); + OptionalText_t answer = + ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), + " is not installed.\nDo you want to install it from path ", Text$from_str(mod.path), "? [Y/n] "), + true, true); if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) return false; diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c index 266dc4ef..c7ec54d3 100644 --- a/src/stdlib/stacktrace.c +++ b/src/stdlib/stacktrace.c @@ -98,7 +98,7 @@ void print_stacktrace(FILE *out, int offset) { cwd[cwd_len++] = '/'; cwd[cwd_len] = '\0'; - const char *install_dir = TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/"; + const char *install_dir = TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/"; static void *stack[1024]; int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0])); diff --git a/src/tomo.c b/src/tomo.c index da10df87..0e455ec6 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -87,8 +87,8 @@ static OptionalText_t show_codegen = NONE_TEXT, " -D_BSD_SOURCE" #endif " -DGC_THREADS" - " -I'" TOMO_PREFIX "/include' -I'" TOMO_PREFIX "/share/tomo_" TOMO_VERSION - "/installed' -I/usr/local/include"), + " -I'" TOMO_PREFIX "/include' -I'" TOMO_PREFIX "/lib/tomo_" TOMO_VERSION + "' -I/usr/local/include"), ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo_" TOMO_VERSION), ldflags = Text("-Wl,-rpath,'" TOMO_PREFIX "/lib',-rpath,/usr/local/lib" " -L/usr/local/lib"), @@ -163,8 +163,7 @@ int main(int argc, char *argv[]) { // Run a tool: if ((streq(argv[1], "-r") || streq(argv[1], "--run")) && argc >= 3) { if (strcspn(argv[2], "/;$") == strlen(argv[2])) { - const char *program = - String("'" TOMO_PREFIX "'/share/tomo_" TOMO_VERSION "/installed/", argv[2], "/", argv[2]); + const char *program = String("'" TOMO_PREFIX "'/lib/tomo_" TOMO_VERSION "/", argv[2], "/", argv[2]); execv(program, &argv[2]); } print_err("This is not an installed tomo program: ", argv[2]); @@ -183,7 +182,7 @@ int main(int argc, char *argv[]) { " --parse|-p: show parse tree\n" " --install|-I: install the executable or library\n" " --optimization|-O : set optimization level\n" - " --run|-r: run a program from " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed\n"); + " --run|-r: run a program from " TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "\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}, @@ -248,7 +247,7 @@ int main(int argc, char *argv[]) { for (int64_t i = 0; i < uninstall.length; i++) { Text_t *u = (Text_t *)(uninstall.data + i * uninstall.stride); - xsystem(as_owner, "rm -rvf '" TOMO_PREFIX "'/share/tomo_" TOMO_VERSION "/installed/", *u); + xsystem(as_owner, "rm -rvf '" TOMO_PREFIX "'/lib/tomo_" TOMO_VERSION "/", *u); print("Uninstalled ", *u); } @@ -410,8 +409,7 @@ void build_library(Path_t lib_dir) { void install_library(Path_t lib_dir) { Text_t lib_dir_name = Path$base_name(lib_dir); Text_t version_suffix = get_version_suffix(lib_dir); - Path_t dest = Path$child(Path$from_str(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed"), - Texts(lib_dir_name, version_suffix)); + Path_t dest = Path$child(Path$from_str(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION), Texts(lib_dir_name, version_suffix)); if (!Path$equal_values(lib_dir, dest)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_dir_name); xsystem(as_owner, "rm -rf '", dest, "'"); @@ -430,8 +428,8 @@ void install_library(Path_t lib_dir) { "' " ">/dev/null 2>/dev/null")); (void)result; - print("Installed \033[1m", lib_dir_name, "\033[m to " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", - lib_dir_name, version_suffix); + print("Installed \033[1m", lib_dir_name, "\033[m to " TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", lib_dir_name, + version_suffix); } void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs) { @@ -592,13 +590,13 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l module_info_t mod = get_module_info(stmt_ast); const char *full_name = mod.version ? String(mod.name, "_", mod.version) : mod.name; Text_t lib = - Texts(Text("-Wl,-rpath,'"), Text(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/"), - Text$from_str(full_name), Text("' '" TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/"), - Text$from_str(full_name), Text("/lib"), Text$from_str(full_name), Text(SHARED_SUFFIX "'")); + Texts(Text("-Wl,-rpath,'"), Text(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/"), Text$from_str(full_name), + Text("' '" TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/"), Text$from_str(full_name), Text("/lib"), + Text$from_str(full_name), Text(SHARED_SUFFIX "'")); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); - List_t children = Path$glob(Path$from_str( - String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", full_name, "/[!._0-9]*.tm"))); + List_t children = + Path$glob(Path$from_str(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); for (int64_t i = 0; i < children.length; i++) { Path_t *child = (Path_t *)(children.data + i * children.stride); Table_t discarded = {.fallback = to_compile}; diff --git a/src/typecheck.c b/src/typecheck.c index 07970bd9..adb267df 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -186,8 +186,8 @@ static env_t *load_module(env_t *env, ast_t *module_ast) { module_info_t mod = get_module_info(module_ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, - NULL, &tm_files) + if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + &tm_files) != 0) { if (!try_install_module(mod)) code_err(module_ast, "Couldn't find or install library: ", folder); } -- cgit v1.2.3 From 02a99d24a310c04622a875dcf4b0c6fd2de71332 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 15:32:19 -0400 Subject: Fix for versions getting double appended for libs --- src/modules.c | 28 +++++++++++++--------------- src/tomo.c | 26 +++++++++++++++----------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/modules.c b/src/modules.c index 67b09fdb..5327f28b 100644 --- a/src/modules.c +++ b/src/modules.c @@ -5,6 +5,7 @@ #include #include +#include "config.h" #include "modules.h" #include "stdlib/memory.h" #include "stdlib/paths.h" @@ -57,6 +58,8 @@ module_info_t get_module_info(ast_t *use) { } bool try_install_module(module_info_t mod) { + Path_t dest = Path$from_text( + Texts(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", Text$from_str(mod.name), "_", Text$from_str(mod.version))); if (mod.git) { OptionalText_t answer = ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), " is not installed.\nDo you want to install it from git URL ", @@ -65,15 +68,9 @@ bool try_install_module(module_info_t mod) { if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) return false; print("Installing ", mod.name, " from git..."); - Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); - tmpdir = Path$child(tmpdir, Text$from_str(mod.name)); - Path$create_directory(tmpdir, 0755); - - if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", tmpdir); - else xsystem("git clone --depth=1 ", mod.git, " ", tmpdir); - if (mod.path) xsystem("tomo -IL ", tmpdir, "/", mod.path); - else xsystem("tomo -IL ", tmpdir); - Path$remove(tmpdir, true); + if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest); + else xsystem("git clone --depth=1 ", mod.git, " ", dest); + xsystem("tomo -L ", dest); return true; } else if (mod.url) { OptionalText_t answer = @@ -96,12 +93,12 @@ bool try_install_module(module_info_t mod) { Path$create_directory(tmpdir, 0755); xsystem("curl ", mod.url, " -o ", tmpdir); - if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename); - else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) xsystem("tar xf ", tmpdir, "/", filename); + Path$create_directory(dest, 0755); + if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename, " -d ", dest); + else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) + xsystem("tar xf ", tmpdir, "/", filename, " -C ", dest); else return false; - const char *basename = String(string_slice(filename, strcspn(filename, "."))); - if (mod.path) xsystem("tomo -IL ", tmpdir, "/", basename, "/", mod.path); - else xsystem("tomo -IL ", tmpdir, "/", basename); + xsystem("tomo -L ", dest); Path$remove(tmpdir, true); return true; } else if (mod.path) { @@ -113,7 +110,8 @@ bool try_install_module(module_info_t mod) { return false; print("Installing ", mod.name, " from path..."); - xsystem("tomo -IL ", mod.path); + xsystem("ln -s ", mod.path, " ", dest); + xsystem("tomo -L ", dest); return true; } diff --git a/src/tomo.c b/src/tomo.c index 0e455ec6..b6440f10 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -368,21 +368,25 @@ static const char *get_version(Path_t lib_dir) { return String(string_slice(version_line + 4, strcspn(version_line + 4, "\r\n"))); } -static Text_t get_version_suffix(Path_t lib_dir) { return Texts(Text("_"), Text$from_str(get_version(lib_dir))); } +static Path_t with_version_suffix(Path_t lib_dir) { + Text_t suffix = Texts(Text("_"), Text$from_str(get_version(lib_dir))); + return Text$ends_with(Path$base_name(lib_dir), suffix, NULL) + ? lib_dir + : Path$sibling(lib_dir, Texts(Path$base_name(lib_dir), suffix)); +} void build_library(Path_t lib_dir) { lib_dir = Path$resolved(lib_dir, Path$current_dir()); if (!Path$is_directory(lib_dir, true)) print_err("Not a valid directory: ", lib_dir); - Text_t lib_dir_name = Path$base_name(lib_dir); List_t tm_files = Path$glob(Path$child(lib_dir, Text("[!._0-9]*.tm"))); env_t *env = fresh_scope(global_env(source_mapping)); List_t object_files = {}, extra_ldlibs = {}; compile_files(env, tm_files, &object_files, &extra_ldlibs); - Text_t version_suffix = get_version_suffix(lib_dir); - Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_dir_name, version_suffix, Text(SHARED_SUFFIX))); + Text_t versioned_dir = Path$base_name(with_version_suffix(lib_dir)); + Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), versioned_dir, Text(SHARED_SUFFIX))); if (!is_stale_for_any(shared_lib, object_files, false)) { if (verbose) whisper("Unchanged: ", shared_lib); return; @@ -390,10 +394,10 @@ void build_library(Path_t lib_dir) { FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), #ifdef __APPLE__ - " -Wl,-install_name,@rpath/'lib", lib_dir_name, version_suffix, SHARED_SUFFIX, + " -Wl,-install_name,@rpath/'lib", Path$base_name(lib_dir), version_suffix, SHARED_SUFFIX, "'" #else - " -Wl,-soname,'lib", lib_dir_name, version_suffix, SHARED_SUFFIX, + " -Wl,-soname,'lib", versioned_dir, SHARED_SUFFIX, "'" #endif " -shared ", @@ -408,8 +412,9 @@ void build_library(Path_t lib_dir) { void install_library(Path_t lib_dir) { Text_t lib_dir_name = Path$base_name(lib_dir); - Text_t version_suffix = get_version_suffix(lib_dir); - Path_t dest = Path$child(Path$from_str(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION), Texts(lib_dir_name, version_suffix)); + Text_t versioned_dir = Path$base_name(with_version_suffix(lib_dir)); + Path_t dest = Path$child(Path$from_str(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION), versioned_dir); + print("Installing ", lib_dir, " into ", dest); if (!Path$equal_values(lib_dir, dest)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_dir_name); xsystem(as_owner, "rm -rf '", dest, "'"); @@ -424,12 +429,11 @@ void install_library(Path_t lib_dir) { int result = system(String(as_owner, "debugedit -b ", lib_dir, " -d '", dest, "'" " '", - dest, "/lib", lib_dir_name, version_suffix, SHARED_SUFFIX, + dest, "/lib", versioned_dir, SHARED_SUFFIX, "' " ">/dev/null 2>/dev/null")); (void)result; - print("Installed \033[1m", lib_dir_name, "\033[m to " TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", lib_dir_name, - version_suffix); + print("Installed \033[1m", lib_dir_name, "\033[m to " TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", versioned_dir); } void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs) { -- cgit v1.2.3 From ed50c5fefb8892ad2ba5262491669f268ddbd436 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 16:44:58 -0400 Subject: Overhaul code to stop keeping examples and libraries in the same repo, but instead spin each out into its own repo. --- Makefile | 17 +- examples/README.md | 19 +- examples/colorful/CHANGES.md | 13 - examples/colorful/README.md | 70 -- examples/colorful/colorful.tm | 220 ----- examples/colorful/modules.ini | 2 - examples/colorful/test.colors | 9 - examples/coroutines/ACO_LICENSE | 202 ----- examples/coroutines/CHANGES.md | 5 - examples/coroutines/README.md | 30 - examples/coroutines/aco.c | 458 ---------- examples/coroutines/aco.h | 213 ----- examples/coroutines/acoyield.S | 208 ----- examples/coroutines/coroutines.tm | 67 -- examples/game/CHANGES.md | 5 - examples/game/Makefile | 17 - examples/game/README.md | 13 - examples/game/box.tm | 7 - examples/game/game.tm | 29 - examples/game/map.txt | 18 - examples/game/player.tm | 28 - examples/game/raylib.tm | 63 -- examples/game/world.tm | 88 -- examples/hello.tm | 3 + examples/http-server/CHANGES.md | 5 - examples/http-server/README.md | 8 - examples/http-server/connection-queue.tm | 25 - examples/http-server/http-server.tm | 149 ---- examples/http-server/modules.ini | 8 - examples/http-server/sample-site/foo.html | 6 - examples/http-server/sample-site/hello.txt | 1 - examples/http-server/sample-site/index.html | 16 - examples/http-server/sample-site/random.tm | 17 - examples/http-server/sample-site/styles.css | 11 - examples/http/CHANGES.md | 5 - examples/http/http.tm | 111 --- examples/ini/CHANGES.md | 5 - examples/ini/ini.tm | 61 -- examples/ini/modules.ini | 2 - examples/ini/test.ini | 8 - examples/log/CHANGES.md | 5 - examples/log/log.tm | 50 -- examples/vectors/CHANGES.md | 5 - examples/vectors/vectors.tm | 136 --- examples/wrap/CHANGES.md | 5 - examples/wrap/wrap.tm | 102 --- lib/README.md | 19 - lib/base64/CHANGES.md | 5 - lib/base64/README.md | 3 - lib/base64/base64.tm | 96 --- lib/commands/CHANGES.md | 5 - lib/commands/README.md | 5 - lib/commands/commands.c | 295 ------- lib/commands/commands.tm | 90 -- lib/core/CHANGES.md | 5 - lib/core/core.tm | 9 - lib/json/CHANGES.md | 5 - lib/json/README.md | 18 - lib/json/json.tm | 173 ---- lib/patterns/CHANGES.md | 8 - lib/patterns/README.md | 444 ---------- lib/patterns/_test.tm | 256 ------ lib/patterns/match_type.h | 9 - lib/patterns/patterns.c | 1212 --------------------------- lib/patterns/patterns.tm | 66 -- lib/pthreads/CHANGES.md | 5 - lib/pthreads/pthreads.tm | 117 --- lib/random/CHANGES.md | 5 - lib/random/README.md | 196 ----- lib/random/chacha.h | 192 ----- lib/random/random.tm | 234 ------ lib/random/sysrandom.h | 17 - lib/shell/CHANGES.md | 5 - lib/shell/README.md | 13 - lib/shell/shell.tm | 44 - lib/time/CHANGES.md | 5 - lib/time/README.md | 5 - lib/time/time.tm | 214 ----- lib/time/time_defs.h | 34 - lib/uuid/CHANGES.md | 5 - lib/uuid/uuid.tm | 39 - src/compile/headers.c | 2 +- src/compile/statements.c | 2 +- src/modules.c | 67 +- src/modules.h | 3 +- src/tomo.c | 11 +- src/typecheck.c | 2 +- 87 files changed, 85 insertions(+), 6400 deletions(-) delete mode 100644 examples/colorful/CHANGES.md delete mode 100644 examples/colorful/README.md delete mode 100644 examples/colorful/colorful.tm delete mode 100644 examples/colorful/modules.ini delete mode 100644 examples/colorful/test.colors delete mode 100644 examples/coroutines/ACO_LICENSE delete mode 100644 examples/coroutines/CHANGES.md delete mode 100644 examples/coroutines/README.md delete mode 100644 examples/coroutines/aco.c delete mode 100644 examples/coroutines/aco.h delete mode 100644 examples/coroutines/acoyield.S delete mode 100644 examples/coroutines/coroutines.tm delete mode 100644 examples/game/CHANGES.md delete mode 100644 examples/game/Makefile delete mode 100644 examples/game/README.md delete mode 100644 examples/game/box.tm delete mode 100644 examples/game/game.tm delete mode 100644 examples/game/map.txt delete mode 100644 examples/game/player.tm delete mode 100644 examples/game/raylib.tm delete mode 100644 examples/game/world.tm create mode 100644 examples/hello.tm delete mode 100644 examples/http-server/CHANGES.md delete mode 100644 examples/http-server/README.md delete mode 100644 examples/http-server/connection-queue.tm delete mode 100644 examples/http-server/http-server.tm delete mode 100644 examples/http-server/modules.ini delete mode 100644 examples/http-server/sample-site/foo.html delete mode 100644 examples/http-server/sample-site/hello.txt delete mode 100644 examples/http-server/sample-site/index.html delete mode 100755 examples/http-server/sample-site/random.tm delete mode 100644 examples/http-server/sample-site/styles.css delete mode 100644 examples/http/CHANGES.md delete mode 100644 examples/http/http.tm delete mode 100644 examples/ini/CHANGES.md delete mode 100644 examples/ini/ini.tm delete mode 100644 examples/ini/modules.ini delete mode 100644 examples/ini/test.ini delete mode 100644 examples/log/CHANGES.md delete mode 100644 examples/log/log.tm delete mode 100644 examples/vectors/CHANGES.md delete mode 100644 examples/vectors/vectors.tm delete mode 100644 examples/wrap/CHANGES.md delete mode 100644 examples/wrap/wrap.tm delete mode 100644 lib/README.md delete mode 100644 lib/base64/CHANGES.md delete mode 100644 lib/base64/README.md delete mode 100644 lib/base64/base64.tm delete mode 100644 lib/commands/CHANGES.md delete mode 100644 lib/commands/README.md delete mode 100644 lib/commands/commands.c delete mode 100644 lib/commands/commands.tm delete mode 100644 lib/core/CHANGES.md delete mode 100644 lib/core/core.tm delete mode 100644 lib/json/CHANGES.md delete mode 100644 lib/json/README.md delete mode 100644 lib/json/json.tm delete mode 100644 lib/patterns/CHANGES.md delete mode 100644 lib/patterns/README.md delete mode 100644 lib/patterns/_test.tm delete mode 100644 lib/patterns/match_type.h delete mode 100644 lib/patterns/patterns.c delete mode 100644 lib/patterns/patterns.tm delete mode 100644 lib/pthreads/CHANGES.md delete mode 100644 lib/pthreads/pthreads.tm delete mode 100644 lib/random/CHANGES.md delete mode 100644 lib/random/README.md delete mode 100644 lib/random/chacha.h delete mode 100644 lib/random/random.tm delete mode 100644 lib/random/sysrandom.h delete mode 100644 lib/shell/CHANGES.md delete mode 100644 lib/shell/README.md delete mode 100644 lib/shell/shell.tm delete mode 100644 lib/time/CHANGES.md delete mode 100644 lib/time/README.md delete mode 100644 lib/time/time.tm delete mode 100644 lib/time/time_defs.h delete mode 100644 lib/uuid/CHANGES.md delete mode 100644 lib/uuid/uuid.tm diff --git a/Makefile b/Makefile index ce05f76a..a7952628 100644 --- a/Makefile +++ b/Makefile @@ -184,10 +184,12 @@ man/man1/tomo.1: docs/tomo.1.md pandoc --lua-filter=docs/.pandoc/bold-code.lua -s $< -t man -o $@ examples: - ./local-tomo -qIL examples/log examples/ini examples/vectors examples/http examples/wrap examples/colorful - ./local-tomo -e examples/game/game.tm examples/http-server/http-server.tm + ./local-tomo -L modules/examples.ini ./local-tomo examples/learnxiny.tm +core-libs: + ./local-tomo -L modules/core.ini + deps: bash ./install_dependencies.sh @@ -217,14 +219,7 @@ install-files: build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) cp man/man3/* "$(PREFIX)/man/man3/"; \ sh link_versions.sh -install-libs: build/bin/$(EXE_FILE) check-utilities - if ! [ -w "$(PREFIX)" ]; then \ - $(SUDO) -u $(OWNER) $(MAKE) install-libs; \ - exit 0; \ - fi; \ - ./local-tomo -qIL lib/patterns lib/json lib/time lib/commands lib/shell lib/random lib/base64 lib/pthreads lib/uuid lib/core - -install: install-files install-libs +install: install-files uninstall: if ! [ -w "$(PREFIX)" ]; then \ @@ -238,4 +233,4 @@ uninstall: endif .SUFFIXES: -.PHONY: all clean install install-files install-libs uninstall test tags examples deps check-utilities check-c-compiler check-libs version +.PHONY: all clean install install-files uninstall test tags core-libs examples deps check-utilities check-c-compiler check-libs version diff --git a/examples/README.md b/examples/README.md index 9e9291e5..d527d692 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,19 +4,20 @@ This folder contains some example programs and libraries. ## Example Programs +- [hello.tm](hello.tm): A Hello World program. - [learnxiny.tm](learnxiny.tm): A quick overview of language features in the style of [learnxinyminutes.com](https://learnxinyminutes.com/). -- [game](game/): An example game using raylib. -- [http-server](http-server/): A multithreaded HTTP server. -- [wrap](wrap/): A command-line program to wrap text. +- [game](https://github.com/bruce-hill/tomo-game): An example game using raylib. +- [http-server](https://github.com/bruce-hill/http-server): A multithreaded HTTP server. +- [wrap](https://github.com/bruce-hill/wrap): A command-line program to wrap text. ## Example Libraries Libraries can be installed with `tomo -IL ./library-folder` -- [colorful](colorful/): A DSL useful for rendering terminal colors. -- [coroutines](coroutines/): A library for stackful coroutines similar to Lua's. (Note: only works on x86_64) -- [http](http/): An HTTP library to make basic synchronous HTTP requests. -- [ini](ini/): An INI configuration file reader tool. -- [log](log/): A logging utility. -- [vectors](vectors/): A math vector library. +- [colorful](https://github.com/bruce-hill/tomo-colorful): A DSL useful for rendering terminal colors. +- [coroutines](https://github.com/bruce-hill/tomo-coroutines): A library for stackful coroutines similar to Lua's. (Note: only works on x86_64) +- [http](https://github.com/bruce-hill/tomo-http): An HTTP library to make basic synchronous HTTP requests. +- [ini](https://github.com/bruce-hill/tomo-ini): An INI configuration file reader tool. +- [log](https://github.com/bruce-hill/tomo-log): A logging utility. +- [vectors](https://github.com/bruce-hill/tomo-vectors): A math vector library. diff --git a/examples/colorful/CHANGES.md b/examples/colorful/CHANGES.md deleted file mode 100644 index 093cc077..00000000 --- a/examples/colorful/CHANGES.md +++ /dev/null @@ -1,13 +0,0 @@ -# Version History - -## v1.2 - -- Version bump for patterns. - -## v1.1 - -- Added syntax for `@(strikethrough:...)` or `@(s:...)` - -## v1.0 - -Initial version diff --git a/examples/colorful/README.md b/examples/colorful/README.md deleted file mode 100644 index faded9b1..00000000 --- a/examples/colorful/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Colorful Lang - -Colorful is a `lang` that lets you write colorful text for the terminal without -having to stress about managing state for color highlighting. - -## Grammar - -The grammar looks like this: - -``` -colorful <- ("@(at)" / "@(lparen)" / "@(rparen)" # Escapes - / "@(" attributes ":" colorful ")" # Colorful text - / .)* # Plain text - -attributes <- (attribute ("," attribute)*)? - -attribute <- color # Color defaults to foreground - / "fg=" color # Foreground color - / "bg=" color # Background color - / "ul=" color # Underline color - / "b" / "bold" - / "d" / "dim" - / "u" / "underline" - / "i" / "italic" - / "B" / "blink" - / "r" / "reverse" - # These are rarely supported by terminals: - / "fraktur" - / "frame" - / "encircle" - / "overline" - / "super" / "superscript" - / "sub" / "subscript" - -color <- "black" / "red" / "green" / "yellow" / "blue" / "magenta" / "cyan" / "white" - # All caps colors are "bright" colors (not always supported): - / "BLACK" / "RED" / "GREEN" / "YELLOW" / "BLUE" / "MAGENTA" / "CYAN" / "WHITE" - / "default" - / "#" 6 hex # Values 0x000000-0xFFFFFF - / "#" 3 hex # Values 0x000-0xFFF - / 1-3 digit # Values 0-255 -``` - -## Command Line Usage - -You can run `colorful` as a standalone executable to render colorful text with -ANSI escape sequences so it looks nice on a terminal. - -``` -colorful [--help] [texts...] [--by-line] [--files ...] -``` - -## Library Usage - -`colorful` can also be used as a Tomo library: - -```ini -# modules.ini -[colorful] -version=v1.0 -``` - -```tomo -use colorful - -$Colorful" - @(blue:Welcome to the @(bold:party)!) - We have @(green,bold:colors)! -".print() -``` diff --git a/examples/colorful/colorful.tm b/examples/colorful/colorful.tm deleted file mode 100644 index 5b01cfd5..00000000 --- a/examples/colorful/colorful.tm +++ /dev/null @@ -1,220 +0,0 @@ -# Colorful language - -HELP := " - colorful: A domain-specific language for writing colored text to the terminal - Usage: colorful [args...] [--by-line] [--files files...] -" - -CSI := "\033[" - -use patterns - -lang Colorful - convert(text:Text -> Colorful) - text = text.translate({"@"="@(at)", "("="@(lparen)", ")"="@(rparen)"}) - return Colorful.from_text(text) - - convert(i:Int -> Colorful) return Colorful.from_text("$i") - convert(n:Num -> Colorful) return Colorful.from_text("$n") - - func for_terminal(c:Colorful -> Text) - return CSI ++ "m" ++ _for_terminal(c, _TermState()) - - func print(c:Colorful, newline=yes) - say(c.for_terminal(), newline=newline) - - -func main(texts:[Text], files:[Path]=[], by_line=no) - for i,text in texts - colorful := Colorful.from_text(text) - colorful.print(newline=no) - if i == texts.length then say("") - else say(" ", newline=no) - - if texts.length == 0 and files.length == 0 - files = [(/dev/stdin)] - - for file in files - if by_line - for line in file.by_line() or exit("Could not read file: $file") - colorful := Colorful.from_text(line) - colorful.print() - else - colorful := Colorful.from_text(file.read() or exit("Could not read file: $file")) - colorful.print(newline=no) - - -func _for_terminal(c:Colorful, state:_TermState -> Text) - return c.text.map_pattern(recursive=no, $Pat/@(?)/, func(m:PatternMatch) _add_ansi_sequences(m.captures[1], state)) - -enum _Color(Default, Bright(color:Int16), Color8Bit(color:Int16), Color24Bit(color:Int32)) - func from_text(text:Text -> _Color?) - if text.matches_pattern($Pat/#{3-6 hex}/) - hex := text.from(2) - return none unless hex.length == 3 or hex.length == 6 - if hex.length == 3 - hex = hex[1]++hex[1]++hex[2]++hex[2]++hex[3]++hex[3] - n := Int32.parse("0x" ++ hex) or return none - return Color24Bit(n) - else if text.matches_pattern($Pat/{1-3 digit}/) - n := Int16.parse(text) or return none - if n >= 0 and n <= 255 return Color8Bit(n) - else if text == "black" return _Color.Color8Bit(0) - else if text == "red" return _Color.Color8Bit(1) - else if text == "green" return _Color.Color8Bit(2) - else if text == "yellow" return _Color.Color8Bit(3) - else if text == "blue" return _Color.Color8Bit(4) - else if text == "magenta" return _Color.Color8Bit(5) - else if text == "cyan" return _Color.Color8Bit(6) - else if text == "white" return _Color.Color8Bit(7) - else if text == "default" return _Color.Default - else if text == "BLACK" return _Color.Bright(0) - else if text == "RED" return _Color.Bright(1) - else if text == "GREEN" return _Color.Bright(2) - else if text == "YELLOW" return _Color.Bright(3) - else if text == "BLUE" return _Color.Bright(4) - else if text == "MAGENTA" return _Color.Bright(5) - else if text == "CYAN" return _Color.Bright(6) - else if text == "WHITE" return _Color.Bright(7) - return none - - func fg(c:_Color -> Text) - when c is Color8Bit(color) - if color >= 0 and color <= 7 return "$(30+color)" - else if color >= 0 and color <= 255 return "38;5;$color" - is Color24Bit(hex) - if hex >= 0 and hex <= 0xFFFFFF - return "38;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)" - is Bright(color) - if color <= 7 return "$(90+color)" - is Default - return "39" - fail("Invalid foreground color: '$c'") - - func bg(c:_Color -> Text) - when c is Color8Bit(color) - if color >= 0 and color <= 7 return "$(40+color)" - else if color >= 0 and color <= 255 return "48;5;$color" - is Color24Bit(hex) - if hex >= 0 and hex <= 0xFFFFFF - return "48;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)" - is Bright(color) - if color <= 7 return "$(90+color)" - is Default - return "49" - fail("Invalid background color: '$c'") - - func underline(c:_Color -> Text) - when c is Color8Bit(color) - if color >= 0 and color <= 255 return "58;5;$color" - is Color24Bit(hex) - if hex >= 0 and hex <= 0xFFFFFF - return "58;2;$((hex >> 16) and 0xFF);$((hex >> 8) and 0xFF);$((hex >> 0) and 0xFF)" - is Default - return "59" - is Bright(color) - pass - fail("Invalid underline color: '$c'") - -func _toggle(sequences:&[Text], cur,new:Bool, apply,unapply:Text; inline) - if new and not cur - sequences.insert(apply) - else if cur and not new - sequences.insert(unapply) - -func _toggle2(sequences:&[Text], cur1,cur2,new1,new2:Bool, apply1,apply2,unapply:Text; inline) - return if new1 == cur1 and new2 == cur2 - if (cur1 and not new1) or (cur2 and not new2) # Gotta wipe at least one - sequences.insert(unapply) - cur1, cur2 = no, no # Wiped out - - if new1 and not cur1 - sequences.insert(apply1) - if new2 and not cur2 - sequences.insert(apply2) - -struct _TermState( - bold=no, dim=no, italic=no, underline=no, blink=no, - reverse=no, conceal=no, strikethrough=no, fraktur=no, frame=no, - encircle=no, overline=no, superscript=no, subscript=no, - bg=_Color.Default, fg=_Color.Default, underline_color=_Color.Default, -) - - func apply(old,new:_TermState -> Text) - sequences : &[Text] - _toggle2(sequences, old.bold, old.dim, new.bold, new.dim, "1", "2", "22") - _toggle2(sequences, old.italic, old.fraktur, new.italic, new.fraktur, "3", "20", "23") - _toggle(sequences, old.underline, new.underline, "4", "24") - _toggle(sequences, old.blink, new.blink, "5", "25") - _toggle(sequences, old.reverse, new.reverse, "7", "27") - _toggle(sequences, old.conceal, new.conceal, "8", "28") - _toggle(sequences, old.strikethrough, new.strikethrough, "9", "29") - _toggle2(sequences, old.frame, old.encircle, new.frame, new.frame, "51", "52", "54") - _toggle(sequences, old.overline, new.overline, "53", "55") - _toggle2(sequences, old.subscript, old.subscript, new.superscript, new.superscript, "73", "74", "75") - - if new.bg != old.bg - sequences.insert(new.bg.bg()) - - if new.fg != old.fg - sequences.insert(new.fg.fg()) - - if new.underline_color != old.underline_color - sequences.insert(new.underline_color.underline()) - - if sequences.length == 0 - return "" - return CSI ++ ";".join(sequences) ++ "m" - -func _add_ansi_sequences(text:Text, prev_state:_TermState -> Text) - if text == "lparen" return "(" - else if text == "rparen" return ")" - else if text == "@" or text == "at" return "@" - parts := ( - text.pattern_captures($Pat/{0+..}:{0+..}/) or - return "@("++_for_terminal(Colorful.from_text(text), prev_state)++")" - ) - attributes := parts[1].split_pattern($Pat/{0+space},{0+space}/) - new_state := prev_state - for attr in attributes - if attr.starts_with("fg=") - new_state.fg = _Color.from_text(attr.from(4))! - else if attr.starts_with("bg=") - new_state.bg = _Color.from_text(attr.from(4))! - else if attr.starts_with("ul=") - new_state.underline_color = _Color.from_text(attr.from(4))! - else if color := _Color.from_text(attr) - new_state.fg = color - else if attr == "b" or attr == "bold" - new_state.bold = yes - else if attr == "d" or attr == "dim" - new_state.dim = yes - else if attr == "i" or attr == "italic" - new_state.italic = yes - else if attr == "u" or attr == "underline" - new_state.underline = yes - else if attr == "s" or attr == "strikethrough" - new_state.strikethrough = yes - else if attr == "B" or attr == "blink" - new_state.blink = yes - else if attr == "r" or attr == "reverse" - new_state.reverse = yes - else if attr == "fraktur" - new_state.fraktur = yes - else if attr == "frame" - new_state.frame = yes - else if attr == "encircle" - new_state.encircle = yes - else if attr == "overline" - new_state.overline = yes - else if attr == "super" or attr == "superscript" - new_state.superscript = yes - else if attr == "sub" or attr == "subscript" - new_state.subscript = yes - else - fail("Invalid attribute: '$attr'") - - result := prev_state.apply(new_state) - result ++= parts[2].map_pattern(recursive=no, $Pat/@(?)/, func(m:PatternMatch) _add_ansi_sequences(m.captures[1], new_state)) - result ++= new_state.apply(prev_state) - return result diff --git a/examples/colorful/modules.ini b/examples/colorful/modules.ini deleted file mode 100644 index 5e4b5b0a..00000000 --- a/examples/colorful/modules.ini +++ /dev/null @@ -1,2 +0,0 @@ -[patterns] -version=v1.1 diff --git a/examples/colorful/test.colors b/examples/colorful/test.colors deleted file mode 100644 index 314b8b05..00000000 --- a/examples/colorful/test.colors +++ /dev/null @@ -1,9 +0,0 @@ -This is some text that has @(bold:@(red:c)@(yellow:o)@(green:l)@(cyan:o)@(blue:r)@(magenta:s))! - -@(fg=#aaf,b:You can have @(red:nested) color directives and stuff (even in -parens) will be handled @(i:right)) - -@(dim:The output @(bold:ANSI) sequences will be optimal, even if you have -nested stuff like bold and dim) - -(which is distinct from @(bold:BOLD) by itself) diff --git a/examples/coroutines/ACO_LICENSE b/examples/coroutines/ACO_LICENSE deleted file mode 100644 index ef4f82f0..00000000 --- a/examples/coroutines/ACO_LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [2018] [Sen Han <00hnes@gmail.com>] - Copyright [2024] [Bruce Hill ] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/examples/coroutines/CHANGES.md b/examples/coroutines/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/coroutines/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/coroutines/README.md b/examples/coroutines/README.md deleted file mode 100644 index 644c0e07..00000000 --- a/examples/coroutines/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Tomo Coroutine Library - -This is a coroutine library built on top of a modified version of -[libaco](https://libaco.org). - -## Example Usage - -```ini -# modules.ini -[coroutines] -version=v1.0 -``` - -```tomo -use coroutines - -func main() - co := Coroutine(func() - say("I'm in the coroutine!") - yield() - say("I'm back in the coroutine!") - ) - >> co - say("I'm in the main func") - >> co.resume() - say("I'm back in the main func") - >> co.resume() - say("I'm back in the main func again") - >> co.resume() -``` diff --git a/examples/coroutines/aco.c b/examples/coroutines/aco.c deleted file mode 100644 index 258efe28..00000000 --- a/examples/coroutines/aco.c +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright 2018 Sen Han <00hnes@gmail.com> -// Modifications copyright 2025 Bruce Hill -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#define _GNU_SOURCE - -#include "aco.h" -#include -#include - -#ifndef public -#define public __attribute__((visibility("default"))) -#endif - -#define aco_size_t_safe_add_assert(a, b) aco_assert((a) + (b) >= (a)) - -static void aco_default_protector_last_word(void *); - -void *(*aco_alloc_fn)(size_t) = malloc; -void (*aco_dealloc_fn)(void *) = free; - -#define aco_alloc(size) \ - ({ \ - void *_ptr = aco_alloc_fn(size); \ - if (aco_unlikely((_ptr) == NULL)) { \ - fprintf(stderr, "Aborting: failed to allocate memory: %s:%d:%s\n", __FILE__, __LINE__, \ - __PRETTY_FUNCTION__); \ - abort(); \ - } \ - _ptr; \ - }) - -// aco's Global Thread Local Storage variable `co` -public -__thread aco_t *aco_gtls_co; -static __thread aco_cofuncp_t aco_gtls_last_word_fp = aco_default_protector_last_word; - -#ifdef __i386__ -static __thread void *aco_gtls_fpucw_mxcsr[2]; -#elif __x86_64__ -static __thread void *aco_gtls_fpucw_mxcsr[1]; -#else -#error "platform not supporteded yet" -#endif - -public -void aco_runtime_test(void) { -#ifdef __i386__ - _Static_assert(sizeof(void *) == 4, "require 'sizeof(void*) == 4'"); -#elif __x86_64__ - _Static_assert(sizeof(void *) == 8, "require 'sizeof(void*) == 8'"); - _Static_assert(sizeof(__uint128_t) == 16, "require 'sizeof(__uint128_t) == 16'"); -#else -#error "platform not supporteded yet" -#endif - _Static_assert(sizeof(int) >= 4, "require 'sizeof(int) >= 4'"); - aco_assert(sizeof(int) >= 4); - _Static_assert(sizeof(int) <= sizeof(size_t), "require 'sizeof(int) <= sizeof(size_t)'"); - aco_assert(sizeof(int) <= sizeof(size_t)); -} - -#ifdef __x86_64__ -static inline void aco_fast_memcpy(void *dst, const void *src, size_t sz) { - if (((uintptr_t)src & 0x0f) != 0 || ((uintptr_t)dst & 0x0f) != 0 || (sz & 0x0f) != 0x08 || (sz >> 4) > 8) { - memcpy(dst, src, sz); - return; - } - - __uint128_t xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7; - switch (sz >> 4) { - case 0: break; - case 1: - xmm0 = *((__uint128_t *)src + 0); - *((__uint128_t *)dst + 0) = xmm0; - break; - case 2: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - break; - case 3: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - break; - case 4: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - break; - case 5: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - break; - case 6: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - xmm5 = *((__uint128_t *)src + 5); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - *((__uint128_t *)dst + 5) = xmm5; - break; - case 7: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - xmm5 = *((__uint128_t *)src + 5); - xmm6 = *((__uint128_t *)src + 6); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - *((__uint128_t *)dst + 5) = xmm5; - *((__uint128_t *)dst + 6) = xmm6; - break; - case 8: - xmm0 = *((__uint128_t *)src + 0); - xmm1 = *((__uint128_t *)src + 1); - xmm2 = *((__uint128_t *)src + 2); - xmm3 = *((__uint128_t *)src + 3); - xmm4 = *((__uint128_t *)src + 4); - xmm5 = *((__uint128_t *)src + 5); - xmm6 = *((__uint128_t *)src + 6); - xmm7 = *((__uint128_t *)src + 7); - *((__uint128_t *)dst + 0) = xmm0; - *((__uint128_t *)dst + 1) = xmm1; - *((__uint128_t *)dst + 2) = xmm2; - *((__uint128_t *)dst + 3) = xmm3; - *((__uint128_t *)dst + 4) = xmm4; - *((__uint128_t *)dst + 5) = xmm5; - *((__uint128_t *)dst + 6) = xmm6; - *((__uint128_t *)dst + 7) = xmm7; - break; - } - *((uint64_t *)((uintptr_t)dst + sz - 8)) = *((uint64_t *)((uintptr_t)src + sz - 8)); -} -#endif - -void aco_default_protector_last_word(void *_) { - aco_t *co = aco_get_co(); - // do some log about the offending `co` - fprintf(stderr, "error: aco_default_protector_last_word triggered\n"); - fprintf(stderr, - "error: co:%p should call `aco_exit()` instead of direct " - "`return` in co_fp:%p to finish its execution\n", - co, (void *)co->fp); - aco_assert(0); -} - -public -void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *)) { - aco_alloc_fn = alloc; - aco_dealloc_fn = dealloc; -} - -public -void aco_thread_init(aco_cofuncp_t last_word_co_fp) { - aco_save_fpucw_mxcsr(aco_gtls_fpucw_mxcsr); - - if ((void *)last_word_co_fp != NULL) aco_gtls_last_word_fp = last_word_co_fp; -} - -// This function `aco_funcp_protector` should never be -// called. If it's been called, that means the offending -// `co` didn't call aco_exit(co) instead of `return` to -// finish its execution. -public -void aco_funcp_protector(void) { - if ((void *)(aco_gtls_last_word_fp) != NULL) { - aco_gtls_last_word_fp(NULL); - } else { - aco_default_protector_last_word(NULL); - } - aco_assert(0); -} - -public -aco_shared_stack_t *aco_shared_stack_new(size_t sz) { return aco_shared_stack_new2(sz, 1); } - -public -aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled) { - if (sz == 0) { - sz = 1024 * 1024 * 2; - } - if (sz < 4096) { - sz = 4096; - } - aco_assert(sz > 0); - - size_t u_pgsz = 0; - if (guard_page_enabled) { - // although gcc's Built-in Functions to Perform Arithmetic with - // Overflow Checking is better, but it would require gcc >= 5.0 - long pgsz = sysconf(_SC_PAGESIZE); - // pgsz must be > 0 && a power of two - aco_assert(pgsz > 0 && (((pgsz - 1) & pgsz) == 0)); - u_pgsz = (size_t)((unsigned long)pgsz); - // it should be always true in real life - aco_assert(u_pgsz == (unsigned long)pgsz && ((u_pgsz << 1) >> 1) == u_pgsz); - if (sz <= u_pgsz) { - sz = u_pgsz << 1; - } else { - size_t new_sz; - if ((sz & (u_pgsz - 1)) != 0) { - new_sz = (sz & (~(u_pgsz - 1))); - aco_assert(new_sz >= u_pgsz); - aco_size_t_safe_add_assert(new_sz, (u_pgsz << 1)); - new_sz = new_sz + (u_pgsz << 1); - aco_assert(sz / u_pgsz + 2 == new_sz / u_pgsz); - } else { - aco_size_t_safe_add_assert(sz, u_pgsz); - new_sz = sz + u_pgsz; - aco_assert(sz / u_pgsz + 1 == new_sz / u_pgsz); - } - sz = new_sz; - aco_assert((sz / u_pgsz > 1) && ((sz & (u_pgsz - 1)) == 0)); - } - } - - aco_shared_stack_t *p = aco_alloc(sizeof(aco_shared_stack_t)); - memset(p, 0, sizeof(aco_shared_stack_t)); - - if (guard_page_enabled) { - p->real_ptr = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (aco_unlikely(p->real_ptr == MAP_FAILED)) { - fprintf(stderr, "Aborting: failed to allocate memory: %s:%d:%s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__); - abort(); - } - p->guard_page_enabled = true; - aco_assert(0 == mprotect(p->real_ptr, u_pgsz, PROT_READ)); - - p->ptr = (void *)(((uintptr_t)p->real_ptr) + u_pgsz); - p->real_sz = sz; - aco_assert(sz >= (u_pgsz << 1)); - p->sz = sz - u_pgsz; - } else { - // p->guard_page_enabled = 0; - p->sz = sz; - p->ptr = aco_alloc(sz); - } - - p->owner = NULL; -#ifdef ACO_USE_VALGRIND - p->valgrind_stk_id = VALGRIND_STACK_REGISTER(p->ptr, (void *)((uintptr_t)p->ptr + p->sz)); -#endif -#if defined(__i386__) || defined(__x86_64__) - uintptr_t u_p = (uintptr_t)(p->sz - (sizeof(void *) << 1) + (uintptr_t)p->ptr); - u_p = (u_p >> 4) << 4; - p->align_highptr = (void *)u_p; - p->align_retptr = (void *)(u_p - sizeof(void *)); - *((void **)(p->align_retptr)) = (void *)(aco_funcp_protector_asm); - aco_assert(p->sz > (16 + (sizeof(void *) << 1) + sizeof(void *))); - p->align_limit = p->sz - 16 - (sizeof(void *) << 1); -#else -#error "platform not supporteded yet" -#endif - return p; -} - -public -void aco_shared_stack_destroy(aco_shared_stack_t *sstk) { - aco_assert(sstk != NULL && sstk->ptr != NULL); -#ifdef ACO_USE_VALGRIND - VALGRIND_STACK_DEREGISTER(sstk->valgrind_stk_id); -#endif - if (sstk->guard_page_enabled) { - aco_assert(0 == munmap(sstk->real_ptr, sstk->real_sz)); - sstk->real_ptr = NULL; - sstk->ptr = NULL; - } else { - if (aco_dealloc_fn != NULL) aco_dealloc_fn(sstk->ptr); - sstk->ptr = NULL; - } - if (aco_dealloc_fn != NULL) aco_dealloc_fn(sstk); -} - -public -aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp, - void *arg) { - aco_t *p = aco_alloc(sizeof(aco_t)); - memset(p, 0, sizeof(aco_t)); - - if (main_co != NULL) { // non-main co - aco_assertptr(shared_stack); - p->shared_stack = shared_stack; -#ifdef __i386__ - // POSIX.1-2008 (IEEE Std 1003.1-2008) - General Information - Data Types - Pointer Types - // http://pubs.opengroup.org/onlinepubs/9699919799.2008edition/functions/V2_chap02.html#tag_15_12_03 - p->reg[ACO_REG_IDX_RETADDR] = (void *)fp; - // push retaddr - p->reg[ACO_REG_IDX_SP] = p->shared_stack->align_retptr; -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - p->reg[ACO_REG_IDX_FPU] = aco_gtls_fpucw_mxcsr[0]; - p->reg[ACO_REG_IDX_FPU + 1] = aco_gtls_fpucw_mxcsr[1]; -#endif -#elif __x86_64__ - p->reg[ACO_REG_IDX_RETADDR] = (void *)fp; - p->reg[ACO_REG_IDX_SP] = p->shared_stack->align_retptr; -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - p->reg[ACO_REG_IDX_FPU] = aco_gtls_fpucw_mxcsr[0]; -#endif -#else -#error "platform not supporteded yet" -#endif - p->main_co = main_co; - p->arg = arg; - p->fp = fp; - if (saved_stack_sz == 0) { - saved_stack_sz = 64; - } - p->saved_stack.ptr = aco_alloc(saved_stack_sz); - p->saved_stack.sz = saved_stack_sz; -#if defined(__i386__) || defined(__x86_64__) - p->saved_stack.valid_sz = 0; -#else -#error "platform not supporteded yet" -#endif - return p; - } else { // main co - p->main_co = NULL; - p->arg = arg; - p->fp = fp; - p->shared_stack = NULL; - p->saved_stack.ptr = NULL; - return p; - } - aco_assert(0); -} - -public -aco_attr_no_asan void aco_resume(aco_t *resume_co) { - aco_assert(resume_co != NULL && resume_co->main_co != NULL && !resume_co->is_finished); - if (resume_co->shared_stack->owner != resume_co) { - if (resume_co->shared_stack->owner != NULL) { - aco_t *owner_co = resume_co->shared_stack->owner; - aco_assert(owner_co->shared_stack == resume_co->shared_stack); -#if defined(__i386__) || defined(__x86_64__) - aco_assert(((uintptr_t)(owner_co->shared_stack->align_retptr) >= (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])) - && ((uintptr_t)(owner_co->shared_stack->align_highptr) - - (uintptr_t)(owner_co->shared_stack->align_limit) - <= (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]))); - owner_co->saved_stack.valid_sz = - (uintptr_t)(owner_co->shared_stack->align_retptr) - (uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]); - if (owner_co->saved_stack.sz < owner_co->saved_stack.valid_sz) { - if (aco_dealloc_fn != NULL) aco_dealloc_fn(owner_co->saved_stack.ptr); - owner_co->saved_stack.ptr = NULL; - while (1) { - owner_co->saved_stack.sz = owner_co->saved_stack.sz << 1; - aco_assert(owner_co->saved_stack.sz > 0); - if (owner_co->saved_stack.sz >= owner_co->saved_stack.valid_sz) { - break; - } - } - owner_co->saved_stack.ptr = aco_alloc(owner_co->saved_stack.sz); - } - // TODO: optimize the performance penalty of memcpy function call - // for very short memory span - if (owner_co->saved_stack.valid_sz > 0) { -#ifdef __x86_64__ - aco_fast_memcpy(owner_co->saved_stack.ptr, owner_co->reg[ACO_REG_IDX_SP], - owner_co->saved_stack.valid_sz); -#else - memcpy(owner_co->saved_stack.ptr, owner_co->reg[ACO_REG_IDX_SP], owner_co->saved_stack.valid_sz); -#endif - owner_co->saved_stack.ct_save++; - } - if (owner_co->saved_stack.valid_sz > owner_co->saved_stack.max_cpsz) { - owner_co->saved_stack.max_cpsz = owner_co->saved_stack.valid_sz; - } - owner_co->shared_stack->owner = NULL; - owner_co->shared_stack->align_validsz = 0; -#else -#error "platform not supporteded yet" -#endif - } - aco_assert(resume_co->shared_stack->owner == NULL); -#if defined(__i386__) || defined(__x86_64__) - aco_assert(resume_co->saved_stack.valid_sz <= resume_co->shared_stack->align_limit - sizeof(void *)); - // TODO: optimize the performance penalty of memcpy function call - // for very short memory span - if (resume_co->saved_stack.valid_sz > 0) { - void *dst = (void *)((uintptr_t)(resume_co->shared_stack->align_retptr) - resume_co->saved_stack.valid_sz); -#ifdef __x86_64__ - aco_fast_memcpy(dst, resume_co->saved_stack.ptr, resume_co->saved_stack.valid_sz); -#else - memcpy(dst, resume_co->saved_stack.ptr, resume_co->saved_stack.valid_sz); -#endif - resume_co->saved_stack.ct_restore++; - } - if (resume_co->saved_stack.valid_sz > resume_co->saved_stack.max_cpsz) { - resume_co->saved_stack.max_cpsz = resume_co->saved_stack.valid_sz; - } - resume_co->shared_stack->align_validsz = resume_co->saved_stack.valid_sz + sizeof(void *); - resume_co->shared_stack->owner = resume_co; -#else -#error "platform not supporteded yet" -#endif - } - aco_gtls_co = resume_co; - aco_yield_asm(resume_co->main_co, resume_co); - aco_gtls_co = resume_co->main_co; -} - -public -void aco_destroy(aco_t *co) { - aco_assertptr(co); - if (aco_is_main_co(co)) { - if (aco_dealloc_fn != NULL) aco_dealloc_fn(co); - } else { - if (co->shared_stack->owner == co) { - co->shared_stack->owner = NULL; - co->shared_stack->align_validsz = 0; - } - if (aco_dealloc_fn != NULL) aco_dealloc_fn(co->saved_stack.ptr); - co->saved_stack.ptr = NULL; - if (aco_dealloc_fn != NULL) aco_dealloc_fn(co); - } -} - -public -void aco_exit_fn(void *_) { aco_exit(); } diff --git a/examples/coroutines/aco.h b/examples/coroutines/aco.h deleted file mode 100644 index 05ba0bdb..00000000 --- a/examples/coroutines/aco.h +++ /dev/null @@ -1,213 +0,0 @@ -// A coroutine library -// Copyright 2018 Sen Han <00hnes@gmail.com> -// Modifications copyright 2025 Bruce Hill -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef ACO_USE_VALGRIND -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define ACO_VERSION_MAJOR 2 -#define ACO_VERSION_MINOR 0 -#define ACO_VERSION_PATCH 0 - -#ifdef __i386__ -#define ACO_REG_IDX_RETADDR 0 -#define ACO_REG_IDX_SP 1 -#define ACO_REG_IDX_BP 2 -#define ACO_REG_IDX_ARG1 0 -#define ACO_REG_IDX_FPU 6 -#elif __x86_64__ -#define ACO_REG_IDX_RETADDR 4 -#define ACO_REG_IDX_SP 5 -#define ACO_REG_IDX_BP 7 -#define ACO_REG_IDX_EDI 8 -#define ACO_REG_IDX_FPU 8 -#else -#error "platform not supported yet" -#endif - -typedef struct { - void *ptr; - size_t sz; - size_t valid_sz; - // max copy size in bytes - size_t max_cpsz; - // copy from shared stack to this saved stack - size_t ct_save; - // copy from this saved stack to shared stack - size_t ct_restore; -} aco_saved_stack_t; - -struct aco_s; -typedef struct aco_s aco_t; - -typedef struct { - void *ptr; - size_t sz; - void *align_highptr; - void *align_retptr; - size_t align_validsz; - size_t align_limit; - aco_t *owner; - - bool guard_page_enabled; - void *real_ptr; - size_t real_sz; - -#ifdef ACO_USE_VALGRIND - unsigned long valgrind_stk_id; -#endif -} aco_shared_stack_t; - -typedef void (*aco_cofuncp_t)(void *); - -struct aco_s { - // cpu registers' state -#ifdef __i386__ -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - void *reg[6]; -#else - void *reg[8]; -#endif -#elif __x86_64__ -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - void *reg[8]; -#else - void *reg[9]; -#endif -#else -#error "platform not supported yet" -#endif - aco_t *main_co; - void *arg; - bool is_finished; - - aco_cofuncp_t fp; - - aco_saved_stack_t saved_stack; - aco_shared_stack_t *shared_stack; -}; - -#define aco_likely(x) (__builtin_expect(!!(x), 1)) - -#define aco_unlikely(x) (__builtin_expect(!!(x), 0)) - -#define aco_assert(EX) ((aco_likely(EX)) ? ((void)0) : (abort())) - -#define aco_assertptr(ptr) ((aco_likely((ptr) != NULL)) ? ((void)0) : (abort())) - -#if defined(aco_attr_no_asan) -#error "aco_attr_no_asan already defined" -#endif -#if defined(ACO_USE_ASAN) -#if defined(__has_feature) -#if __has_feature(__address_sanitizer__) -#define aco_attr_no_asan __attribute__((__no_sanitize_address__)) -#endif -#endif -#if defined(__SANITIZE_ADDRESS__) && !defined(aco_attr_no_asan) -#define aco_attr_no_asan __attribute__((__no_sanitize_address__)) -#endif -#endif -#ifndef aco_attr_no_asan -#define aco_attr_no_asan -#endif - -void aco_runtime_test(void); - -void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *)); - -void aco_thread_init(aco_cofuncp_t last_word_co_fp); - -void aco_yield_asm(aco_t *from_co, aco_t *to_co) __asm__("aco_yield_asm"); // asm - -void aco_save_fpucw_mxcsr(void *p) __asm__("aco_save_fpucw_mxcsr"); // asm - -void aco_funcp_protector_asm(void) __asm__("aco_funcp_protector_asm"); // asm - -void aco_funcp_protector(void); - -aco_shared_stack_t *aco_shared_stack_new(size_t sz); - -aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled); - -void aco_shared_stack_destroy(aco_shared_stack_t *sstk); - -aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp, void *arg); - -// aco's Global Thread Local Storage variable `co` -#ifdef __TINYC__ -#error "TinyCC doesn't support thread-local storage!" -#else -extern __thread aco_t *aco_gtls_co; -#endif - -aco_attr_no_asan void aco_resume(aco_t *resume_co); - -// void aco_yield1(aco_t* yield_co); -#define aco_yield1(yield_co) \ - do { \ - aco_assertptr((yield_co)); \ - aco_assertptr((yield_co)->main_co); \ - aco_yield_asm((yield_co), (yield_co)->main_co); \ - } while (0) - -#define aco_yield() aco_yield1(aco_gtls_co) - -#define aco_get_arg() (aco_gtls_co->arg) - -#define aco_get_co() \ - ({ \ - (void)0; \ - aco_gtls_co; \ - }) - -void aco_destroy(aco_t *co); - -#define aco_is_main_co(co) ({ ((co)->main_co) == NULL; }) - -#define aco_exit1(co) \ - do { \ - (co)->is_finished = true; \ - aco_assert((co)->shared_stack->owner == (co)); \ - (co)->shared_stack->owner = NULL; \ - (co)->shared_stack->align_validsz = 0; \ - aco_yield1((co)); \ - aco_assert(0); \ - } while (0) - -#define aco_exit() aco_exit1(aco_gtls_co) - -void aco_exit_fn(void *); - -#ifdef __cplusplus -} -#endif diff --git a/examples/coroutines/acoyield.S b/examples/coroutines/acoyield.S deleted file mode 100644 index 7bc87ff1..00000000 --- a/examples/coroutines/acoyield.S +++ /dev/null @@ -1,208 +0,0 @@ -.text -.globl aco_yield_asm -#if defined(__APPLE__) -#else -.type aco_yield_asm, @function -#endif -.intel_syntax noprefix -aco_yield_asm: -/* - extern void aco_yield_asm(aco_t* from_co, aco_t* to_co); - - struct aco_t { - void* reg[X]; - // ... - } - - reference: - https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI - - pitfall: - http://man7.org/linux/man-pages/man7/signal.7.html - http://man7.org/linux/man-pages/man2/sigaltstack.2.html - - > $ man 7 signal - > ... - > By default, the signal handler is invoked on the normal process - > stack. It is possible to arrange that the signal handler - > uses an alternate stack; see sigaltstack(2) for a discussion of - > how to do this and when it might be useful. - > ... - - This is a BUG example: - https://github.com/Tencent/libco/blob/v1.0/coctx_swap.S#L27 - - proof of correctness: - https://github.com/hnes/libaco - - mxcsr & fpu: - fnstcw * m2byte - Store FPU control word to m2byte without checking for - pending unmasked floating-point exceptions. - - fldcw m2byte - Load FPU control word from m2byte. - - stmxcsr m32 - Store contents of MXCSR register to m32 - - ldmxcsr m32 - Load MXCSR register from m32. -*/ -/* - 0x00 --> 0xff - eip esp ebp edi esi ebx fpucw16 mxcsr32 - 0 4 8 c 10 14 18 1c -*/ -#ifdef __i386__ - mov eax,DWORD PTR [esp+0x4] // from_co - mov edx,DWORD PTR [esp] // retaddr - lea ecx,[esp+0x4] // esp - mov DWORD PTR [eax+0x8],ebp //esp - mov ebp,DWORD PTR [ecx+0x8] //>ebp - mov eax,DWORD PTR [ecx+0x0] //>retaddr - mov edi,DWORD PTR [ecx+0xc] //>edi - mov esi,DWORD PTR [ecx+0x10] //>esi - mov ebx,DWORD PTR [ecx+0x14] //>ebx -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fldcw WORD PTR [ecx+0x18] //>fpucw - ldmxcsr DWORD PTR [ecx+0x1c] //>mxcsr -#endif - xor ecx,ecx - mov esp,edx - mov edx,eax - - // Pass the user-provided argument as first argument: -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - mov eax,DWORD PTR [ecx+0x24] -#else - mov eax,DWORD PTR [ecx+0x28] -#endif - - jmp edx -#elif __x86_64__ -/* - 0x00 --> 0xff - r12 r13 r14 r15 rip rsp rbx rbp fpucw16 mxcsr32 - 0 8 10 18 20 28 30 38 40 44 -*/ - // rdi - from_co | rsi - to_co - mov rdx,QWORD PTR [rsp] // retaddr - lea rcx,[rsp+0x8] // rsp - mov QWORD PTR [rdi+0x0], r12 - mov QWORD PTR [rdi+0x8], r13 - mov QWORD PTR [rdi+0x10],r14 - mov QWORD PTR [rdi+0x18],r15 - mov QWORD PTR [rdi+0x20],rdx // retaddr - mov QWORD PTR [rdi+0x28],rcx // rsp - mov QWORD PTR [rdi+0x30],rbx - mov QWORD PTR [rdi+0x38],rbp -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fnstcw WORD PTR [rdi+0x40] - stmxcsr DWORD PTR [rdi+0x44] -#endif - mov r12,QWORD PTR [rsi+0x0] - mov r13,QWORD PTR [rsi+0x8] - mov r14,QWORD PTR [rsi+0x10] - mov r15,QWORD PTR [rsi+0x18] - mov rax,QWORD PTR [rsi+0x20] // retaddr - mov rcx,QWORD PTR [rsi+0x28] // rsp - mov rbx,QWORD PTR [rsi+0x30] - mov rbp,QWORD PTR [rsi+0x38] -#ifndef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - fldcw WORD PTR [rsi+0x40] - ldmxcsr DWORD PTR [rsi+0x44] -#endif - - // Pass the user-provided argument as first argument: -#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV - mov rdi,QWORD PTR [rsi+0x48] -#else - mov rdi,QWORD PTR [rsi+0x50] -#endif - - mov rsp,rcx - jmp rax -#else - #error "platform not supported" -#endif - -.globl aco_save_fpucw_mxcsr -#if defined(__APPLE__) -#else -.type aco_save_fpucw_mxcsr, @function -#endif -.intel_syntax noprefix -aco_save_fpucw_mxcsr: -#ifdef __i386__ - mov eax,DWORD PTR [esp+0x4] // ptr - fnstcw WORD PTR [eax] - stmxcsr DWORD PTR [eax+0x4] - ret -#elif __x86_64__ - fnstcw WORD PTR [rdi] - stmxcsr DWORD PTR [rdi+0x4] - ret -#else - #error "platform not supported" -#endif - -#if defined(__APPLE__) -.globl _abort -.globl _aco_funcp_protector -#else -.globl abort -.globl aco_funcp_protector -#endif - -.globl aco_funcp_protector_asm -#if defined(__APPLE__) -#else -.type aco_funcp_protector_asm, @function -#endif -.intel_syntax noprefix -aco_funcp_protector_asm: -#ifdef __i386__ - and esp,0xfffffff0 - #if defined(__APPLE__) - call _aco_funcp_protector - call _abort - #else - #if defined(__pic__) || defined(__PIC__) - call aco_funcp_protector@PLT - call abort@PLT - #else - call aco_funcp_protector - call abort - #endif - #endif - ret -#elif __x86_64__ - and rsp,0xfffffffffffffff0 - #if defined(__APPLE__) - call _aco_funcp_protector - call _abort - #else - #if defined(__pic__) || defined(__PIC__) - call aco_funcp_protector@PLT - call abort@PLT - #else - call aco_funcp_protector - call abort - #endif - #endif - ret -#else - #error "platform not supported" -#endif diff --git a/examples/coroutines/coroutines.tm b/examples/coroutines/coroutines.tm deleted file mode 100644 index b530a685..00000000 --- a/examples/coroutines/coroutines.tm +++ /dev/null @@ -1,67 +0,0 @@ -# This is a coroutine library that uses libaco (https://libaco.org) -# -# Lua programmers will recognize this as similar to Lua's stackful coroutines. -# -# Async/Await programmers will weep at its beauty and gnash their teeth and -# rend their garments in despair at what they could have had. - -use ./aco.h -use ./aco.c -use ./acoyield.S - -func main() - say("Example usage") - co := Coroutine(func() - say("I'm in the coroutine!") - yield() - say("I'm back in the coroutine!") - ) - >> co - say("I'm in the main func") - >> co.resume() - say("I'm back in the main func") - >> co.resume() - say("I'm back in the main func again") - >> co.resume() - -struct aco_t(; extern, opaque) -struct aco_shared_stack_t(; extern, opaque) - -_main_co : @aco_t? = none -_shared_stack : @aco_shared_stack_t? = none - -struct Coroutine(co:@aco_t) - convert(fn:func() -> Coroutine) - if not _main_co - _init() - - main_co := _main_co - shared_stack := _shared_stack - aco_ptr := C_code:@aco_t( - aco_create(@main_co, @shared_stack, 0, (void*)@fn.fn, @fn.userdata) - ) - return Coroutine(aco_ptr) - - func is_finished(co:Coroutine->Bool; inline) - return C_code:Bool(((aco_t*)@co.co)->is_finished) - - func resume(co:Coroutine->Bool) - if co.is_finished() - return no - C_code { aco_resume(@co.co); } - return yes - -func _init() - C_code { - aco_set_allocator(GC_malloc, NULL); - aco_thread_init(aco_exit_fn); - } - _main_co = C_code:@aco_t(aco_create(NULL, NULL, 0, NULL, NULL)) - - _shared_stack = C_code:@aco_shared_stack_t(aco_shared_stack_new(0)) - -func yield(; inline) - C_code { - aco_yield(); - } - diff --git a/examples/game/CHANGES.md b/examples/game/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/game/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/game/Makefile b/examples/game/Makefile deleted file mode 100644 index 7cf46ce6..00000000 --- a/examples/game/Makefile +++ /dev/null @@ -1,17 +0,0 @@ - - -game: game.tm box.tm color.tm player.tm world.tm - tomo -e game.tm - -# Disable built-in makefile rules: -%: %.c -%.o: %.c -%: %.o - -clean: - rm -vf game *.tm.* - -play: game - ./game - -.PHONY: play, clean diff --git a/examples/game/README.md b/examples/game/README.md deleted file mode 100644 index 475a8299..00000000 --- a/examples/game/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Example Game - -This is a simple example game that uses [raylib](https://www.raylib.com/) to -demonstrate a project that spans multiple files and showcases some game -programming concepts used in idiomatic Tomo code. It also showcases how to -interact with an external C library. To run the game: - -```bash -tomo game.tm -``` - -An example [Makefile](Makefile) is also provided if you want to use `make` to -build the game and `make clean` to clean up the built files. diff --git a/examples/game/box.tm b/examples/game/box.tm deleted file mode 100644 index 41ae10e5..00000000 --- a/examples/game/box.tm +++ /dev/null @@ -1,7 +0,0 @@ -# Defines a struct representing boxes on the terrain -use ./world.tm -use ./raylib.tm - -struct Box(pos:Vector2, size=Vector2(50, 50), color=Color(0x80,0x80,0x80)) - func draw(b:Box) - DrawRectangleV(b.pos, b.size, b.color) diff --git a/examples/game/game.tm b/examples/game/game.tm deleted file mode 100644 index f82e4f40..00000000 --- a/examples/game/game.tm +++ /dev/null @@ -1,29 +0,0 @@ -# This game demo uses Raylib to present a simple maze-type game -use ./raylib.tm -use ./world.tm - -func main(map=(./map.txt)) - InitWindow(1600, 900, CString("raylib [core] example - 2d camera")) - - map_contents := map.read() or exit("Could not find the game map: $map") - - world := @World( - player=@Player(Vector2(0,0), Vector2(0,0)), - goal=@Box(Vector2(0,0), Vector2(50,50), color=Color(0x10,0xa0,0x10)), - boxes=@[], - ) - world.load_map(map_contents) - - SetTargetFPS(60) - - while not WindowShouldClose() - dt := GetFrameTime() - world.update(dt) - - BeginDrawing() - ClearBackground(Color(0xCC, 0xCC, 0xCC, 0xFF)) - world.draw() - EndDrawing() - - CloseWindow() - diff --git a/examples/game/map.txt b/examples/game/map.txt deleted file mode 100644 index 46fccd09..00000000 --- a/examples/game/map.txt +++ /dev/null @@ -1,18 +0,0 @@ -################################ -#@ # # # -# #### #### # # #### ##### -# # # # # # -# ####### # ####### # # ### -# # # # # # # -# #### ########## # #### # -# # # # # # # -#### # # # # # # # # ## -# # # # # # # # -####### ########## # ######## -# # # # # -# ########## #### # # # # -# # # # # # # # # -# ####### # # ########## # -# # # # # # # ? # -# # # # # # # -################################ diff --git a/examples/game/player.tm b/examples/game/player.tm deleted file mode 100644 index 2e5e54f6..00000000 --- a/examples/game/player.tm +++ /dev/null @@ -1,28 +0,0 @@ -# Defines a struct representing the player, which is controlled by WASD keys -use ./world.tm -use ./raylib.tm - -struct Player(pos,prev_pos:Vector2) - WALK_SPEED := Num32(500.) - ACCEL := Num32(0.3) - FRICTION := Num32(0.99) - SIZE := Vector2(30, 30) - COLOR := Color(0x60, 0x60, 0xbF) - - func update(p:@Player) - target_x := C_code:Num32( - (Num32_t)((IsKeyDown(KEY_A) ? -1 : 0) + (IsKeyDown(KEY_D) ? 1 : 0)) - ) - target_y := C_code:Num32( - (Num32_t)((IsKeyDown(KEY_W) ? -1 : 0) + (IsKeyDown(KEY_S) ? 1 : 0)) - ) - target_vel := Vector2(target_x, target_y).norm() * Player.WALK_SPEED - - vel := (p.pos - p.prev_pos)/World.DT - vel *= Player.FRICTION - vel = vel.mix(target_vel, Player.ACCEL) - - p.prev_pos, p.pos = p.pos, p.pos + World.DT*vel - - func draw(p:Player) - DrawRectangleV(p.pos, Player.SIZE, Player.COLOR) diff --git a/examples/game/raylib.tm b/examples/game/raylib.tm deleted file mode 100644 index b2ba53d7..00000000 --- a/examples/game/raylib.tm +++ /dev/null @@ -1,63 +0,0 @@ -# Raylib wrapper for some functions and structs -use -lraylib -use -use - -struct Color(r,g,b:Byte,a=Byte(255); extern) -struct Rectangle(x,y,width,height:Num32; extern) - func draw(r:Rectangle, color:Color) - DrawRectangleRec(r, color) - -struct Vector2(x,y:Num32; extern) - ZERO := Vector2(0, 0) - func plus(a,b:Vector2->Vector2; inline) - return Vector2(a.x+b.x, a.y+b.y) - func minus(a,b:Vector2->Vector2; inline) - return Vector2(a.x-b.x, a.y-b.y) - func times(a,b:Vector2->Vector2; inline) - return Vector2(a.x*b.x, a.y*b.y) - func negative(v:Vector2->Vector2; inline) - return Vector2(-v.x, -v.y) - func dot(a,b:Vector2->Num32; inline) - return ((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)) - func cross(a,b:Vector2->Num32; inline) - return a.x*b.y - a.y*b.x - func scaled_by(v:Vector2, k:Num32->Vector2; inline) - return Vector2(v.x*k, v.y*k) - func divided_by(v:Vector2, divisor:Num32->Vector2; inline) - return Vector2(v.x/divisor, v.y/divisor) - func length(v:Vector2->Num32; inline) - return (v.x*v.x + v.y*v.y).sqrt() - func dist(a,b:Vector2->Num32; inline) - return a.minus(b).length() - func angle(v:Vector2->Num32; inline) - return Num32.atan2(v.y, v.x) - func norm(v:Vector2->Vector2; inline) - if v.x == 0 and v.y == 0 - return v - len := v.length() - return Vector2(v.x/len, v.y/len) - func rotated(v:Vector2, radians:Num32 -> Vector2) - cos := radians.cos() or return v - sin := radians.sin() or return v - return Vector2(cos*v.x - sin*v.y, sin*v.x + cos*v.y) - func mix(a,b:Vector2, amount:Num32 -> Vector2) - return Vector2( - amount.mix(a.x, b.x), - amount.mix(a.y, b.y), - ) - -extern InitWindow : func(width:Int32, height:Int32, title:CString) -extern SetTargetFPS : func(fps:Int32) -extern WindowShouldClose : func(->Bool) -extern GetFrameTime : func(->Num32) -extern BeginDrawing : func() -extern EndDrawing : func() -extern CloseWindow : func() -extern ClearBackground : func(color:Color) -extern DrawRectangle : func(x,y,width,height:Int32, color:Color) -extern DrawRectangleRec : func(rec:Rectangle, color:Color) -extern DrawRectangleV : func(pos:Vector2, size:Vector2, color:Color) -extern DrawText : func(text:CString, x,y:Int32, text_height:Int32, color:Color) -extern GetScreenWidth : func(->Int32) -extern GetScreenHeight : func(->Int32) diff --git a/examples/game/world.tm b/examples/game/world.tm deleted file mode 100644 index 0de8ea4b..00000000 --- a/examples/game/world.tm +++ /dev/null @@ -1,88 +0,0 @@ -# This file defines a World object for keeping track of everything, as well -# as the collision logic. -use ./player.tm -use ./raylib.tm -use ./box.tm - -# Return a displacement relative to `a` that will push it out of `b` -func solve_overlap(a_pos:Vector2, a_size:Vector2, b_pos:Vector2, b_size:Vector2 -> Vector2) - a_left := a_pos.x - a_right := a_pos.x + a_size.x - a_top := a_pos.y - a_bottom := a_pos.y + a_size.y - - b_left := b_pos.x - b_right := b_pos.x + b_size.x - b_top := b_pos.y - b_bottom := b_pos.y + b_size.y - - # Calculate the overlap in each dimension - overlap_x := (a_right _min_ b_right) - (a_left _max_ b_left) - overlap_y := (a_bottom _min_ b_bottom) - (a_top _max_ b_top) - - # If either axis is not overlapping, then there is no collision: - if overlap_x <= 0 or overlap_y <= 0 - return Vector2(0, 0) - - if overlap_x < overlap_y - if a_right > b_left and a_right < b_right - return Vector2(-(overlap_x), 0) - else if a_left < b_right and a_left > b_left - return Vector2(overlap_x, 0) - else - if a_top < b_bottom and a_top > b_top - return Vector2(0, overlap_y) - else if a_bottom > b_top and a_bottom < b_bottom - return Vector2(0, -overlap_y) - - return Vector2(0, 0) - -struct World(player:@Player, goal:@Box, boxes:@[@Box], dt_accum=Num32(0.0), won=no) - DT := (Num32(1.)/Num32(60.)) - STIFFNESS := Num32(0.3) - - func update(w:@World, dt:Num32) - w.dt_accum += dt - while w.dt_accum > 0 - w.update_once() - w.dt_accum -= World.DT - - func update_once(w:@World) - w.player.update() - - if solve_overlap(w.player.pos, Player.SIZE, w.goal.pos, w.goal.size) != Vector2(0,0) - w.won = yes - - # Resolve player overlapping with any boxes: - for i in 3 - for b in w.boxes - w.player.pos += World.STIFFNESS * solve_overlap(w.player.pos, Player.SIZE, b.pos, b.size) - - func draw(w:@World) - for b in w.boxes - b.draw() - w.goal.draw() - w.player.draw() - - if w.won - DrawText(CString("WINNER"), GetScreenWidth()/Int32(2)-Int32(48*3), GetScreenHeight()/Int32(2)-Int32(24), 48, Color(0,0,0)) - - func load_map(w:@World, map:Text) - if map.has("[]") - map = map.translate({"[]"="#", "@ "="@", " "=" "}) - w.boxes = @[] - box_size := Vector2(50., 50.) - for y,line in map.lines() - for x,cell in line.split() - if cell == "#" - pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y) - box := @Box(pos, size=box_size, color=Color(0x80,0x80,0x80)) - w.boxes.insert(box) - else if cell == "@" - pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y) - pos += box_size/Num32(2) - Player.SIZE/Num32(2) - w.player = @Player(pos,pos) - else if cell == "?" - pos := Vector2((Num32(x)-1) * box_size.x, (Num32(y)-1) * box_size.y) - w.goal.pos = pos - diff --git a/examples/hello.tm b/examples/hello.tm new file mode 100644 index 00000000..09ee3ab4 --- /dev/null +++ b/examples/hello.tm @@ -0,0 +1,3 @@ +# A simple hello world program: +func main() + say("Hello world!") diff --git a/examples/http-server/CHANGES.md b/examples/http-server/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/http-server/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/http-server/README.md b/examples/http-server/README.md deleted file mode 100644 index 78c8d793..00000000 --- a/examples/http-server/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# HTTP Server - -This is a simple multithreaded Tomo HTTP server that can be run like this: - -``` -tomo -e http-server.tm -./http-server ./sample-site -``` diff --git a/examples/http-server/connection-queue.tm b/examples/http-server/connection-queue.tm deleted file mode 100644 index c56069e1..00000000 --- a/examples/http-server/connection-queue.tm +++ /dev/null @@ -1,25 +0,0 @@ -use pthreads - -func _assert_success(name:Text, val:Int32; inline) - fail("$name() failed!") if val < 0 - -struct ConnectionQueue(_connections:@[Int32]=@[], _mutex=pthread_mutex_t.new(), _cond=pthread_cond_t.new()) - func enqueue(queue:ConnectionQueue, connection:Int32) - queue._mutex.lock() - queue._connections.insert(connection) - queue._mutex.unlock() - queue._cond.signal() - - - func dequeue(queue:ConnectionQueue -> Int32) - conn : Int32? - - queue._mutex.lock() - - while queue._connections.length == 0 - queue._cond.wait(queue._mutex) - - conn = queue._connections.pop(1) - queue._mutex.unlock() - queue._cond.signal() - return conn! diff --git a/examples/http-server/http-server.tm b/examples/http-server/http-server.tm deleted file mode 100644 index 8e8aff7e..00000000 --- a/examples/http-server/http-server.tm +++ /dev/null @@ -1,149 +0,0 @@ -#!/bin/env tomo - -# This file provides an HTTP server module and standalone executable - -use -use -use -use -use -use - -use commands -use pthreads -use patterns - -use ./connection-queue.tm - -func serve(port:Int32, handler:func(request:HTTPRequest -> HTTPResponse), num_threads=16) - connections := ConnectionQueue() - workers : &[@pthread_t] - for i in num_threads - workers.insert(pthread_t.new(func() - repeat - connection := connections.dequeue() - request_text := C_code:Text( - Text_t request = EMPTY_TEXT; - char buf[1024] = {}; - for (ssize_t n; (n = read(@connection, buf, sizeof(buf) - 1)) > 0; ) { - buf[n] = 0; - request = Text$concat(request, Text$from_strn(buf, n)); - if (request.length > 1000000 || strstr(buf, "\r\n\r\n")) - break; - } - request - ) - - request := HTTPRequest.from_text(request_text) or skip - response := handler(request).bytes() - C_code { - if (@response.stride != 1) - List$compact(&@response, 1); - write(@connection, @response.data, @response.length); - close(@connection); - } - )) - - - sock := C_code:Int32( - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) err(1, "Couldn't connect to socket!"); - - int opt = 1; - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) - err(1, "Couldn't set socket option"); - - struct sockaddr_in addr = {AF_INET, htons(@port), INADDR_ANY}; - if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) - err(1, "Couldn't bind to socket"); - if (listen(s, 8) < 0) - err(1, "Couldn't listen on socket"); - - s - ) - - repeat - conn := C_code:Int32(accept(@sock, NULL, NULL)) - stop if conn < 0 - connections.enqueue(conn) - - say("Shutting down...") - for w in workers - w.cancel() - -struct HTTPRequest(method:Text, path:Text, version:Text, headers:[Text], body:Text) - func from_text(text:Text -> HTTPRequest?) - m := text.pattern_captures($Pat'{word} {..} HTTP/{..}{crlf}{..}') or return none - method := m[1] - path := m[2].replace_pattern($Pat'{2+ /}', '/') - version := m[3] - rest := m[-1].pattern_captures($Pat/{..}{2 crlf}{0+ ..}/) or return none - headers := rest[1].split_pattern($Pat/{crlf}/) - body := rest[-1] - return HTTPRequest(method, path, version, headers, body) - -struct HTTPResponse(body:Text, status=200, content_type="text/plain", headers:{Text=Text}={}) - func bytes(r:HTTPResponse -> [Byte]) - body_bytes := r.body.bytes() - extra_headers := (++: "$k: $v\r\n" for k,v in r.headers) or "" - return " - HTTP/1.1 $(r.status) OK\r - Content-Length: $(body_bytes.length + 2)\r - Content-Type: $(r.content_type)\r - Connection: close\r - $extra_headers - \r\n - ".bytes() ++ body_bytes - -func _content_type(file:Path -> Text) - when file.extension() is "html" return "text/html" - is "tm" return "text/html" - is "js" return "text/javascript" - is "css" return "text/css" - else return "text/plain" - -enum RouteEntry(ServeFile(file:Path), Redirect(destination:Text)) - func respond(entry:RouteEntry, request:HTTPRequest -> HTTPResponse) - when entry is ServeFile(file) - body := if file.can_execute() - Command(Text(file)).get_output()! - else - file.read()! - return HTTPResponse(body, content_type=_content_type(file)) - is Redirect(destination) - return HTTPResponse("Found", 302, headers={"Location"=destination}) - -func load_routes(directory:Path -> {Text=RouteEntry}) - routes : &{Text=RouteEntry} - for file in (directory ++ (./*)).glob() - skip unless file.is_file() - contents := file.read() or skip - server_path := "/" ++ "/".join(file.relative_to(directory).components) - if file.base_name() == "index.html" - canonical := server_path.without_suffix("index.html") - routes[server_path] = Redirect(canonical) - routes[canonical] = ServeFile(file) - else if file.extension() == "html" - canonical := server_path.without_suffix(".html") - routes[server_path] = Redirect(canonical) - routes[canonical] = ServeFile(file) - else if file.extension() == "tm" - canonical := server_path.without_suffix(".tm") - routes[server_path] = Redirect(canonical) - routes[canonical] = ServeFile(file) - else - routes[server_path] = ServeFile(file) - return routes[] - -func main(directory:Path, port=Int32(8080)) - say("Serving on port $port") - routes := load_routes(directory) - say("Hosting: $routes") - - serve(port, func(request:HTTPRequest) - if handler := routes[request.path] - return handler.respond(request) - else - return HTTPResponse("Not found!", 404) - ) - diff --git a/examples/http-server/modules.ini b/examples/http-server/modules.ini deleted file mode 100644 index 171e11d2..00000000 --- a/examples/http-server/modules.ini +++ /dev/null @@ -1,8 +0,0 @@ -[pthreads] -version=v1.0 - -[patterns] -version=v1.0 - -[commands] -version=v1.0 diff --git a/examples/http-server/sample-site/foo.html b/examples/http-server/sample-site/foo.html deleted file mode 100644 index 162a7146..00000000 --- a/examples/http-server/sample-site/foo.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - This is the foo page. - - diff --git a/examples/http-server/sample-site/hello.txt b/examples/http-server/sample-site/hello.txt deleted file mode 100644 index e965047a..00000000 --- a/examples/http-server/sample-site/hello.txt +++ /dev/null @@ -1 +0,0 @@ -Hello diff --git a/examples/http-server/sample-site/index.html b/examples/http-server/sample-site/index.html deleted file mode 100644 index 8e1573bb..00000000 --- a/examples/http-server/sample-site/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - HTTP Example - - - -

- Hello world! -

- -

- Try going to /random or /foo or /hello.txt -

- - diff --git a/examples/http-server/sample-site/random.tm b/examples/http-server/sample-site/random.tm deleted file mode 100755 index 153ac2af..00000000 --- a/examples/http-server/sample-site/random.tm +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/env tomo -use random - -func main() - say(" - - - - Random Number - - - -

Random Number

- Your random number is: $(random.int(1,100)) - - - ") diff --git a/examples/http-server/sample-site/styles.css b/examples/http-server/sample-site/styles.css deleted file mode 100644 index f15d25de..00000000 --- a/examples/http-server/sample-site/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -body{ - margin:40px auto; - max-width:650px; - line-height:1.6; - font-size:18px; - color:#444; - padding:0 10px; -} -h1,h2,h3{ - line-height:1.2 -} diff --git a/examples/http/CHANGES.md b/examples/http/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/http/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/http/http.tm b/examples/http/http.tm deleted file mode 100644 index 3fe41ae2..00000000 --- a/examples/http/http.tm +++ /dev/null @@ -1,111 +0,0 @@ -# A simple HTTP library built using CURL - -use -lcurl -use - -struct HTTPResponse(code:Int, body:Text) - -enum _Method(GET, POST, PUT, PATCH, DELETE) - -func _send(method:_Method, url:Text, data:Text?, headers:[Text]=[] -> HTTPResponse) - chunks : @[Text] - save_chunk := func(chunk:CString, size:Int64, n:Int64) - chunks.insert(C_code:Text(Text$from_strn(@chunk, @size*@n))) - return n*size - - C_code { - CURL *curl = curl_easy_init(); - struct curl_slist *chunk = NULL; - curl_easy_setopt(curl, CURLOPT_URL, @(CString(url))); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, @save_chunk.fn); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, @save_chunk.userdata); - } - - defer - C_code { - if (chunk) - curl_slist_free_all(chunk); - curl_easy_cleanup(curl); - } - - when method is POST - C_code { - curl_easy_setopt(curl, CURLOPT_POST, 1L); - } - if posting := data - C_code { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(posting))); - } - is PUT - C_code { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); - } - if putting := data - C_code { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(putting))); - } - is PATCH - C_code { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); - } - if patching := data - C_code { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, @(CString(patching))); - } - is DELETE - C_code { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); - } - else - pass - - for header in headers - C_code { - chunk = curl_slist_append(chunk, @(CString(header))); - } - - C_code { - if (chunk) - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); - } - - code := Int64(0) - C_code { - CURLcode res = curl_easy_perform(curl); - if (res != CURLE_OK) - fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); - - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &@code); - } - return HTTPResponse(Int(code), "".join(chunks)) - -func get(url:Text, headers:[Text]=[] -> HTTPResponse) - return _send(GET, url, none, headers) - -func post(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse) - return _send(POST, url, data, headers) - -func put(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse) - return _send(PUT, url, data, headers) - -func patch(url:Text, data="", headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse) - return _send(PATCH, url, data, headers) - -func delete(url:Text, data:Text?=none, headers=["Content-Type: application/json", "Accept: application/json"] -> HTTPResponse) - return _send(DELETE, url, data, headers) - -func main() - say("GET:") - say(get("https://httpbin.org/get").body) - say("Waiting 1sec...") - sleep(1) - say("POST:") - say(post("https://httpbin.org/post", `{"key": "value"}`).body) - say("Waiting 1sec...") - sleep(1) - say("PUT:") - say(put("https://httpbin.org/put", `{"key": "value"}`).body) - say("Waiting 1sec...") - sleep(1) - say("DELETE:") - say(delete("https://httpbin.org/delete").body) diff --git a/examples/ini/CHANGES.md b/examples/ini/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/ini/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/ini/ini.tm b/examples/ini/ini.tm deleted file mode 100644 index 4dc27725..00000000 --- a/examples/ini/ini.tm +++ /dev/null @@ -1,61 +0,0 @@ - -use patterns - -_USAGE := " - Usage: ini "[section[/key]]" -" -_HELP := " - ini: A .ini config file reader tool. - $_USAGE -" - -func parse_ini(path:Path -> {Text={Text=Text}}) - text := path.read() or exit("Could not read INI file: \[31;1]$(path)\[]") - sections : @{Text=@{Text=Text}} - current_section : @{Text=Text} - - # Line wraps: - text = text.replace_pattern($Pat/\\{1 nl}{0+space}/, " ") - - for line in text.lines() - line = line.trim() - skip if line.starts_with(";") or line.starts_with("#") - if line.matches_pattern($Pat/[?]/) - section_name := line.replace($Pat/[?]/, "@1").trim().lower() - current_section = @{} - sections[section_name] = current_section - else if line.matches_pattern($Pat/{..}={..}/) - key := line.replace_pattern($Pat/{..}={..}/, "@1").trim().lower() - value := line.replace_pattern($Pat/{..}={..}/, "@2").trim() - current_section[key] = value - - return {k=v[] for k,v in sections[]} - -func main(path:Path, key:Text?) - keys := (key or "").split($|/|) - if keys.length > 2 - exit(" - Too many arguments! - $_USAGE - ") - - data := parse_ini(path) - if keys.length < 1 or keys[1] == '*' - say("$data") - return - - section := keys[1].lower() - section_data := data[section] or exit(" - Invalid section name: \[31;1]$section\[] - Valid names: \[1]$(", ".join([k.quoted() for k in data.keys]))\[] - ") - if keys.length < 2 or keys[2] == '*' - say("$section_data") - return - - section_key := keys[2].lower() - value := section_data[section_key] or exit(" - Invalid key: \[31;1]$section_key\[] - Valid keys: \[1]$(", ".join([s.quoted() for s in section_data.keys]))\[] - ") - say(value) diff --git a/examples/ini/modules.ini b/examples/ini/modules.ini deleted file mode 100644 index fb52a859..00000000 --- a/examples/ini/modules.ini +++ /dev/null @@ -1,2 +0,0 @@ -[patterns] -version=v1.0 diff --git a/examples/ini/test.ini b/examples/ini/test.ini deleted file mode 100644 index 782dc76f..00000000 --- a/examples/ini/test.ini +++ /dev/null @@ -1,8 +0,0 @@ -[ Book ] -title = Dirk Gently's Holistic Detective Agency -author = Douglas Adams -published = 1987 - -[ Protagonist ] -name = Dirk Gently -age = 42 diff --git a/examples/log/CHANGES.md b/examples/log/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/log/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/log/log.tm b/examples/log/log.tm deleted file mode 100644 index 4b7893fd..00000000 --- a/examples/log/log.tm +++ /dev/null @@ -1,50 +0,0 @@ -use -use - -timestamp_format := CString("%F %T") - -logfiles : @|Path| - -func _timestamp(->Text) - c_str := C_code:CString( - char *str = GC_MALLOC_ATOMIC(20); - time_t t; time(&t); - struct tm *tm_info = localtime(&t); - strftime(str, 20, "%F %T", tm_info); - str - ) - return c_str.as_text() - -func info(text:Text, newline=yes) - say("\[2]⚫ $text\[]", newline) - for file in logfiles - file.append("$(_timestamp()) [info] $text\n") - -func debug(text:Text, newline=yes) - say("\[32]🟢 $text\[]", newline) - for file in logfiles - file.append("$(_timestamp()) [debug] $text\n") - -func warn(text:Text, newline=yes) - say("\[33;1]🟡 $text\[]", newline) - for file in logfiles - file.append("$(_timestamp()) [warn] $text\n") - -func error(text:Text, newline=yes) - say("\[31;1]🔴 $text\[]", newline) - for file in logfiles - file.append("$(_timestamp()) [error] $text\n") - -func add_logfile(file:Path) - logfiles.add(file) - -func remove_logfile(file:Path) - logfiles.remove(file) - -func main() - add_logfile((./log.txt)) - >> info("Hello") - >> debug("Hello") - >> warn("Hello") - >> error("Hello") - diff --git a/examples/vectors/CHANGES.md b/examples/vectors/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/vectors/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/vectors/vectors.tm b/examples/vectors/vectors.tm deleted file mode 100644 index 1fd0c4fa..00000000 --- a/examples/vectors/vectors.tm +++ /dev/null @@ -1,136 +0,0 @@ -# A math vector library for 2D and 3D vectors of Nums or Ints - -struct Vec2(x,y:Num) - ZERO := Vec2(0, 0) - func plus(a,b:Vec2->Vec2; inline) - return Vec2(a.x+b.x, a.y+b.y) - func minus(a,b:Vec2->Vec2; inline) - return Vec2(a.x-b.x, a.y-b.y) - func times(a,b:Vec2->Vec2; inline) - return Vec2(a.x*b.x, a.y*b.y) - func negative(v:Vec2->Vec2; inline) - return Vec2(-v.x, -v.y) - func dot(a,b:Vec2->Num; inline) - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) - func cross(a,b:Vec2->Num; inline) - return a.x*b.y - a.y*b.x - func scaled_by(v:Vec2, k:Num->Vec2; inline) - return Vec2(v.x*k, v.y*k) - func divided_by(v:Vec2, divisor:Num->Vec2; inline) - return Vec2(v.x/divisor, v.y/divisor) - func length(v:Vec2->Num; inline) - return (v.x*v.x + v.y*v.y).sqrt() - func dist(a,b:Vec2->Num; inline) - return a.minus(b).length() - func angle(v:Vec2->Num; inline) - return Num.atan2(v.y, v.x) - func norm(v:Vec2->Vec2; inline) - if v.x == 0 and v.y == 0 - return v - len := v.length() - return Vec2(v.x/len, v.y/len) - func rotated(v:Vec2, radians:Num -> Vec2) - cos := radians.cos() or return v - sin := radians.sin() or return v - return Vec2(cos*v.x - sin*v.y, sin*v.x + cos*v.y) - func mix(a,b:Vec2, amount:Num -> Vec2) - return Vec2( - amount.mix(a.x, b.x), - amount.mix(a.y, b.y), - ) - -struct Vec3(x,y,z:Num) - ZERO := Vec3(0, 0, 0) - func plus(a,b:Vec3->Vec3; inline) - return Vec3(a.x+b.x, a.y+b.y, a.z+b.z) - func minus(a,b:Vec3->Vec3; inline) - return Vec3(a.x-b.x, a.y-b.y, a.z-b.z) - func times(a,b:Vec3->Vec3; inline) - return Vec3(a.x*b.x, a.y*b.y, a.z*b.z) - func negative(v:Vec3->Vec3; inline) - return Vec3(-v.x, -v.y, -v.z) - func dot(a,b:Vec3->Num; inline) - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) - func cross(a,b:Vec3->Vec3; inline) - return Vec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) - func scaled_by(v:Vec3, k:Num->Vec3; inline) - return Vec3(v.x*k, v.y*k, v.z*k) - func divided_by(v:Vec3, divisor:Num->Vec3; inline) - return Vec3(v.x/divisor, v.y/divisor, v.z/divisor) - func length(v:Vec3->Num; inline) - return (v.x*v.x + v.y*v.y + v.z*v.z).sqrt() - func dist(a,b:Vec3->Num; inline) - return a.minus(b).length() - func norm(v:Vec3->Vec3; inline) - if v.x == 0 and v.y == 0 and v.z == 0 - return v - len := v.length() - return Vec3(v.x/len, v.y/len, v.z/len) - func mix(a,b:Vec3, amount:Num -> Vec3) - return Vec3( - amount.mix(a.x, b.x), - amount.mix(a.y, b.y), - amount.mix(a.z, b.z), - ) - - -struct IVec2(x,y:Int) - ZERO := IVec2(0, 0) - func plus(a,b:IVec2->IVec2; inline) - return IVec2(a.x+b.x, a.y+b.y) - func minus(a,b:IVec2->IVec2; inline) - return IVec2(a.x-b.x, a.y-b.y) - func times(a,b:IVec2->IVec2; inline) - return IVec2(a.x*b.x, a.y*b.y) - func negative(v:IVec2->IVec2; inline) - return IVec2(-v.x, -v.y) - func dot(a,b:IVec2->Int; inline) - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) - func cross(a,b:IVec2->Int; inline) - return a.x*b.y - a.y*b.x - func scaled_by(v:IVec2, k:Int->IVec2; inline) - return IVec2(v.x*k, v.y*k) - func divided_by(v:IVec2, divisor:Int->IVec2; inline) - return IVec2(v.x/divisor, v.y/divisor) - func length(v:IVec2->Num; inline) - x := Num(v.x) - y := Num(v.y) - return Num.sqrt(x*x + y*y) - func dist(a,b:IVec2->Num; inline) - return a.minus(b).length() - func angle(v:IVec2->Num; inline) - return Num.atan2(Num(v.y), Num(v.x)) - -struct IVec3(x,y,z:Int) - ZERO := IVec3(0, 0, 0) - func plus(a,b:IVec3->IVec3; inline) - return IVec3(a.x+b.x, a.y+b.y, a.z+b.z) - func minus(a,b:IVec3->IVec3; inline) - return IVec3(a.x-b.x, a.y-b.y, a.z-b.z) - func times(a,b:IVec3->IVec3; inline) - return IVec3(a.x*b.x, a.y*b.y, a.z*b.z) - func negative(v:IVec3->IVec3; inline) - return IVec3(-v.x, -v.y, -v.z) - func dot(a,b:IVec3->Int; inline) - return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y) + (a.z-b.z)*(a.z-b.z) - func cross(a,b:IVec3->IVec3; inline) - return IVec3(a.y*b.z - a.z-b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) - func scaled_by(v:IVec3, k:Int->IVec3; inline) - return IVec3(v.x*k, v.y*k, v.z*k) - func divided_by(v:IVec3, divisor:Int->IVec3; inline) - return IVec3(v.x/divisor, v.y/divisor, v.z/divisor) - func length(v:IVec3->Num; inline) - x := Num(v.x) - y := Num(v.y) - z := Num(v.z) - return Num.sqrt(x*x + y*y + z*z) - func dist(a,b:IVec3->Num; inline) - return a.minus(b).length() - -func main() - >> Vec2(10, 20) - >> Vec2(10, 20) + Vec2(100, 100) - = Vec2(x=110, y=120) - >> Vec3(10, 20, 30) - >> Vec2.ZERO - diff --git a/examples/wrap/CHANGES.md b/examples/wrap/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/examples/wrap/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/examples/wrap/wrap.tm b/examples/wrap/wrap.tm deleted file mode 100644 index bae01739..00000000 --- a/examples/wrap/wrap.tm +++ /dev/null @@ -1,102 +0,0 @@ -HELP := " - wrap: A tool for wrapping lines of text - - usage: wrap [--help] [files...] [--width=80] [--inplace=no] [--min_split=3] [--no-rewrap] [--hyphen='-'] - --help: Print this message and exit - [files...]: The files to wrap (stdin is used if no files are provided) - --width=N: The width to wrap the text - --inplace: Whether or not to perform the modification in-place or print the output - --min-split=N: The minimum amount of text on either end of a hyphenation split - --rewrap|--no-rewrap: Whether to rewrap text that is already wrapped or only split long lines - --hyphen='-': The text to use for hyphenation -" - -UNICODE_HYPHEN := "\{hyphen}" - -func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN -> Text) - if preserve_paragraphs - paragraphs := text.split($/{2+ nl}/) - if paragraphs.length > 1 - return "\n\n".join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs]) - - return text.replace("$(hyphen)\n", "") - -func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text) - if width <= 0 - fail("Width must be a positive integer, not $width") - - if 2*min_split - hyphen.length > width - fail(" - Minimum word split length ($min_split) is too small for the given wrap width ($width)! - - I can't fit a $(2*min_split - hyphen.length)-wide word on a line without splitting it, - ... and I can't split it without splitting into chunks smaller than $min_split. - ") - - lines : @[Text] - line := "" - for word in text.split($/{whitespace}/) - letters := word.split() - skip if letters.length == 0 - - while not _can_fit_word(line, letters, width) - line_space := width - line.length - if line != "" then line_space -= 1 - - if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split - # Split word with a hyphen: - split := line_space - hyphen.length - split = split _max_ min_split - split = split _min_ (letters.length - min_split) - if line != "" then line ++= " " - line ++= ((++: letters.to(split)) or "") ++ hyphen - letters = letters.from(split + 1) - else if line == "" - # Force split word without hyphenation: - if line != "" then line ++= " " - line ++= (++: letters.to(line_space)) or "" - letters = letters.from(line_space + 1) - else - pass # Move to next line - - lines.insert(line) - line = "" - - if letters.length > 0 - if line != "" then line ++= " " - line ++= (++: letters) or "" - - if line != "" - lines.insert(line) - - return "\n".join(lines) - -func _can_fit_word(line:Text, letters:[Text], width:Int -> Bool; inline) - if line == "" - return letters.length <= width - else - return line.length + 1 + letters.length <= width - -func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UNICODE_HYPHEN) - if files.length == 0 - files = [(/dev/stdin)] - - for file in files - text := file.read() or exit("Could not read file: $file") - - if rewrap - text = unwrap(text) - - out := if file.is_file() and inplace - file - else - (/dev/stdout) - - first := yes - wrapped_paragraphs : @[Text] - for paragraph in text.split($/{2+ nl}/) - wrapped_paragraphs.insert( - wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen) - ) - - out.write("\n\n".join(wrapped_paragraphs[]) ++ "\n") diff --git a/lib/README.md b/lib/README.md deleted file mode 100644 index 31de5b92..00000000 --- a/lib/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Core Libraries - -This folder contains some libraries that are installed by default when -installing Tomo. - -## Libraries - -Libraries can be installed with `tomo -IL ./library-folder` - -- [base64](base64/): A base64 encoding/decoding library. -- [commands](commands/): A library for running commands. -- [core](core/): Bundling up commonly used libraries into a single library. -- [json](json/): JSON parsing and encoding. -- [patterns](patterns/): Pattern matching for text. -- [pthreads](pthreads/): A POSIX threads library. -- [random](random/): Pseudorandom number generators. -- [shell](shell/): A DSL for running shell commands. -- [time](time/): A module for working with dates and times. -- [uuid](uuid/): A universally unique identifier library. diff --git a/lib/base64/CHANGES.md b/lib/base64/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/base64/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/base64/README.md b/lib/base64/README.md deleted file mode 100644 index 8db0f8ec..00000000 --- a/lib/base64/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Base64 - -This is a library for encoding/decoding Base64 values. diff --git a/lib/base64/base64.tm b/lib/base64/base64.tm deleted file mode 100644 index bf512a83..00000000 --- a/lib/base64/base64.tm +++ /dev/null @@ -1,96 +0,0 @@ -# Base 64 encoding and decoding - -_enc := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".bytes() - -_EQUAL_BYTE := Byte(0x3D) - -_dec : [Byte] = [ - 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 62, 255, 255, 255, 63, - 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 255, 255, 255, 255, 255, 255, - 255, 0, 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 255, 255, 255, 255, 255, - 255, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 255, 255, 255, 255, 255, -] - -lang Base64 - func parse(text:Text -> Base64?) - return Base64.from_bytes(text.bytes()) - - func from_bytes(bytes:[Byte] -> Base64?) - output := &[Byte(0) for _ in bytes.length * 4 / 3 + 4] - src := Int64(1) - dest := Int64(1) - while src + 2 <= Int64(bytes.length) - chunk24 := ( - (Int32(bytes[src]) <<< 16) or (Int32(bytes[src+1]) <<< 8) or Int32(bytes[src+2]) - ) - src += 3 - - output[dest] = _enc[1 + ((chunk24 >>> 18) and 0b111111)] - output[dest+1] = _enc[1 + ((chunk24 >>> 12) and 0b111111)] - output[dest+2] = _enc[1 + ((chunk24 >>> 6) and 0b111111)] - output[dest+3] = _enc[1 + (chunk24 and 0b111111)] - dest += 4 - - if src + 1 == bytes.length - chunk16 := ( - (Int32(bytes[src]) <<< 8) or Int32(bytes[src+1]) - ) - output[dest] = _enc[1 + ((chunk16 >>> 10) and 0b11111)] - output[dest+1] = _enc[1 + ((chunk16 >>> 4) and 0b111111)] - output[dest+2] = _enc[1 + ((chunk16 <<< 2)and 0b111111)] - output[dest+3] = _EQUAL_BYTE - else if src == bytes.length - chunk8 := Int32(bytes[src]) - output[dest] = _enc[1 + ((chunk8 >>> 2) and 0b111111)] - output[dest+1] = _enc[1 + ((chunk8 <<< 4) and 0b111111)] - output[dest+2] = _EQUAL_BYTE - output[dest+3] = _EQUAL_BYTE - - return Base64.from_text(Text.from_bytes(output[]) or return none) - - func decode_text(b64:Base64 -> Text?) - return Text.from_bytes(b64.decode_bytes() or return none) - - func decode_bytes(b64:Base64 -> [Byte]?) - bytes := b64.text.bytes() - output := &[Byte(0) for _ in bytes.length/4 * 3] - src := Int64(1) - dest := Int64(1) - while src + 3 <= Int64(bytes.length) - chunk24 := ( - (Int32(_dec[1+bytes[src]]) <<< 18) or - (Int32(_dec[1+bytes[src+1]]) <<< 12) or - (Int32(_dec[1+bytes[src+2]]) <<< 6) or - Int32(_dec[1+bytes[src+3]]) - ) - src += 4 - - output[dest] = Byte((chunk24 >>> 16) and 0xFF) - output[dest+1] = Byte((chunk24 >>> 8) and 0xFF) - output[dest+2] = Byte(chunk24 and 0xFF) - dest += 3 - - while output[-1] == 0xFF - output[] = output.to(-2) - - return output[] - -func main(input=(/dev/stdin), decode=no) - if decode - b := Base64.from_text(input.read()!) - say(b.decode_text()!) - else - text := input.read()! - say(Base64.parse(text)!.text) diff --git a/lib/commands/CHANGES.md b/lib/commands/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/commands/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/commands/README.md b/lib/commands/README.md deleted file mode 100644 index 040f4bd5..00000000 --- a/lib/commands/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Commands - -This module provides a way to run executable programs and get their output. You -can also feed in text to the programs' `stdin`. Think of it as `popen()` on -steroids. diff --git a/lib/commands/commands.c b/lib/commands/commands.c deleted file mode 100644 index 110ae696..00000000 --- a/lib/commands/commands.c +++ /dev/null @@ -1,295 +0,0 @@ -// Logic for running system commands - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// This is a workaround fix for an issue on some systems that don't have `__GLIBC__` defined -// and run into problems with - -#ifndef __GLIBC__ -#define __GLIBC__ 2 -#include // IWYU pragma: export -#undef __GLIBC__ -#else -#include // IWYU pragma: export -#endif - -#define READ_END 0 -#define WRITE_END 1 - -static void xpipe(int fd[2]) { - if (pipe(fd) != 0) fail("Failed to create pipe: ", strerror(errno)); -} - -int run_command(Text_t exe, List_t arg_list, Table_t env_table, OptionalList_t input_bytes, List_t *output_bytes, - List_t *error_bytes) { - pthread_testcancel(); - - struct sigaction sa = {.sa_handler = SIG_IGN}, oldint, oldquit; - sigaction(SIGINT, &sa, &oldint); - sigaction(SIGQUIT, &sa, &oldquit); - sigaddset(&sa.sa_mask, SIGCHLD); - sigset_t old, reset; - sigprocmask(SIG_BLOCK, &sa.sa_mask, &old); - sigemptyset(&reset); - if (oldint.sa_handler != SIG_IGN) sigaddset(&reset, SIGINT); - if (oldquit.sa_handler != SIG_IGN) sigaddset(&reset, SIGQUIT); - posix_spawnattr_t attr; - posix_spawnattr_init(&attr); - posix_spawnattr_setsigmask(&attr, &old); - posix_spawnattr_setsigdefault(&attr, &reset); - posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK); - - int child_inpipe[2], child_outpipe[2], child_errpipe[2]; - if (input_bytes.length >= 0) xpipe(child_inpipe); - if (output_bytes) xpipe(child_outpipe); - if (error_bytes) xpipe(child_errpipe); - - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - if (input_bytes.length >= 0) { - posix_spawn_file_actions_adddup2(&actions, child_inpipe[READ_END], STDIN_FILENO); - posix_spawn_file_actions_addclose(&actions, child_inpipe[WRITE_END]); - } - if (output_bytes) { - posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO); - posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]); - } - if (error_bytes) { - posix_spawn_file_actions_adddup2(&actions, child_errpipe[WRITE_END], STDERR_FILENO); - posix_spawn_file_actions_addclose(&actions, child_errpipe[READ_END]); - } - - const char *exe_str = Text$as_c_string(exe); - - List_t arg_strs = {}; - List$insert_value(&arg_strs, exe_str, I(0), sizeof(char *)); - for (int64_t i = 0; i < arg_list.length; i++) - List$insert_value(&arg_strs, Text$as_c_string(*(Text_t *)(arg_list.data + i * arg_list.stride)), I(0), - sizeof(char *)); - List$insert_value(&arg_strs, NULL, I(0), sizeof(char *)); - char **args = arg_strs.data; - - extern char **environ; - char **env = environ; - if (env_table.entries.length > 0) { - List_t env_list = {}; // List of const char* - for (char **e = environ; *e; e++) - List$insert(&env_list, e, I(0), sizeof(char *)); - - for (int64_t i = 0; i < env_table.entries.length; i++) { - struct { - Text_t key, value; - } *entry = env_table.entries.data + env_table.entries.stride * i; - const char *env_entry = String(entry->key, "=", entry->value); - List$insert(&env_list, &env_entry, I(0), sizeof(char *)); - } - List$insert_value(&env_list, NULL, I(0), sizeof(char *)); - assert(env_list.stride == sizeof(char *)); - env = env_list.data; - } - - pid_t pid; - int ret = exe_str[0] == '/' ? posix_spawn(&pid, exe_str, &actions, &attr, args, env) - : posix_spawnp(&pid, exe_str, &actions, &attr, args, env); - if (ret != 0) return -1; - - posix_spawnattr_destroy(&attr); - posix_spawn_file_actions_destroy(&actions); - - if (input_bytes.length >= 0) close(child_inpipe[READ_END]); - if (output_bytes) close(child_outpipe[WRITE_END]); - if (error_bytes) close(child_errpipe[WRITE_END]); - - struct pollfd pollfds[3] = {}; - if (input_bytes.length >= 0) pollfds[0] = (struct pollfd){.fd = child_inpipe[WRITE_END], .events = POLLOUT}; - if (output_bytes) pollfds[1] = (struct pollfd){.fd = child_outpipe[WRITE_END], .events = POLLIN}; - if (error_bytes) pollfds[2] = (struct pollfd){.fd = child_errpipe[WRITE_END], .events = POLLIN}; - - if (input_bytes.length > 0 && input_bytes.stride != 1) List$compact(&input_bytes, sizeof(char)); - if (output_bytes) *output_bytes = (List_t){.atomic = 1, .stride = 1, .length = 0}; - if (error_bytes) *error_bytes = (List_t){.atomic = 1, .stride = 1, .length = 0}; - - while (input_bytes.length > 0 || output_bytes || error_bytes) { - (void)poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1); // Wait for data or readiness - bool did_something = false; - if (input_bytes.length >= 0 && pollfds[0].revents) { - if (input_bytes.length > 0) { - ssize_t written = write(child_inpipe[WRITE_END], input_bytes.data, (size_t)input_bytes.length); - if (written > 0) { - input_bytes.data += written; - input_bytes.length -= (int64_t)written; - did_something = true; - } else if (written < 0) { - close(child_inpipe[WRITE_END]); - pollfds[0].events = 0; - } - } - if (input_bytes.length <= 0) { - close(child_inpipe[WRITE_END]); - pollfds[0].events = 0; - } - } - char buf[256]; - if (output_bytes && pollfds[1].revents) { - ssize_t n = read(child_outpipe[READ_END], buf, sizeof(buf)); - did_something = did_something || (n > 0); - if (n <= 0) { - close(child_outpipe[READ_END]); - pollfds[1].events = 0; - } else if (n > 0) { - if (output_bytes->free < n) { - output_bytes->data = GC_REALLOC(output_bytes->data, (size_t)(output_bytes->length + n)); - output_bytes->free = 0; - } - memcpy(output_bytes->data + output_bytes->length, buf, (size_t)n); - output_bytes->length += n; - } - } - if (error_bytes && pollfds[2].revents) { - ssize_t n = read(child_errpipe[READ_END], buf, sizeof(buf)); - did_something = did_something || (n > 0); - if (n <= 0) { - close(child_errpipe[READ_END]); - pollfds[2].events = 0; - } else if (n > 0) { - if (error_bytes->free < n) { - error_bytes->data = GC_REALLOC(error_bytes->data, (size_t)(error_bytes->length + n)); - error_bytes->free = 0; - } - memcpy(error_bytes->data + error_bytes->length, buf, (size_t)n); - error_bytes->length += n; - } - } - if (!did_something) break; - } - - int status = 0; - if (ret == 0) { - while (waitpid(pid, &status, 0) < 0 && errno == EINTR) { - if (WIFEXITED(status) || WIFSIGNALED(status)) break; - else if (WIFSTOPPED(status)) kill(pid, SIGCONT); - } - } - - if (input_bytes.length >= 0) close(child_inpipe[WRITE_END]); - if (output_bytes) close(child_outpipe[READ_END]); - if (error_bytes) close(child_errpipe[READ_END]); - - sigaction(SIGINT, &oldint, NULL); - sigaction(SIGQUIT, &oldquit, NULL); - sigprocmask(SIG_SETMASK, &old, NULL); - - if (ret) errno = ret; - return status; -} - -typedef struct { - pid_t pid; - FILE *out; -} child_info_t; - -static void _line_reader_cleanup(child_info_t *child) { - if (child && child->out) { - fclose(child->out); - child->out = NULL; - } - if (child->pid) { - kill(child->pid, SIGTERM); - child->pid = 0; - } -} - -static Text_t _next_line(child_info_t *child) { - if (!child || !child->out) return NONE_TEXT; - - char *line = NULL; - size_t size = 0; - ssize_t len = getline(&line, &size, child->out); - if (len <= 0) { - _line_reader_cleanup(child); - return NONE_TEXT; - } - - while (len > 0 && (line[len - 1] == '\r' || line[len - 1] == '\n')) - --len; - - if (u8_check((uint8_t *)line, (size_t)len) != NULL) fail("Invalid UTF8!"); - - Text_t line_text = Text$from_strn(line, len); - free(line); - return line_text; -} - -OptionalClosure_t command_by_line(Text_t exe, List_t arg_list, Table_t env_table) { - posix_spawnattr_t attr; - posix_spawnattr_init(&attr); - - int child_outpipe[2]; - xpipe(child_outpipe); - - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO); - posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]); - - const char *exe_str = Text$as_c_string(exe); - - List_t arg_strs = {}; - List$insert_value(&arg_strs, exe_str, I(0), sizeof(char *)); - for (int64_t i = 0; i < arg_list.length; i++) - List$insert_value(&arg_strs, Text$as_c_string(*(Text_t *)(arg_list.data + i * arg_list.stride)), I(0), - sizeof(char *)); - List$insert_value(&arg_strs, NULL, I(0), sizeof(char *)); - char **args = arg_strs.data; - - extern char **environ; - char **env = environ; - if (env_table.entries.length > 0) { - List_t env_list = {}; // List of const char* - for (char **e = environ; *e; e++) - List$insert(&env_list, e, I(0), sizeof(char *)); - - for (int64_t i = 0; i < env_table.entries.length; i++) { - struct { - Text_t key, value; - } *entry = env_table.entries.data + env_table.entries.stride * i; - const char *env_entry = String(entry->key, "=", entry->value); - List$insert(&env_list, &env_entry, I(0), sizeof(char *)); - } - List$insert_value(&env_list, NULL, I(0), sizeof(char *)); - assert(env_list.stride == sizeof(char *)); - env = env_list.data; - } - - pid_t pid; - int ret = exe_str[0] == '/' ? posix_spawn(&pid, exe_str, &actions, &attr, args, env) - : posix_spawnp(&pid, exe_str, &actions, &attr, args, env); - if (ret != 0) return NONE_CLOSURE; - - posix_spawnattr_destroy(&attr); - posix_spawn_file_actions_destroy(&actions); - - close(child_outpipe[WRITE_END]); - - child_info_t *child_info = GC_MALLOC(sizeof(child_info_t)); - child_info->out = fdopen(child_outpipe[READ_END], "r"); - child_info->pid = pid; - GC_register_finalizer(child_info, (void *)_line_reader_cleanup, NULL, NULL, NULL); - return (Closure_t){.fn = (void *)_next_line, .userdata = child_info}; -} - -#undef READ_END -#undef WRITE_END diff --git a/lib/commands/commands.tm b/lib/commands/commands.tm deleted file mode 100644 index 8a131042..00000000 --- a/lib/commands/commands.tm +++ /dev/null @@ -1,90 +0,0 @@ -# Functions for running system commands - -use ./commands.c - -extern run_command : func(exe:Text, args:[Text], env:{Text=Text}, input:[Byte]?, output:&[Byte]?, error:&[Byte]? -> Int32) -extern command_by_line : func(exe:Text, args:[Text], env:{Text=Text} -> func(->Text?)?) - -enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed) - func succeeded(e:ExitType -> Bool) - when e is Exited(status) return (status == 0) - else return no - - func or_fail(e:ExitType, message:Text?=none) - if not e.succeeded() - fail(message or "Program failed: $e") - -struct ProgramResult(output:[Byte], errors:[Byte], exit_type:ExitType) - func or_fail(r:ProgramResult, message:Text?=none -> ProgramResult) - when r.exit_type is Exited(status) - if status != 0 - fail(message or "Program failed: $r") - else fail(message or "Program failed: $r") - return r - - func output_text(r:ProgramResult, trim_newline=yes -> Text?) - when r.exit_type is Exited(status) - if status == 0 - if text := Text.from_bytes(r.output) - if trim_newline - text = text.without_suffix("\n") - return text - else return none - return none - - func error_text(r:ProgramResult -> Text?) - when r.exit_type is Exited(status) - if status == 0 - return Text.from_bytes(r.errors) - else return none - return none - - func succeeded(r:ProgramResult -> Bool) - when r.exit_type is Exited(status) - return (status == 0) - else - return no - -struct Command(command:Text, args:[Text]=[], env:{Text=Text}={}) - func from_path(path:Path, args:[Text]=[], env:{Text=Text}={} -> Command) - return Command(Text(path), args, env) - - func result(command:Command, input="", input_bytes:[Byte]=[] -> ProgramResult) - if input.length > 0 - (&input_bytes).insert_all(input.bytes()) - - output : [Byte] - errors : [Byte] - status := run_command(command.command, command.args, command.env, input_bytes, &output, &errors) - - if C_code:Bool(WIFEXITED(@status)) - return ProgramResult(output, errors, ExitType.Exited(C_code:Int32(WEXITSTATUS(@status)))) - - if C_code:Bool(WIFSIGNALED(@status)) - return ProgramResult(output, errors, ExitType.Signaled(C_code:Int32(WTERMSIG(@status)))) - - return ProgramResult(output, errors, ExitType.Failed) - - func run(command:Command, -> ExitType) - status := run_command(command.command, command.args, command.env, none, none, none) - - if C_code:Bool(WIFEXITED(@status)) - return ExitType.Exited(C_code:Int32(WEXITSTATUS(@status))) - - if C_code:Bool(WIFSIGNALED(@status)) - return ExitType.Signaled(C_code:Int32(WTERMSIG(@status))) - - return ExitType.Failed - - func get_output(command:Command, input="", trim_newline=yes -> Text?) - return command.result(input=input).output_text(trim_newline=trim_newline) - - func get_output_bytes(command:Command, input="", input_bytes:[Byte]=[] -> [Byte]?) - result := command.result(input=input, input_bytes=input_bytes) - when result.exit_type is Exited(status) - if status == 0 return result.output - return none - else return none - - func by_line(command:Command -> func(->Text?)?) - return command_by_line(command.command, command.args, command.env) diff --git a/lib/core/CHANGES.md b/lib/core/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/core/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/core/core.tm b/lib/core/core.tm deleted file mode 100644 index 5ed77756..00000000 --- a/lib/core/core.tm +++ /dev/null @@ -1,9 +0,0 @@ -# This file just uses all the most commonly used standard -# library modules so you don't have to import them one-by-one - -use patterns_v1.1 -use commands_v1.0 -use shell_v1.0 -use pthreads_v1.0 -use random_v1.0 -use time_v1.0 diff --git a/lib/json/CHANGES.md b/lib/json/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/json/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/json/README.md b/lib/json/README.md deleted file mode 100644 index 33e2101a..00000000 --- a/lib/json/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# JSON - -This is a library for encoding/decoding JSON values. - -## Usage - -```tomo ->> j := JSON({"key1"=123, "key2"=[yes, {"ok"="inner"}, JSON.Null]}) -= JSON.Object({"key1"=Number(123), "key2"=Array([Boolean(yes), Object({"ok"=String("inner")}), Null])}) - -say("$(j.encode())") -say("$(j.pretty_print())") - -when JSON.parse("[1, null, true]") is Success(obj) - >> obj -is Failure(msg) - fail("Failed to parse JSON: $msg") -``` diff --git a/lib/json/json.tm b/lib/json/json.tm deleted file mode 100644 index 8127ce52..00000000 --- a/lib/json/json.tm +++ /dev/null @@ -1,173 +0,0 @@ -# Base 64 encoding and decoding -use patterns - -enum JSONDecodeResult( - Success(json:JSON) - Failure(reason:Text) -) - func invalid(text:Text -> JSONDecodeResult) - return Failure("Unrecognized JSON: $(text.quoted())") - -extend Text - func json_quoted(text:Text -> Text) - return '"' ++ text.translate({ - "\\"="\\\\", - '"'='\\"', - "\f"="\\f", - "\r"="\\r", - "\n"="\\n", - "\b"="\\b", - "\t"="\\t", - }) ++ '"' - -enum JSON( - Object(items:{Text=JSON}) - Array(items:[JSON]) - Boolean(value:Bool) - String(text:Text) - Number(n:Num) - Null -) - func encode(j:JSON -> Text) - when j is Object(items) - return "{" ++ ", ".join([ - '$(k.json_quoted()): $(v.encode())' - for k,v in items - ]) ++ "}" - is Array(items) - return "[" ++ ", ".join([item.encode() for item in items]) ++ "]" - is Boolean(value) - return (if value then "true" else "false") - is String(text) - return text.json_quoted() - is Number(n) - return "$n" - is Null - return "null" - - func pretty_print(j:JSON, max_line:Int=80, indent:Text=" ", current_indent:Text="" -> Text) - inline := j.encode() - if inline.length > max_line - next_indent := current_indent ++ indent - when j is Object(items) - return "{\n$next_indent" ++ ",\n$next_indent".join([ - '$(k.json_quoted()): $(v.pretty_print(max_line, indent, next_indent))' - for k,v in items - ]) ++ "\n$current_indent}" - is Array(items) - return "[\n$next_indent" ++ ",\n$next_indent".join([item.pretty_print(max_line, indent, next_indent) for item in items]) ++ "\n$current_indent]" - else pass - - return inline - - func parse_text(text:Text, remainder:&Text? = none -> JSONDecodeResult) - if text.starts_with('"') - string := "" - pos := 2 - escapes := {"n"="\n", "t"="\t", "r"="\r", '"'='"', "\\"="\\", "/"="/", "b"="\b", "f"="\f"} - while pos <= text.length - c := text[pos] - if c == '"' - if remainder - remainder[] = text.from(pos + 1) - return Success(JSON.String(string)) - - if c == "\\" - stop if pos + 1 > text.length - - if esc := escapes[text[pos+1]] - string ++= esc - pos += 2 - else if m := text.matching_pattern($Pat/u{4 digit}/) - string ++= Text.from_codepoints([Int32.parse(m.captures[1])!]) - pos += 1 + m.text.length - else - if remainder - remainder[] = text - return JSONDecodeResult.invalid(text) - else - string ++= c - pos += 1 - - if remainder - remainder[] = text - return JSONDecodeResult.invalid(text) - - func parse(text:Text, remainder:&Text? = none, trailing_commas:Bool=no -> JSONDecodeResult) - if text.starts_with("true", remainder) - return Success(JSON.Boolean(yes)) - else if text.starts_with("false", remainder) - return Success(JSON.Boolean(no)) - else if text.starts_with("null", remainder) - return Success(JSON.Null) - else if n := Num.parse(text, remainder) - return Success(JSON.Number(n)) - else if text.starts_with('"') - return JSON.parse_text(text, remainder) - else if text.starts_with("[") - elements : &[JSON] - text = text.from(2).trim_pattern($Pat"{whitespace}", right=no) - repeat - when JSON.parse(text, &text) is Success(elem) - elements.insert(elem) - else stop - - if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}') - text = text.from(delim.text.length + 1) - else stop - - if trailing_commas - if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}') - text = text.from(delim.text.length + 1) - - if terminator := text.matching_pattern($Pat'{0+ ws}]') - if remainder - remainder[] = text.from(terminator.text.length + 1) - return Success(JSON.Array(elements)) - else if text.starts_with("{") - object : &{Text=JSON} - text = text.from(2).trim_pattern($Pat"{whitespace}", right=no) - repeat - key_text := text - when JSON.parse_text(text, &text) is Success(key) - if separator := text.matching_pattern($Pat'{0+ ws}:{0+ ws}') - text = text.from(separator.text.length + 1) - else - return JSONDecodeResult.invalid(text) - - when JSON.parse(text, &text) is Success(value) - when key is String(str) - object[str] = value - else - return JSONDecodeResult.invalid(key_text) - else - return JSONDecodeResult.invalid(text) - else stop - - if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}') - text = text.from(delim.text.length + 1) - else stop - - if trailing_commas - if delim := text.matching_pattern($Pat'{0+ ws},{0+ ws}') - text = text.from(delim.text.length + 1) - - if terminator := text.matching_pattern($Pat'{0+ ws}{}}') - if remainder - remainder[] = text.from(terminator.text.length + 1) - return Success(JSON.Object(object)) - - return JSONDecodeResult.invalid(text) - -func main(input=(/dev/stdin), pretty_print:Bool = no, trailing_commas:Bool = yes) - text := (input.read() or exit("Invalid file: $input")).trim_pattern($Pat"{whitespace}") - while text.length > 0 - when JSON.parse(text, remainder=&text, trailing_commas=trailing_commas) is Success(json) - if pretty_print - say(json.pretty_print()) - else - say(json.encode()) - is Failure(msg) - exit("\033[31;1m$msg\033[m", code=1) - - text = text.trim_pattern($Pat"{whitespace}") diff --git a/lib/patterns/CHANGES.md b/lib/patterns/CHANGES.md deleted file mode 100644 index cf6254cb..00000000 --- a/lib/patterns/CHANGES.md +++ /dev/null @@ -1,8 +0,0 @@ -# Version History - -## v1.1 -- Added `Text.matching_pattern(text:Text, pattern:Pattern, pos:Int = 1 -> PatternMatch?)` - -## v1.0 - -Initial version diff --git a/lib/patterns/README.md b/lib/patterns/README.md deleted file mode 100644 index faf2854e..00000000 --- a/lib/patterns/README.md +++ /dev/null @@ -1,444 +0,0 @@ -# Text Pattern Matching - -As an alternative to full regular expressions, Tomo provides a limited text -matching pattern syntax that is intended to solve 80% of use cases in under 1% -of the code size (PCRE's codebase is roughly 150k lines of code, and Tomo's -pattern matching code is a bit under 1k lines of code). Tomo's pattern matching -syntax is highly readable and works well for matching literal text without -getting [leaning toothpick syndrome](https://en.wikipedia.org/wiki/Leaning_toothpick_syndrome). - -For more advanced use cases, consider linking against a C library for regular -expressions or pattern matching. - -`Pat` is a [domain-specific language](docs/langs.md), in other words, it's -like a `Text`, but it has a distinct type. - -Patterns are used in a small, but very powerful API that handles many text -functions that would normally be handled by a more extensive API: - -- [`by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?))`](#by_pattern) -- [`by_pattern_split(text:Text, pattern:Pat -> func(->Text?))`](#by_pattern_split) -- [`each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes)`](#each_pattern) -- [`find_patterns(text:Text, pattern:Pat -> [PatternMatch])`](#find_patterns) -- [`has_pattern(text:Text, pattern:Pat -> Bool)`](#has_pattern) -- [`map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text)`](#map_pattern) -- [`matches_pattern(text:Text, pattern:Pat -> Bool)`](#matches_pattern) -- [`pattern_captures(text:Text, pattern:Pat -> [Text]?)`](#pattern_captures) -- [`replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text)`](#replace_pattern) -- [`split_pattern(text:Text, pattern:Pat -> [Text])`](#split_pattern) -- [`translate_patterns(text:Text, replacements:{Pat,Text}, backref="@", recursive=yes -> Text)`](#translate_patterns) -- [`trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text)`](#trim_pattern) - -## Matches - -Pattern matching functions work with a type called `PatternMatch` that has three fields: - -- `text`: The full text of the match. -- `index`: The index in the text where the match was found. -- `captures`: A list containing the matching text of each non-literal pattern group. - -See [Text Functions](text.md#Text-Functions) for the full API documentation. - -## Syntax - -Patterns have three types of syntax: - -- `{` followed by an optional count (`n`, `n-m`, or `n+`), followed by an - optional `!` to negate the pattern, followed by an optional pattern name or - Unicode character name, followed by a required `}`. - -- Any matching pair of quotes or parentheses or braces with a `?` in the middle - (e.g. `"?"` or `(?)`). - -- Any other character is treated as a literal to be matched exactly. - -## Named Patterns - -Named patterns match certain pre-defined patterns that are commonly useful. To -use a named pattern, use the syntax `{name}`. Names are case-insensitive and -mostly ignore spaces, underscores, and dashes. - -- `..` - Any character (note that a single `.` would mean the literal period - character). -- `digit` - A unicode digit -- `email` - an email address -- `emoji` - an emoji -- `end` - the very end of the text -- `id` - A unicode identifier -- `int` - One or more digits with an optional `-` (minus sign) in front -- `ip` - an IP address (IPv4 or IPv6) -- `ipv4` - an IPv4 address -- `ipv6` - an IPv6 address -- `nl`/`newline`/`crlf` - A line break (either `\r\n` or `\n`) -- `num` - One or more digits with an optional `-` (minus sign) in front and an optional `.` and more digits after -- `start` - the very start of the text -- `uri` - a URI -- `url` - a URL (URI that specifically starts with `http://`, `https://`, `ws://`, `wss://`, or `ftp://`) -- `word` - A unicode identifier (same as `id`) - -For non-alphabetic characters, any single character is treated as matching -exactly that character. For example, `{1{}` matches exactly one `{` -character. Or, `{1.}` matches exactly one `.` character. - -Patterns can also use any Unicode property name. Some helpful ones are: - -- `hex` - Hexidecimal digits -- `lower` - Lowercase letters -- `space` - The space character -- `upper` - Uppercase letters -- `whitespace` - Whitespace characters - -Patterns may also use exact Unicode codepoint names. For example: `{1 latin -small letter A}` matches `a`. - -## Negating Patterns - -If an exclamation mark (`!`) is placed before a pattern's name, then characters -are matched only when they _don't_ match the pattern. For example, `{!alpha}` -will match all characters _except_ alphabetic ones. - -## Interpolating Text and Escaping - -To escape a character in a pattern (e.g. if you want to match the literal -character `?`), you can use the syntax `{1 ?}`. This is almost never necessary -unless you have text that looks like a Tomo text pattern and has something like -`{` or `(?)` inside it. - -However, if you're trying to do an exact match of arbitrary text values, you'll -want to have the text automatically escaped. Fortunately, Tomo's injection-safe -DSL text interpolation supports automatic text escaping. This means that if you -use text interpolation with the `$` sign to insert a text value, the value will -be automatically escaped using the `{1 ?}` rule described above: - -```tomo -# Risk of code injection (would cause an error because 'xxx' is not a valid -# pattern name: ->> user_input := get_user_input() -= "{xxx}" - -# Interpolation automatically escapes: ->> $/$user_input/ -= $/{1{}..xxx}/ - -# This is: `{ 1{ }` (one open brace) followed by the literal text "..xxx}" - -# No error: ->> some_text.find($/$user_input/) -= 0 -``` - -If you prefer, you can also use this to insert literal characters: - -```tomo ->> $/literal $"{..}"/ -= $/literal {1{}..}/ -``` - -## Repetitions - -By default, named patterns match 1 or more repetitions, but you can specify how -many repetitions you want by putting a number or range of numbers first using -`n` (exactly `n` repetitions), `n-m` (between `n` and `m` repetitions), or `n+` -(`n` or more repetitions): - -``` -{4-5 alpha} -0x{hex} -{4 digit}-{2 digit}-{2 digit} -{2+ space} -{0-1 question mark} -``` - - -# Methods - -### `by_pattern` -Returns an iterator function that yields `PatternMatch` objects for each occurrence. - -```tomo -func by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?)) -``` - -- `text`: The text to search. -- `pattern`: The pattern to match. - -**Returns:** -An iterator function that yields `PatternMatch` objects one at a time. - -**Example:** -```tomo -text := "one, two, three" -for word in text.by_pattern($Pat"{id}"): - say(word.text) -``` - ---- - -### `by_pattern_split` -Returns an iterator function that yields text segments split by a pattern. - -```tomo -func by_pattern_split(text:Text, pattern:Pat -> func(->Text?)) -``` - -- `text`: The text to split. -- `pattern`: The pattern to use as a separator. - -**Returns:** -An iterator function that yields text segments. - -**Example:** -```tomo -text := "one two three" -for word in text.by_pattern_split($Pat"{whitespace}"): - say(word.text) -``` - ---- - -### `each_pattern` -Applies a function to each occurrence of a pattern in the text. - -```tomo -func each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes) -``` - -- `text`: The text to search. -- `pattern`: The pattern to match. -- `fn`: The function to apply to each match. -- `recursive`: If `yes`, applies the function recursively on modified text. - -**Example:** -```tomo -text := "one two three" -text.each_pattern($Pat"{id}", func(m:PatternMatch): - say(m.txt) -) -``` - ---- - -### `find_patterns` -Finds all occurrences of a pattern in a text and returns them as `PatternMatch` objects. - -```tomo -func find_patterns(text:Text, pattern:Pat -> [PatternMatch]) -``` - -- `text`: The text to search. -- `pattern`: The pattern to match. - -**Returns:** -A list of `PatternMatch` objects. - -**Example:** -```tomo -text := "one! two three!" ->> text.find_patterns($Pat"{id}!") -= [PatternMatch(text="one!", index=1, captures=["one"]), PatternMatch(text="three!", index=10, captures=["three"])] -``` - ---- - -### `has_pattern` -Checks whether a given pattern appears in the text. - -```tomo -func has_pattern(text:Text, pattern:Pat -> Bool) -``` - -- `text`: The text to search. -- `pattern`: The pattern to check for. - -**Returns:** -`yes` if a match is found, otherwise `no`. - -**Example:** -```tomo -text := "...okay..." ->> text.has_pattern($Pat"{id}") -= yes -``` - ---- - -### `map_pattern` -Transforms matches of a pattern using a mapping function. - -```tomo -func map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text) -``` - -- `text`: The text to modify. -- `pattern`: The pattern to match. -- `fn`: A function that transforms matches. -- `recursive`: If `yes`, applies transformations recursively. - -**Returns:** -A new text with the transformed matches. - -**Example:** -```tomo -text := "I have #apples and #oranges and #plums" -fruits := {"apples"=4, "oranges"=5} ->> text.map_pattern($Pat'#{id}', func(match:PatternMatch): - fruit := match.captures[1] - "$(fruits[fruit] or 0) $fruit" -) -= "I have 4 apples and 5 oranges and 0 plums" -``` - ---- - -### `matches_pattern` -Returns whether or not text matches a pattern completely. - -```tomo -func matches_pattern(text:Text, pattern:Pat -> Bool) -``` - -- `text`: The text to match against. -- `pattern`: The pattern to match. - -**Returns:** -`yes` if the whole text matches the pattern, otherwise `no`. - -**Example:** -```tomo ->> "Hello!!!".matches_pattern($Pat"{id}") -= no ->> "Hello".matches_pattern($Pat"{id}") -= yes -``` - ---- - -### `pattern_captures` -Returns a list of pattern captures for the given pattern. - -```tomo -func pattern_captures(text:Text, pattern:Pat -> [Text]?) -``` - -- `text`: The text to match against. -- `pattern`: The pattern to match. - -**Returns:** -An optional list of matched pattern captures. Returns `none` if the text does -not match the pattern. - -**Example:** -```tomo ->> "123 boxes".pattern_captures($Pat"{int} {id}") -= ["123", "boxes"]? ->> "xxx".pattern_captures($Pat"{int} {id}") -= none -``` - ---- - -### `replace_pattern` -Replaces occurrences of a pattern with a replacement text, supporting backreferences. - -```tomo -func replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text) -``` - -- `text`: The text to modify. -- `pattern`: The pattern to match. -- `replacement`: The text to replace matches with. -- `backref`: The symbol for backreferences in the replacement. -- `recursive`: If `yes`, applies replacements recursively. - -**Returns:** -A new text with replacements applied. - -**Example:** -```tomo ->> "I have 123 apples and 456 oranges".replace_pattern($Pat"{int}", "some") -= "I have some apples and some oranges" - ->> "I have 123 apples and 456 oranges".replace_pattern($Pat"{int}", "(@1)") -= "I have (123) apples and (456) oranges" - ->> "I have 123 apples and 456 oranges".replace_pattern($Pat"{int}", "(?1)", backref="?") -= "I have (123) apples and (456) oranges" - ->> "bad(fn(), bad(notbad))".replace_pattern($Pat"bad(?)", "good(@1)") -= "good(fn(), good(notbad))" - ->> "bad(fn(), bad(notbad))".replace_pattern($Pat"bad(?)", "good(@1)", recursive=no) -= "good(fn(), bad(notbad))" -``` - ---- - -### `split_pattern` -Splits a text into segments using a pattern as the delimiter. - -```tomo -func split_pattern(text:Text, pattern:Pat -> [Text]) -``` - -- `text`: The text to split. -- `pattern`: The pattern to use as a separator. - -**Returns:** -A list of text segments. - -**Example:** -```tomo ->> "one two three".split_pattern($Pat"{whitespace}") -= ["one", "two", "three"] -``` - ---- - -### `translate_patterns` -Replaces multiple patterns using a mapping of patterns to replacement texts. - -```tomo -func translate_patterns(text:Text, replacements:{Pat,Text}, backref="@", recursive=yes -> Text) -``` - -- `text`: The text to modify. -- `replacements`: A table mapping patterns to their replacements. -- `backref`: The symbol for backreferences in replacements. -- `recursive`: If `yes`, applies replacements recursively. - -**Returns:** -A new text with all specified replacements applied. - -**Example:** -```tomo ->> text := "foo(x, baz(1))" ->> text.translate_patterns({ - $Pat"{id}(?)"="call(fn('@1'), @2)", - $Pat"{id}"="var('@1')", - $Pat"{int}"="int(@1)", -}) -= "call(fn('foo'), var('x'), call(fn('baz'), int(1)))" -``` - ---- - -### `trim_pattern` -Removes matching patterns from the beginning and/or end of a text. - -```tomo -func trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text) -``` - -- `text`: The text to trim. -- `pattern`: The pattern to trim (defaults to whitespace). -- `left`: If `yes`, trims from the beginning. -- `right`: If `yes`, trims from the end. - -**Returns:** -The trimmed text. - -**Example:** -```tomo ->> "123abc456".trim_pattern($Pat"{digit}") -= "abc" -``` diff --git a/lib/patterns/_test.tm b/lib/patterns/_test.tm deleted file mode 100644 index 26c23628..00000000 --- a/lib/patterns/_test.tm +++ /dev/null @@ -1,256 +0,0 @@ -use patterns_v1.0 - -func main() - amelie := "Am\{UE9}lie" - amelie2 := "Am\{U65}\{U301}lie" - >> "Hello".replace_pattern($Pat/e/, "X") - = "HXllo" - - >> "Hello".has_pattern($Pat/l/) - = yes - >> "Hello".has_pattern($Pat/l{end}/) - = no - >> "Hello".has_pattern($Pat/{start}l/) - = no - - >> "Hello".has_pattern($Pat/o/) - = yes - >> "Hello".has_pattern($Pat/o{end}/) - = yes - >> "Hello".has_pattern($Pat/{start}o/) - = no - - >> "Hello".has_pattern($Pat/H/) - = yes - >> "Hello".has_pattern($Pat/H{end}/) - = no - >> "Hello".has_pattern($Pat/{start}H/) - = yes - - >> "Hello".replace_pattern($Pat/l/, "") - = "Heo" - >> "xxxx".replace_pattern($Pat/x/, "") - = "" - >> "xxxx".replace_pattern($Pat/y/, "") - = "xxxx" - >> "One two three four five six".replace_pattern($Pat/e /, "") - = "Ontwo threfour fivsix" - - >> " one ".replace_pattern($Pat/{start}{space}/, "") - = "one " - >> " one ".replace_pattern($Pat/{space}{end}/, "") - = " one" - - >> amelie.has_pattern($Pat/$amelie2/) - = yes - - >> "one two three".replace_pattern($Pat/{alpha}/, "") - = " " - >> "one two three".replace_pattern($Pat/{alpha}/, "word") - = "word word word" - - say("Test splitting and joining text:") - - >> "one two three".split_pattern($Pat/ /) - = ["one", "two", "three"] - - >> "one,two,three,".split_pattern($Pat/,/) - = ["one", "two", "three", ""] - - >> "one two three".split_pattern($Pat/{space}/) - = ["one", "two", "three"] - - >> "abc".split_pattern($Pat//) - = ["a", "b", "c"] - - >> ", ".join(["one", "two", "three"]) - = "one, two, three" - - >> "".join(["one", "two", "three"]) - = "onetwothree" - - >> "+".join(["one"]) - = "one" - - >> "+".join([]) - = "" - - say("Test text.find_patterns()") - >> " #one #two #three ".find_patterns($Pat/#{alpha}/) - = [PatternMatch(text="#one", index=2, captures=["one"]), PatternMatch(text="#two", index=8, captures=["two"]), PatternMatch(text="#three", index=13, captures=["three"])] - - >> " #one #two #three ".find_patterns($Pat/#{!space}/) - = [PatternMatch(text="#one", index=2, captures=["one"]), PatternMatch(text="#two", index=8, captures=["two"]), PatternMatch(text="#three", index=13, captures=["three"])] - - >> " ".find_patterns($Pat/{alpha}/) - = [] - - >> " foo(baz(), 1) doop() ".find_patterns($Pat/{id}(?)/) - = [PatternMatch(text="foo(baz(), 1)", index=2, captures=["foo", "baz(), 1"]), PatternMatch(text="doop()", index=17, captures=["doop", ""])] - - >> "".find_patterns($Pat'') - = [] - - >> "Hello".find_patterns($Pat'') - = [] - - say("Test text slicing:") - >> "abcdef".slice() - = "abcdef" - >> "abcdef".slice(from=3) - = "cdef" - >> "abcdef".slice(to=-2) - = "abcde" - >> "abcdef".slice(from=2, to=4) - = "bcd" - >> "abcdef".slice(from=5, to=1) - = "" - - >> house := "家" - = "家" - >> house.length - = 1 - >> house.codepoint_names() - = ["CJK Unified Ideographs-5BB6"] - >> house.utf32_codepoints() - = [23478] - - >> "🐧".codepoint_names() - = ["PENGUIN"] - - >> Text.from_codepoint_names(["not a valid name here buddy"]) - = none - - >> "one two; three four".find_patterns($Pat/; {..}/) - = [PatternMatch(text="; three four", index=8, captures=["three four"])] - - malicious := "{xxx}" - >> $Pat/$malicious/ - = $Pat/{1{}xxx}/ - - >> "Hello".replace_pattern($Pat/{lower}/, "(@0)") - = "H(ello)" - - >> " foo(xyz) foo(yyy) foo(z()) ".replace_pattern($Pat/foo(?)/, "baz(@1)") - = " baz(xyz) baz(yyy) baz(z()) " - - >> "".translate_patterns({$Pat//=">"}) - = "<tag>" - - >> " BAD(x, fn(y), BAD(z), w) ".replace_pattern($Pat/BAD(?)/, "good(@1)", recursive=yes) - = " good(x, fn(y), good(z), w) " - - >> " BAD(x, fn(y), BAD(z), w) ".replace_pattern($Pat/BAD(?)/, "good(@1)", recursive=no) - = " good(x, fn(y), BAD(z), w) " - - >> "Hello".matches_pattern($Pat/{id}/) - = yes - >> "Hello".matches_pattern($Pat/{lower}/) - = no - >> "Hello".matches_pattern($Pat/{upper}/) - = no - >> "Hello...".matches_pattern($Pat/{id}/) - = no - - >> "hello world".map_pattern($Pat/world/, func(m:PatternMatch) m.text.upper()) - = "hello WORLD" - - >> "Abc".repeat(3) - = "AbcAbcAbc" - - >> " abc def ".trim_pattern() - = "abc def" - >> " abc123def ".trim_pattern($Pat/{!digit}/) - = "123" - >> " abc123def ".trim_pattern($Pat/{!digit}/, left=no) - = " abc123" - >> " abc123def ".trim_pattern($Pat/{!digit}/, right=no) - = "123def " - # Only trim single whole matches that bookend the text: - >> "AbcAbcxxxxxxxxAbcAbc".trim_pattern($Pat/Abc/) - = "AbcxxxxxxxxAbc" - - >> "A=B=C=D".replace_pattern($Pat/{..}={..}/, "1:(@1) 2:(@2)") - = "1:(A) 2:(B=C=D)" - - >> "abcde".starts_with("ab") - = yes - >> "abcde".starts_with("bc") - = no - - >> "abcde".ends_with("de") - = yes - >> "abcde".starts_with("cd") - = no - - >> ("hello" ++ " " ++ "Amélie").reversed() - = "eilémA olleh" - - do - say("Testing concatenation-stability:") - ab := Text.from_codepoint_names(["LATIN SMALL LETTER E", "COMBINING VERTICAL LINE BELOW"])! - >> ab.codepoint_names() - = ["LATIN SMALL LETTER E", "COMBINING VERTICAL LINE BELOW"] - >> ab.length - = 1 - - a := Text.from_codepoint_names(["LATIN SMALL LETTER E"])! - b := Text.from_codepoint_names(["COMBINING VERTICAL LINE BELOW"])! - >> (a++b).codepoint_names() - = ["LATIN SMALL LETTER E", "COMBINING VERTICAL LINE BELOW"] - >> (a++b) == ab - = yes - >> (a++b).length - = 1 - - - do - concat := "e" ++ Text.from_codepoints([Int32(0x300)]) - >> concat.length - = 1 - - concat2 := concat ++ Text.from_codepoints([Int32(0x302)]) - >> concat2.length - = 1 - - concat3 := concat2 ++ Text.from_codepoints([Int32(0x303)]) - >> concat3.length - = 1 - - final := Text.from_codepoints([Int32(0x65), Int32(0x300), Int32(0x302), Int32(0x303)]) - >> final.length - = 1 - >> concat3 == final - = yes - - concat4 := Text.from_codepoints([Int32(0x65), Int32(0x300)]) ++ Text.from_codepoints([Int32(0x302), Int32(0x303)]) - >> concat4.length - = 1 - >> concat4 == final - = yes - - >> "x".left_pad(5) - = " x" - >> "x".right_pad(5) - = "x " - >> "x".middle_pad(5) - = " x " - >> "1234".left_pad(8, "XYZ") - = "XYZX1234" - >> "1234".right_pad(8, "XYZ") - = "1234XYZX" - >> "1234".middle_pad(9, "XYZ") - = "XY1234XYZ" - - >> amelie.width() - = 6 - cowboy := "🤠" - >> cowboy.width() - = 2 - >> cowboy.left_pad(4) - = " 🤠" - >> cowboy.right_pad(4) - = "🤠 " - >> cowboy.middle_pad(4) - = " 🤠 " - diff --git a/lib/patterns/match_type.h b/lib/patterns/match_type.h deleted file mode 100644 index f85f1862..00000000 --- a/lib/patterns/match_type.h +++ /dev/null @@ -1,9 +0,0 @@ -// A datatype used for pattern match results - -#pragma once - -typedef struct { - Text_t text; - Int_t index; - List_t captures; -} XMatch; diff --git a/lib/patterns/patterns.c b/lib/patterns/patterns.c deleted file mode 100644 index 27799c40..00000000 --- a/lib/patterns/patterns.c +++ /dev/null @@ -1,1212 +0,0 @@ -// Logic for text pattern matching - -#include -#include -#include -#include -#include -#include -#include - -#define MAX_BACKREFS 100 - -typedef struct { - Text_t text; - Int_t index; - List_t captures; -} PatternMatch; - -typedef struct { - Text_t text; - Int_t index; - List_t captures; - bool is_none : 1; -} OptionalPatternMatch; - -#define NONE_MATCH ((OptionalPatternMatch){.is_none = true}) - -typedef struct { - int64_t index, length; - bool occupied, recursive; -} capture_t; - -typedef struct { - enum { PAT_START, PAT_END, PAT_ANY, PAT_GRAPHEME, PAT_PROPERTY, PAT_QUOTE, PAT_PAIR, PAT_FUNCTION } tag; - bool negated, non_capturing; - int64_t min, max; - union { - int32_t grapheme; - uc_property_t property; - int64_t (*fn)(TextIter_t *, int64_t); - int32_t quote_graphemes[2]; - int32_t pair_graphemes[2]; - }; -} pat_t; - -static Text_t replace_list(Text_t text, List_t replacements, Text_t backref_pat, bool recursive); - -static INLINE void skip_whitespace(TextIter_t *state, int64_t *i) { - while (*i < state->stack[0].text.length) { - int32_t grapheme = Text$get_grapheme_fast(state, *i); - if (grapheme > 0 && !uc_is_property_white_space((ucs4_t)grapheme)) return; - *i += 1; - } -} - -static INLINE bool match_grapheme(TextIter_t *state, int64_t *i, int32_t grapheme) { - if (*i < state->stack[0].text.length && Text$get_grapheme_fast(state, *i) == grapheme) { - *i += 1; - return true; - } - return false; -} - -static INLINE bool match_str(TextIter_t *state, int64_t *i, const char *str) { - int64_t matched = 0; - while (matched[str]) { - if (*i + matched >= state->stack[0].text.length || Text$get_grapheme_fast(state, *i + matched) != str[matched]) - return false; - matched += 1; - } - *i += matched; - return true; -} - -static int64_t parse_int(TextIter_t *state, int64_t *i) { - int64_t value = 0; - for (;; *i += 1) { - uint32_t grapheme = Text$get_main_grapheme_fast(state, *i); - int digit = uc_digit_value(grapheme); - if (digit < 0) break; - if (value >= INT64_MAX / 10) break; - value = 10 * value + digit; - } - return value; -} - -static const char *get_property_name(TextIter_t *state, int64_t *i) { - skip_whitespace(state, i); - char *name = GC_MALLOC_ATOMIC(UNINAME_MAX); - char *dest = name; - while (*i < state->stack[0].text.length) { - int32_t grapheme = Text$get_grapheme_fast(state, *i); - if (!(grapheme & ~0xFF) && (isalnum(grapheme) || grapheme == ' ' || grapheme == '_' || grapheme == '-')) { - *dest = (char)grapheme; - ++dest; - if (dest >= name + UNINAME_MAX - 1) break; - } else { - break; - } - *i += 1; - } - - while (dest > name && dest[-1] == ' ') - *(dest--) = '\0'; - - if (dest == name) return NULL; - *dest = '\0'; - return name; -} - -#define EAT1(state, index, cond) \ - ({ \ - int32_t grapheme = Text$get_grapheme_fast(state, index); \ - bool success = (cond); \ - if (success) index += 1; \ - success; \ - }) - -#define EAT2(state, index, cond1, cond2) \ - ({ \ - int32_t grapheme = Text$get_grapheme_fast(state, index); \ - bool success = (cond1); \ - if (success) { \ - grapheme = Text$get_grapheme_fast(state, index + 1); \ - success = (cond2); \ - if (success) index += 2; \ - } \ - success; \ - }) - -#define EAT_MANY(state, index, cond) \ - ({ \ - int64_t _n = 0; \ - while (EAT1(state, index, cond)) { \ - _n += 1; \ - } \ - _n; \ - }) - -static int64_t match_email(TextIter_t *state, int64_t index) { - // email = local "@" domain - // local = 1-64 ([a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~] | non-ascii) - // domain = dns-label ("." dns-label)* - // dns-label = 1-63 ([a-zA-Z0-9-] | non-ascii) - - if (index > 0) { - uint32_t prev_codepoint = Text$get_main_grapheme_fast(state, index - 1); - if (uc_is_property_alphabetic(prev_codepoint)) return -1; - } - - int64_t start_index = index; - - // Local part: - int64_t local_len = 0; - static const char *allowed_local = "!#$%&‘*+–/=?^_`.{|}~"; - while (EAT1(state, index, (grapheme & ~0x7F) || isalnum((char)grapheme) || strchr(allowed_local, (char)grapheme))) { - local_len += 1; - if (local_len > 64) return -1; - } - - if (!EAT1(state, index, grapheme == '@')) return -1; - - // Host - int64_t host_len = 0; - do { - int64_t label_len = 0; - while (EAT1(state, index, (grapheme & ~0x7F) || isalnum((char)grapheme) || grapheme == '-')) { - label_len += 1; - if (label_len > 63) return -1; - } - - if (label_len == 0) return -1; - - host_len += label_len; - if (host_len > 255) return -1; - host_len += 1; - } while (EAT1(state, index, grapheme == '.')); - - return index - start_index; -} - -static int64_t match_ipv6(TextIter_t *state, int64_t index) { - if (index > 0) { - int32_t prev_codepoint = Text$get_grapheme_fast(state, index - 1); - if ((prev_codepoint & ~0x7F) && (isxdigit(prev_codepoint) || prev_codepoint == ':')) return -1; - } - int64_t start_index = index; - const int NUM_CLUSTERS = 8; - bool double_colon_used = false; - for (int cluster = 0; cluster < NUM_CLUSTERS; cluster++) { - for (int digits = 0; digits < 4; digits++) { - if (!EAT1(state, index, ~(grapheme & ~0x7F) && isxdigit((char)grapheme))) break; - } - if (EAT1(state, index, ~(grapheme & ~0x7F) && isxdigit((char)grapheme))) return -1; // Too many digits - - if (cluster == NUM_CLUSTERS - 1) { - break; - } else if (!EAT1(state, index, grapheme == ':')) { - if (double_colon_used) break; - return -1; - } - - if (EAT1(state, index, grapheme == ':')) { - if (double_colon_used) return -1; - double_colon_used = true; - } - } - return index - start_index; -} - -static int64_t match_ipv4(TextIter_t *state, int64_t index) { - if (index > 0) { - int32_t prev_codepoint = Text$get_grapheme_fast(state, index - 1); - if ((prev_codepoint & ~0x7F) && (isdigit(prev_codepoint) || prev_codepoint == '.')) return -1; - } - int64_t start_index = index; - - const int NUM_CLUSTERS = 4; - for (int cluster = 0; cluster < NUM_CLUSTERS; cluster++) { - for (int digits = 0; digits < 3; digits++) { - if (!EAT1(state, index, ~(grapheme & ~0x7F) && isdigit((char)grapheme))) { - if (digits == 0) return -1; - break; - } - } - - if (EAT1(state, index, ~(grapheme & ~0x7F) && isdigit((char)grapheme))) return -1; // Too many digits - - if (cluster == NUM_CLUSTERS - 1) break; - else if (!EAT1(state, index, grapheme == '.')) return -1; - } - return (index - start_index); -} - -static int64_t match_ip(TextIter_t *state, int64_t index) { - int64_t len = match_ipv6(state, index); - if (len >= 0) return len; - len = match_ipv4(state, index); - return (len >= 0) ? len : -1; -} - -static int64_t match_host(TextIter_t *state, int64_t index) { - int64_t ip_len = match_ip(state, index); - if (ip_len > 0) return ip_len; - - int64_t start_index = index; - if (match_grapheme(state, &index, '[')) { - ip_len = match_ip(state, index); - if (ip_len <= 0) return -1; - index += ip_len; - if (match_grapheme(state, &index, ']')) return (index - start_index); - return -1; - } - - if (!EAT1(state, index, isalpha(grapheme))) return -1; - - static const char *non_host_chars = "/#?:@ \t\r\n<>[]{}\\^|\"`"; - EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_host_chars, (char)grapheme)); - return (index - start_index); -} - -static int64_t match_authority(TextIter_t *state, int64_t index) { - int64_t authority_start = index; - static const char *non_segment_chars = "/#?:@ \t\r\n<>[]{}\\^|\"`."; - - // Optional user@ prefix: - int64_t username_len = EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_segment_chars, (char)grapheme)); - if (username_len < 1 || !EAT1(state, index, grapheme == '@')) index = authority_start; // No user@ part - - // Host: - int64_t host_len = match_host(state, index); - if (host_len <= 0) return -1; - index += host_len; - - // Port: - if (EAT1(state, index, grapheme == ':')) { - if (EAT_MANY(state, index, !(grapheme & ~0x7F) && isdigit(grapheme)) == 0) return -1; - } - return (index - authority_start); -} - -static int64_t match_uri(TextIter_t *state, int64_t index) { - // URI = scheme ":" ["//" authority] path ["?" query] ["#" fragment] - // scheme = [a-zA-Z] [a-zA-Z0-9+.-] - // authority = [userinfo "@"] host [":" port] - - if (index > 0) { - // Don't match if we're not at a word edge: - uint32_t prev_codepoint = Text$get_main_grapheme_fast(state, index - 1); - if (uc_is_property_alphabetic(prev_codepoint)) return -1; - } - - int64_t start_index = index; - - // Scheme: - if (!EAT1(state, index, isalpha(grapheme))) return -1; - EAT_MANY(state, index, - !(grapheme & ~0x7F) && (isalnum(grapheme) || grapheme == '+' || grapheme == '.' || grapheme == '-')); - if (!match_grapheme(state, &index, ':')) return -1; - - // Authority: - int64_t authority_len; - if (match_str(state, &index, "//")) { - authority_len = match_authority(state, index); - if (authority_len > 0) index += authority_len; - } else { - authority_len = 0; - } - - // Path: - int64_t path_start = index; - if (EAT1(state, index, grapheme == '/') || authority_len <= 0) { - static const char *non_path = " \"#?<>[]{}\\^`|"; - EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_path, (char)grapheme)); - - if (EAT1(state, index, grapheme == '?')) { // Query - static const char *non_query = " \"#<>[]{}\\^`|"; - EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_query, (char)grapheme)); - } - - if (EAT1(state, index, grapheme == '#')) { // Fragment - static const char *non_fragment = " \"#<>[]{}\\^`|"; - EAT_MANY(state, index, (grapheme & ~0x7F) || !strchr(non_fragment, (char)grapheme)); - } - } - - if (authority_len <= 0 && index == path_start) return -1; - - return index - start_index; -} - -static int64_t match_url(TextIter_t *state, int64_t index) { - int64_t lookahead = index; - if (!(match_str(state, &lookahead, "https:") || match_str(state, &lookahead, "http:") - || match_str(state, &lookahead, "ftp:") || match_str(state, &lookahead, "wss:") - || match_str(state, &lookahead, "ws:"))) - return -1; - - return match_uri(state, index); -} - -static int64_t match_id(TextIter_t *state, int64_t index) { - if (!EAT1(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_XID_START))) return -1; - return 1 + EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_XID_CONTINUE)); -} - -static int64_t match_int(TextIter_t *state, int64_t index) { - int64_t negative = EAT1(state, index, grapheme == '-') ? 1 : 0; - int64_t len = EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_DECIMAL_DIGIT)); - return len > 0 ? negative + len : -1; -} - -static int64_t match_alphanumeric(TextIter_t *state, int64_t index) { - return EAT1(state, index, uc_is_property_alphabetic((ucs4_t)grapheme) || uc_is_property_numeric((ucs4_t)grapheme)) - ? 1 - : -1; -} - -static int64_t match_num(TextIter_t *state, int64_t index) { - bool negative = EAT1(state, index, grapheme == '-') ? 1 : 0; - int64_t pre_decimal = EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_DECIMAL_DIGIT)); - bool decimal = (EAT1(state, index, grapheme == '.') == 1); - int64_t post_decimal = - decimal ? EAT_MANY(state, index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_DECIMAL_DIGIT)) : 0; - if (pre_decimal == 0 && post_decimal == 0) return -1; - return negative + pre_decimal + decimal + post_decimal; -} - -static int64_t match_newline(TextIter_t *state, int64_t index) { - if (index >= state->stack[0].text.length) return -1; - - uint32_t grapheme = index >= state->stack[0].text.length ? 0 : Text$get_main_grapheme_fast(state, index); - if (grapheme == '\n') return 1; - if (grapheme == '\r' && Text$get_grapheme_fast(state, index + 1) == '\n') return 2; - return -1; -} - -static int64_t match_pat(TextIter_t *state, int64_t index, pat_t pat) { - Text_t text = state->stack[0].text; - int32_t grapheme = index >= text.length ? 0 : Text$get_grapheme_fast(state, index); - - switch (pat.tag) { - case PAT_START: { - if (index == 0) return pat.negated ? -1 : 0; - return pat.negated ? 0 : -1; - } - case PAT_END: { - if (index >= text.length) return pat.negated ? -1 : 0; - return pat.negated ? 0 : -1; - } - case PAT_ANY: { - assert(!pat.negated); - return (index < text.length) ? 1 : -1; - } - case PAT_GRAPHEME: { - if (index >= text.length) return -1; - else if (grapheme == pat.grapheme) return pat.negated ? -1 : 1; - return pat.negated ? 1 : -1; - } - case PAT_PROPERTY: { - if (index >= text.length) return -1; - else if (uc_is_property((ucs4_t)grapheme, pat.property)) return pat.negated ? -1 : 1; - return pat.negated ? 1 : -1; - } - case PAT_PAIR: { - // Nested punctuation: (?), [?], etc - if (index >= text.length) return -1; - - int32_t open = pat.pair_graphemes[0]; - if (grapheme != open) return pat.negated ? 1 : -1; - - int32_t close = pat.pair_graphemes[1]; - int64_t depth = 1; - int64_t match_len = 1; - for (; depth > 0; match_len++) { - if (index + match_len >= text.length) return pat.negated ? 1 : -1; - - int32_t c = Text$get_grapheme_fast(state, index + match_len); - if (c == open) depth += 1; - else if (c == close) depth -= 1; - } - return pat.negated ? -1 : match_len; - } - case PAT_QUOTE: { - // Nested quotes: "?", '?', etc - if (index >= text.length) return -1; - - int32_t open = pat.quote_graphemes[0]; - if (grapheme != open) return pat.negated ? 1 : -1; - - int32_t close = pat.quote_graphemes[1]; - for (int64_t i = index + 1; i < text.length; i++) { - int32_t c = Text$get_grapheme_fast(state, i); - if (c == close) { - return pat.negated ? -1 : (i - index) + 1; - } else if (c == '\\' && index + 1 < text.length) { - i += 1; // Skip ahead an extra step - } - } - return pat.negated ? 1 : -1; - } - case PAT_FUNCTION: { - int64_t match_len = pat.fn(state, index); - if (match_len >= 0) return pat.negated ? -1 : match_len; - return pat.negated ? 1 : -1; - } - default: errx(1, "Invalid pattern"); - } - errx(1, "Unreachable"); - return 0; -} - -static pat_t parse_next_pat(TextIter_t *state, int64_t *index) { - if (EAT2(state, *index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_QUOTATION_MARK), grapheme == '?')) { - // Quotations: "?", '?', etc - int32_t open = Text$get_grapheme_fast(state, *index - 2); - int32_t close = open; - uc_mirror_char((ucs4_t)open, (ucs4_t *)&close); - if (!match_grapheme(state, index, close)) fail("Pattern's closing quote is missing: ", state->stack[0].text); - - return (pat_t){ - .tag = PAT_QUOTE, - .min = 1, - .max = 1, - .quote_graphemes = {open, close}, - }; - } else if (EAT2(state, *index, uc_is_property((ucs4_t)grapheme, UC_PROPERTY_PAIRED_PUNCTUATION), grapheme == '?')) { - // Nested punctuation: (?), [?], etc - int32_t open = Text$get_grapheme_fast(state, *index - 2); - int32_t close = open; - uc_mirror_char((ucs4_t)open, (ucs4_t *)&close); - if (!match_grapheme(state, index, close)) fail("Pattern's closing brace is missing: ", state->stack[0].text); - - return (pat_t){ - .tag = PAT_PAIR, - .min = 1, - .max = 1, - .pair_graphemes = {open, close}, - }; - } else if (EAT1(state, *index, grapheme == '{')) { // named patterns {id}, {2-3 hex}, etc. - skip_whitespace(state, index); - int64_t min, max; - if (uc_is_digit((ucs4_t)Text$get_grapheme_fast(state, *index))) { - min = parse_int(state, index); - skip_whitespace(state, index); - if (match_grapheme(state, index, '+')) { - max = INT64_MAX; - } else if (match_grapheme(state, index, '-')) { - max = parse_int(state, index); - } else { - max = min; - } - if (min > max) fail("Minimum repetitions (", min, ") is less than the maximum (", max, ")"); - } else { - min = -1, max = -1; - } - - skip_whitespace(state, index); - - bool negated = match_grapheme(state, index, '!'); -#define PAT(_tag, ...) ((pat_t){.min = min, .max = max, .negated = negated, .tag = _tag, __VA_ARGS__}) - const char *prop_name; - if (match_str(state, index, "..")) prop_name = ".."; - else prop_name = get_property_name(state, index); - - if (!prop_name) { - // Literal character, e.g. {1?} - skip_whitespace(state, index); - int32_t grapheme = Text$get_grapheme_fast(state, (*index)++); - if (!match_grapheme(state, index, '}')) fail("Missing closing '}' in pattern: ", state->stack[0].text); - return PAT(PAT_GRAPHEME, .grapheme = grapheme); - } else if (strlen(prop_name) == 1) { - // Single letter names: {1+ A} - skip_whitespace(state, index); - if (!match_grapheme(state, index, '}')) fail("Missing closing '}' in pattern: ", state->stack[0].text); - return PAT(PAT_GRAPHEME, .grapheme = prop_name[0]); - } - - skip_whitespace(state, index); - if (!match_grapheme(state, index, '}')) fail("Missing closing '}' in pattern: ", state->stack[0].text); - - switch (tolower(prop_name[0])) { - case '.': - if (prop_name[1] == '.') { - if (negated) return ((pat_t){.tag = PAT_END, .min = min, .max = max, .non_capturing = true}); - else return PAT(PAT_ANY); - } - break; - case 'a': - if (strcasecmp(prop_name, "authority") == 0) { - return PAT(PAT_FUNCTION, .fn = match_authority); - } else if (strcasecmp(prop_name, "alphanum") == 0 || strcasecmp(prop_name, "anum") == 0 - || strcasecmp(prop_name, "alphanumeric") == 0) { - return PAT(PAT_FUNCTION, .fn = match_alphanumeric); - } - break; - case 'c': - if (strcasecmp(prop_name, "crlf") == 0) return PAT(PAT_FUNCTION, .fn = match_newline); - break; - case 'd': - if (strcasecmp(prop_name, "digit") == 0) { - return PAT(PAT_PROPERTY, .property = UC_PROPERTY_DECIMAL_DIGIT); - } - break; - case 'e': - if (strcasecmp(prop_name, "end") == 0) { - return PAT(PAT_END, .non_capturing = !negated); - } else if (strcasecmp(prop_name, "email") == 0) { - return PAT(PAT_FUNCTION, .fn = match_email); - } -#if _LIBUNISTRING_VERSION >= 0x0100000 - else if (strcasecmp(prop_name, "emoji") == 0) { - return PAT(PAT_PROPERTY, .property = UC_PROPERTY_EMOJI); - } -#endif - break; - case 'h': - if (strcasecmp(prop_name, "host") == 0) { - return PAT(PAT_FUNCTION, .fn = match_host); - } - break; - case 'i': - if (strcasecmp(prop_name, "id") == 0) { - return PAT(PAT_FUNCTION, .fn = match_id); - } else if (strcasecmp(prop_name, "int") == 0) { - return PAT(PAT_FUNCTION, .fn = match_int); - } else if (strcasecmp(prop_name, "ipv4") == 0) { - return PAT(PAT_FUNCTION, .fn = match_ipv4); - } else if (strcasecmp(prop_name, "ipv6") == 0) { - return PAT(PAT_FUNCTION, .fn = match_ipv6); - } else if (strcasecmp(prop_name, "ip") == 0) { - return PAT(PAT_FUNCTION, .fn = match_ip); - } - break; - case 'n': - if (strcasecmp(prop_name, "nl") == 0 || strcasecmp(prop_name, "newline") == 0) { - return PAT(PAT_FUNCTION, .fn = match_newline); - } else if (strcasecmp(prop_name, "num") == 0) { - return PAT(PAT_FUNCTION, .fn = match_num); - } - break; - case 's': - if (strcasecmp(prop_name, "start") == 0) { - return PAT(PAT_START, .non_capturing = !negated); - } - break; - case 'u': - if (strcasecmp(prop_name, "uri") == 0) { - return PAT(PAT_FUNCTION, .fn = match_uri); - } else if (strcasecmp(prop_name, "url") == 0) { - return PAT(PAT_FUNCTION, .fn = match_url); - } - break; - case 'w': - if (strcasecmp(prop_name, "word") == 0) { - return PAT(PAT_FUNCTION, .fn = match_id); - } else if (strcasecmp(prop_name, "ws") == 0 || strcasecmp(prop_name, "whitespace") == 0) { - return PAT(PAT_PROPERTY, .property = UC_PROPERTY_WHITE_SPACE); - } - break; - default: break; - } - - uc_property_t prop = uc_property_byname(prop_name); - if (uc_property_is_valid(prop)) return PAT(PAT_PROPERTY, .property = prop); - - ucs4_t grapheme = unicode_name_character(prop_name); - if (grapheme == UNINAME_INVALID) fail("Not a valid property or character name: ", prop_name); - return PAT(PAT_GRAPHEME, .grapheme = (int32_t)grapheme); -#undef PAT - } else { - return (pat_t){.tag = PAT_GRAPHEME, - .non_capturing = true, - .min = 1, - .max = 1, - .grapheme = Text$get_grapheme_fast(state, (*index)++)}; - } -} - -static int64_t match(Text_t text, int64_t text_index, Text_t pattern, int64_t pattern_index, capture_t *captures, - int64_t capture_index) { - if (pattern_index >= pattern.length) // End of the pattern - return 0; - - int64_t start_index = text_index; - TextIter_t pattern_state = NEW_TEXT_ITER_STATE(pattern), text_state = NEW_TEXT_ITER_STATE(text); - pat_t pat = parse_next_pat(&pattern_state, &pattern_index); - - if (pat.min == -1 && pat.max == -1) { - if (pat.tag == PAT_ANY && pattern_index >= pattern.length) { - pat.min = pat.max = MAX(1, text.length - text_index); - } else { - pat.min = 1; - pat.max = INT64_MAX; - } - } - - int64_t capture_start = text_index; - int64_t count = 0, capture_len = 0, next_match_len = 0; - - if (pat.tag == PAT_ANY && pattern_index >= pattern.length) { - int64_t remaining = text.length - text_index; - capture_len = remaining >= pat.min ? MIN(remaining, pat.max) : -1; - text_index += capture_len; - goto success; - } - - if (pat.min == 0 && pattern_index < pattern.length) { - next_match_len = - match(text, text_index, pattern, pattern_index, captures, capture_index + (pat.non_capturing ? 0 : 1)); - if (next_match_len >= 0) { - capture_len = 0; - goto success; - } - } - - while (count < pat.max) { - int64_t match_len = match_pat(&text_state, text_index, pat); - if (match_len < 0) break; - capture_len += match_len; - text_index += match_len; - count += 1; - - if (pattern_index < pattern.length) { // More stuff after this - if (count < pat.min) next_match_len = -1; - else - next_match_len = match(text, text_index, pattern, pattern_index, captures, - capture_index + (pat.non_capturing ? 0 : 1)); - } else { - next_match_len = 0; - } - - if (match_len == 0) { - if (next_match_len >= 0) { - // If we're good to go, no need to keep re-matching zero-length - // matches till we hit max: - count = pat.max; - break; - } else { - return -1; - } - } - - if (pattern_index < pattern.length && next_match_len >= 0) break; // Next guy exists and wants to stop here - - if (text_index >= text.length) break; - } - - if (count < pat.min || next_match_len < 0) return -1; - -success: - if (captures && capture_index < MAX_BACKREFS && !pat.non_capturing) { - if (pat.tag == PAT_PAIR || pat.tag == PAT_QUOTE) { - assert(capture_len > 0); - captures[capture_index] = (capture_t){ - .index = capture_start + 1, // Skip leading quote/paren - .length = capture_len - 2, // Skip open/close - .occupied = true, - .recursive = (pat.tag == PAT_PAIR), - }; - } else { - captures[capture_index] = (capture_t){ - .index = capture_start, - .length = capture_len, - .occupied = true, - .recursive = false, - }; - } - } - return (text_index - start_index) + next_match_len; -} - -#undef EAT1 -#undef EAT2 -#undef EAT_MANY - -static int64_t _find(Text_t text, Text_t pattern, int64_t first, int64_t last, int64_t *match_length, - capture_t *captures) { - int32_t first_grapheme = Text$get_grapheme(pattern, 0); - bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK) - && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION)); - - TextIter_t text_state = NEW_TEXT_ITER_STATE(text); - for (int64_t i = first; i <= last; i++) { - // Optimization: quickly skip ahead to first char in pattern: - if (find_first) { - while (i < text.length && Text$get_grapheme_fast(&text_state, i) != first_grapheme) - ++i; - } - - int64_t m = match(text, i, pattern, 0, captures, 0); - if (m >= 0) { - if (match_length) *match_length = m; - return i; - } - } - if (match_length) *match_length = -1; - return -1; -} - -static OptionalPatternMatch find(Text_t text, Text_t pattern, Int_t from_index) { - int64_t first = Int64$from_int(from_index, false); - if (first == 0) fail("Invalid index: 0"); - if (first < 0) first = text.length + first + 1; - if (first > text.length || first < 1) return NONE_MATCH; - - capture_t captures[MAX_BACKREFS] = {}; - int64_t len = 0; - int64_t found = _find(text, pattern, first - 1, text.length - 1, &len, captures); - if (found == -1) return NONE_MATCH; - - List_t capture_list = {}; - for (int i = 0; captures[i].occupied; i++) { - Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length)); - List$insert(&capture_list, &capture, I(0), sizeof(Text_t)); - } - return (OptionalPatternMatch){ - .text = Text$slice(text, I(found + 1), I(found + len)), - .index = I(found + 1), - .captures = capture_list, - }; -} - -PUREFUNC static bool Pattern$has(Text_t text, Text_t pattern) { - if (Text$starts_with(pattern, Text("{start}"), &pattern)) { - int64_t m = match(text, 0, pattern, 0, NULL, 0); - return m >= 0; - } else if (Text$ends_with(text, Text("{end}"), NULL)) { - for (int64_t i = text.length - 1; i >= 0; i--) { - int64_t match_len = match(text, i, pattern, 0, NULL, 0); - if (match_len >= 0 && i + match_len == text.length) return true; - } - return false; - } else { - int64_t found = _find(text, pattern, 0, text.length - 1, NULL, NULL); - return (found >= 0); - } -} - -static bool Pattern$matches(Text_t text, Text_t pattern) { - capture_t captures[MAX_BACKREFS] = {}; - int64_t match_len = match(text, 0, pattern, 0, NULL, 0); - return (match_len == text.length); -} - -static bool Pattern$match_at(Text_t text, Text_t pattern, Int_t pos, PatternMatch *dest) { - int64_t start = Int64$from_int(pos, false) - 1; - capture_t captures[MAX_BACKREFS] = {}; - int64_t match_len = match(text, start, pattern, 0, captures, 0); - if (match_len < 0) return false; - - List_t capture_list = {}; - for (int i = 0; captures[i].occupied; i++) { - Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length)); - List$insert(&capture_list, &capture, I(0), sizeof(Text_t)); - } - dest->text = Text$slice(text, I(start + 1), I(start + match_len)); - dest->index = I(start + 1); - dest->captures = capture_list; - return true; -} - -static OptionalList_t Pattern$captures(Text_t text, Text_t pattern) { - capture_t captures[MAX_BACKREFS] = {}; - int64_t match_len = match(text, 0, pattern, 0, captures, 0); - if (match_len != text.length) return NONE_LIST; - - List_t capture_list = {}; - for (int i = 0; captures[i].occupied; i++) { - Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length)); - List$insert(&capture_list, &capture, I(0), sizeof(Text_t)); - } - return capture_list; -} - -static List_t Pattern$find_all(Text_t text, Text_t pattern) { - if (pattern.length == 0) // special case - return (List_t){.length = 0}; - - List_t matches = {}; - for (int64_t i = 1;;) { - OptionalPatternMatch m = find(text, pattern, I(i)); - if (m.is_none) break; - i = Int64$from_int(m.index, false) + m.text.length; - List$insert(&matches, &m, I_small(0), sizeof(PatternMatch)); - } - return matches; -} - -typedef struct { - TextIter_t state; - Int_t i; - Text_t pattern; -} match_iter_state_t; - -static OptionalPatternMatch next_match(match_iter_state_t *state) { - if (Int64$from_int(state->i, false) > state->state.stack[0].text.length) return NONE_MATCH; - - OptionalPatternMatch m = find(state->state.stack[0].text, state->pattern, state->i); - if (m.is_none) // No match - state->i = I(state->state.stack[0].text.length + 1); - else state->i = Int$plus(m.index, I(MAX(1, m.text.length))); - return m; -} - -static Closure_t Pattern$by_match(Text_t text, Text_t pattern) { - return (Closure_t){ - .fn = (void *)next_match, - .userdata = new (match_iter_state_t, .state = NEW_TEXT_ITER_STATE(text), .i = I_small(1), .pattern = pattern), - }; -} - -static Text_t apply_backrefs(Text_t text, List_t recursive_replacements, Text_t replacement, Text_t backref_pat, - capture_t *captures) { - if (backref_pat.length == 0) return replacement; - - int32_t first_grapheme = Text$get_grapheme(backref_pat, 0); - bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK) - && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION)); - - Text_t ret = Text(""); - TextIter_t replacement_state = NEW_TEXT_ITER_STATE(replacement); - int64_t nonmatching_pos = 0; - for (int64_t pos = 0; pos < replacement.length;) { - // Optimization: quickly skip ahead to first char in the backref pattern: - if (find_first) { - while (pos < replacement.length && Text$get_grapheme_fast(&replacement_state, pos) != first_grapheme) - ++pos; - } - - int64_t backref_len = match(replacement, pos, backref_pat, 0, NULL, 0); - if (backref_len < 0) { - pos += 1; - continue; - } - - int64_t after_backref = pos + backref_len; - int64_t backref = parse_int(&replacement_state, &after_backref); - if (after_backref == pos + backref_len) { // Not actually a backref if there's no number - pos += 1; - continue; - } - if (backref < 0 || backref > 9) - fail("Invalid backref index: ", backref, " (only 0-", MAX_BACKREFS - 1, " are allowed)"); - backref_len = (after_backref - pos); - - if (Text$get_grapheme_fast(&replacement_state, pos + backref_len) == ';') - backref_len += 1; // skip optional semicolon - - if (!captures[backref].occupied) fail("There is no capture number ", backref, "!"); - - Text_t backref_text = - Text$slice(text, I(captures[backref].index + 1), I(captures[backref].index + captures[backref].length)); - - if (captures[backref].recursive && recursive_replacements.length > 0) - backref_text = replace_list(backref_text, recursive_replacements, backref_pat, true); - - if (pos > nonmatching_pos) { - Text_t before_slice = Text$slice(replacement, I(nonmatching_pos + 1), I(pos)); - ret = Text$concat(ret, before_slice, backref_text); - } else { - ret = Text$concat(ret, backref_text); - } - - pos += backref_len; - nonmatching_pos = pos; - } - if (nonmatching_pos < replacement.length) { - Text_t last_slice = Text$slice(replacement, I(nonmatching_pos + 1), I(replacement.length)); - ret = Text$concat(ret, last_slice); - } - return ret; -} - -static Text_t Pattern$replace(Text_t text, Text_t pattern, Text_t replacement, Text_t backref_pat, bool recursive) { - Text_t ret = EMPTY_TEXT; - - int32_t first_grapheme = Text$get_grapheme(pattern, 0); - bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK) - && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION)); - - Text_t entries[2] = {pattern, replacement}; - List_t replacements = { - .data = entries, - .length = 1, - .stride = sizeof(entries), - }; - - TextIter_t text_state = NEW_TEXT_ITER_STATE(text); - int64_t nonmatching_pos = 0; - for (int64_t pos = 0; pos < text.length;) { - // Optimization: quickly skip ahead to first char in pattern: - if (find_first) { - while (pos < text.length && Text$get_grapheme_fast(&text_state, pos) != first_grapheme) - ++pos; - } - - capture_t captures[MAX_BACKREFS] = {}; - int64_t match_len = match(text, pos, pattern, 0, captures, 1); - if (match_len < 0) { - pos += 1; - continue; - } - captures[0] = (capture_t){ - .index = pos, - .length = match_len, - .occupied = true, - .recursive = false, - }; - - Text_t replacement_text = - apply_backrefs(text, recursive ? replacements : (List_t){}, replacement, backref_pat, captures); - if (pos > nonmatching_pos) { - Text_t before_slice = Text$slice(text, I(nonmatching_pos + 1), I(pos)); - ret = Text$concat(ret, before_slice, replacement_text); - } else { - ret = Text$concat(ret, replacement_text); - } - nonmatching_pos = pos + match_len; - pos += MAX(match_len, 1); - } - if (nonmatching_pos < text.length) { - Text_t last_slice = Text$slice(text, I(nonmatching_pos + 1), I(text.length)); - ret = Text$concat(ret, last_slice); - } - return ret; -} - -static Text_t Pattern$trim(Text_t text, Text_t pattern, bool trim_left, bool trim_right) { - int64_t first = 0, last = text.length - 1; - if (trim_left) { - int64_t match_len = match(text, 0, pattern, 0, NULL, 0); - if (match_len > 0) first = match_len; - } - - if (trim_right) { - for (int64_t i = text.length - 1; i >= first; i--) { - int64_t match_len = match(text, i, pattern, 0, NULL, 0); - if (match_len > 0 && i + match_len == text.length) last = i - 1; - } - } - return Text$slice(text, I(first + 1), I(last + 1)); -} - -static Text_t Pattern$map(Text_t text, Text_t pattern, Closure_t fn, bool recursive) { - Text_t ret = EMPTY_TEXT; - - int32_t first_grapheme = Text$get_grapheme(pattern, 0); - bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK) - && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION)); - - TextIter_t text_state = NEW_TEXT_ITER_STATE(text); - int64_t nonmatching_pos = 0; - - Text_t (*text_mapper)(PatternMatch, void *) = fn.fn; - for (int64_t pos = 0; pos < text.length; pos++) { - // Optimization: quickly skip ahead to first char in pattern: - if (find_first) { - while (pos < text.length && Text$get_grapheme_fast(&text_state, pos) != first_grapheme) - ++pos; - } - - capture_t captures[MAX_BACKREFS] = {}; - int64_t match_len = match(text, pos, pattern, 0, captures, 0); - if (match_len < 0) continue; - - PatternMatch m = { - .text = Text$slice(text, I(pos + 1), I(pos + match_len)), - .index = I(pos + 1), - .captures = {}, - }; - for (int i = 0; captures[i].occupied; i++) { - Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length)); - if (recursive) capture = Pattern$map(capture, pattern, fn, recursive); - List$insert(&m.captures, &capture, I(0), sizeof(Text_t)); - } - - Text_t replacement = text_mapper(m, fn.userdata); - if (pos > nonmatching_pos) { - Text_t before_slice = Text$slice(text, I(nonmatching_pos + 1), I(pos)); - ret = Text$concat(ret, before_slice, replacement); - } else { - ret = Text$concat(ret, replacement); - } - nonmatching_pos = pos + match_len; - pos += (match_len - 1); - } - if (nonmatching_pos < text.length) { - Text_t last_slice = Text$slice(text, I(nonmatching_pos + 1), I(text.length)); - ret = Text$concat(ret, last_slice); - } - return ret; -} - -static void Pattern$each(Text_t text, Text_t pattern, Closure_t fn, bool recursive) { - int32_t first_grapheme = Text$get_grapheme(pattern, 0); - bool find_first = (first_grapheme != '{' && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_QUOTATION_MARK) - && !uc_is_property((ucs4_t)first_grapheme, UC_PROPERTY_PAIRED_PUNCTUATION)); - - TextIter_t text_state = NEW_TEXT_ITER_STATE(text); - void (*action)(PatternMatch, void *) = fn.fn; - for (int64_t pos = 0; pos < text.length; pos++) { - // Optimization: quickly skip ahead to first char in pattern: - if (find_first) { - while (pos < text.length && Text$get_grapheme_fast(&text_state, pos) != first_grapheme) - ++pos; - } - - capture_t captures[MAX_BACKREFS] = {}; - int64_t match_len = match(text, pos, pattern, 0, captures, 0); - if (match_len < 0) continue; - - PatternMatch m = { - .text = Text$slice(text, I(pos + 1), I(pos + match_len)), - .index = I(pos + 1), - .captures = {}, - }; - for (int i = 0; captures[i].occupied; i++) { - Text_t capture = Text$slice(text, I(captures[i].index + 1), I(captures[i].index + captures[i].length)); - if (recursive) Pattern$each(capture, pattern, fn, recursive); - List$insert(&m.captures, &capture, I(0), sizeof(Text_t)); - } - - action(m, fn.userdata); - pos += (match_len - 1); - } -} - -Text_t replace_list(Text_t text, List_t replacements, Text_t backref_pat, bool recursive) { - if (replacements.length == 0) return text; - - Text_t ret = EMPTY_TEXT; - - int64_t nonmatch_pos = 0; - for (int64_t pos = 0; pos < text.length;) { - // Find the first matching pattern at this position: - for (int64_t i = 0; i < replacements.length; i++) { - Text_t pattern = *(Text_t *)(replacements.data + i * replacements.stride); - capture_t captures[MAX_BACKREFS] = {}; - int64_t len = match(text, pos, pattern, 0, captures, 1); - if (len < 0) continue; - captures[0].index = pos; - captures[0].length = len; - - // If we skipped over some non-matching text before finding a match, insert it here: - if (pos > nonmatch_pos) { - Text_t before_slice = Text$slice(text, I(nonmatch_pos + 1), I(pos)); - ret = Text$concat(ret, before_slice); - } - - // Concatenate the replacement: - Text_t replacement = *(Text_t *)(replacements.data + i * replacements.stride + sizeof(Text_t)); - Text_t replacement_text = - apply_backrefs(text, recursive ? replacements : (List_t){}, replacement, backref_pat, captures); - ret = Text$concat(ret, replacement_text); - pos += MAX(len, 1); - nonmatch_pos = pos; - goto next_pos; - } - - pos += 1; - next_pos: - continue; - } - - if (nonmatch_pos <= text.length) { - Text_t last_slice = Text$slice(text, I(nonmatch_pos + 1), I(text.length)); - ret = Text$concat(ret, last_slice); - } - return ret; -} - -static Text_t Pattern$replace_all(Text_t text, Table_t replacements, Text_t backref_pat, bool recursive) { - return replace_list(text, replacements.entries, backref_pat, recursive); -} - -static List_t Pattern$split(Text_t text, Text_t pattern) { - if (text.length == 0) // special case - return (List_t){.length = 0}; - - if (pattern.length == 0) // special case - return Text$clusters(text); - - List_t chunks = {}; - - int64_t i = 0; - for (;;) { - int64_t len = 0; - int64_t found = _find(text, pattern, i, text.length - 1, &len, NULL); - if (found == i && len == 0) found = _find(text, pattern, i + 1, text.length - 1, &len, NULL); - if (found < 0) break; - Text_t chunk = Text$slice(text, I(i + 1), I(found)); - List$insert(&chunks, &chunk, I_small(0), sizeof(Text_t)); - i = MAX(found + len, i + 1); - } - - Text_t last_chunk = Text$slice(text, I(i + 1), I(text.length)); - List$insert(&chunks, &last_chunk, I_small(0), sizeof(Text_t)); - - return chunks; -} - -typedef struct { - TextIter_t state; - int64_t i; - Text_t pattern; -} split_iter_state_t; - -static OptionalText_t next_split(split_iter_state_t *state) { - Text_t text = state->state.stack[0].text; - if (state->i >= text.length) { - if (state->pattern.length > 0 && state->i == text.length) { // special case - state->i = text.length + 1; - return EMPTY_TEXT; - } - return NONE_TEXT; - } - - if (state->pattern.length == 0) { // special case - Text_t ret = Text$cluster(text, I(state->i + 1)); - state->i += 1; - return ret; - } - - int64_t start = state->i; - int64_t len = 0; - int64_t found = _find(text, state->pattern, start, text.length - 1, &len, NULL); - - if (found == start && len == 0) found = _find(text, state->pattern, start + 1, text.length - 1, &len, NULL); - - if (found >= 0) { - state->i = MAX(found + len, state->i + 1); - return Text$slice(text, I(start + 1), I(found)); - } else { - state->i = state->state.stack[0].text.length + 1; - return Text$slice(text, I(start + 1), I(text.length)); - } -} - -static Closure_t Pattern$by_split(Text_t text, Text_t pattern) { - return (Closure_t){ - .fn = (void *)next_split, - .userdata = new (split_iter_state_t, .state = NEW_TEXT_ITER_STATE(text), .i = 0, .pattern = pattern), - }; -} - -static Text_t Pattern$escape_text(Text_t text) { - // TODO: optimize for spans of non-escaped text - Text_t ret = EMPTY_TEXT; - TextIter_t state = NEW_TEXT_ITER_STATE(text); - for (int64_t i = 0; i < text.length; i++) { - uint32_t g = Text$get_main_grapheme_fast(&state, i); - if (g == '{') { - ret = Text$concat(ret, Text("{1{}")); - } else if (g == '?' || uc_is_property_quotation_mark(g) - || (uc_is_property_paired_punctuation(g) && uc_is_property_left_of_pair(g))) { - ret = Text$concat(ret, Text("{1"), Text$slice(text, I(i + 1), I(i + 1)), Text("}")); - } else { - ret = Text$concat(ret, Text$slice(text, I(i + 1), I(i + 1))); - } - } - return ret; -} - -static Text_t Pattern$as_text(const void *obj, bool colorize, const TypeInfo_t *info) { - (void)info; - if (!obj) return Text("Pattern"); - - Text_t pat = *(Text_t *)obj; - Text_t quote = Pattern$has(pat, Text("/")) && !Pattern$has(pat, Text("|")) ? Text("|") : Text("/"); - return Text$concat(colorize ? Text("\x1b[1m$\033[m") : Text("$"), Text$quoted(pat, colorize, quote)); -} diff --git a/lib/patterns/patterns.tm b/lib/patterns/patterns.tm deleted file mode 100644 index c5444b86..00000000 --- a/lib/patterns/patterns.tm +++ /dev/null @@ -1,66 +0,0 @@ -use ./patterns.c - -struct PatternMatch(text:Text, index:Int, captures:[Text]) - -lang Pat - convert(text:Text -> Pat) - return C_code:Pat(Pattern$escape_text(@text)) - - convert(n:Int -> Pat) - return Pat.from_text("$n") - -extend Text - func matching_pattern(text:Text, pattern:Pat, pos:Int = 1 -> PatternMatch?) - result : PatternMatch - if C_code:Bool(Pattern$match_at(@text, @pattern, @pos, (void*)&@result)) - return result - return none - - func matches_pattern(text:Text, pattern:Pat -> Bool) - return C_code:Bool(Pattern$matches(@text, @pattern)) - - func pattern_captures(text:Text, pattern:Pat -> [Text]?) - return C_code:[Text]?(Pattern$captures(@text, @pattern)) - - func replace_pattern(text:Text, pattern:Pat, replacement:Text, backref="@", recursive=yes -> Text) - return C_code:Text(Pattern$replace(@text, @pattern, @replacement, @backref, @recursive)) - - func translate_patterns(text:Text, replacements:{Pat=Text}, backref="@", recursive=yes -> Text) - return C_code:Text(Pattern$replace_all(@text, @replacements, @backref, @recursive)) - - func has_pattern(text:Text, pattern:Pat -> Bool) - return C_code:Bool(Pattern$has(@text, @pattern)) - - func find_patterns(text:Text, pattern:Pat -> [PatternMatch]) - return C_code:[PatternMatch](Pattern$find_all(@text, @pattern)) - - func by_pattern(text:Text, pattern:Pat -> func(->PatternMatch?)) - return C_code:func(->PatternMatch?)(Pattern$by_match(@text, @pattern)) - - func each_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch), recursive=yes) - C_code { Pattern$each(@text, @pattern, @fn, @recursive); } - - func map_pattern(text:Text, pattern:Pat, fn:func(m:PatternMatch -> Text), recursive=yes -> Text) - return C_code:Text(Pattern$map(@text, @pattern, @fn, @recursive)) - - func split_pattern(text:Text, pattern:Pat -> [Text]) - return C_code:[Text](Pattern$split(@text, @pattern)) - - func by_pattern_split(text:Text, pattern:Pat -> func(->Text?)) - return C_code:func(->Text?)(Pattern$by_split(@text, @pattern)) - - func trim_pattern(text:Text, pattern=$Pat"{space}", left=yes, right=yes -> Text) - return C_code:Text(Pattern$trim(@text, @pattern, @left, @right)) - -func main() - >> "Hello world".matching_pattern($Pat'{id}') - >> "...Hello world".matching_pattern($Pat'{id}') -# func main(pattern:Pat, input=(/dev/stdin)) -# for line in input.by_line()! -# skip if not line.has_pattern(pattern) -# pos := 1 -# for match in line.by_pattern(pattern) -# say(line.slice(pos, match.index-1), newline=no) -# say("\033[34;1m$(match.text)\033[m", newline=no) -# pos = match.index + match.text.length -# say(line.from(pos), newline=yes) diff --git a/lib/pthreads/CHANGES.md b/lib/pthreads/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/pthreads/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/pthreads/pthreads.tm b/lib/pthreads/pthreads.tm deleted file mode 100644 index 8fca6bd6..00000000 --- a/lib/pthreads/pthreads.tm +++ /dev/null @@ -1,117 +0,0 @@ -# A Posix Threads (pthreads) wrapper -use - -struct pthread_mutex_t(; extern, opaque) - func new(->@pthread_mutex_t) - return C_code : @pthread_mutex_t( - pthread_mutex_t *mutex = GC_MALLOC(sizeof(pthread_mutex_t)); - pthread_mutex_init(mutex, NULL); - GC_register_finalizer(mutex, (void*)pthread_mutex_destroy, NULL, NULL, NULL); - mutex - ) - - func lock(m:&pthread_mutex_t) - fail("Failed to lock mutex") unless C_code:Int32(pthread_mutex_lock(@m)) == 0 - - func unlock(m:&pthread_mutex_t) - fail("Failed to unlock mutex") unless C_code:Int32(pthread_mutex_unlock(@m)) == 0 - -struct pthread_cond_t(; extern, opaque) - func new(->@pthread_cond_t) - return C_code : @pthread_cond_t( - pthread_cond_t *cond = GC_MALLOC(sizeof(pthread_cond_t)); - pthread_cond_init(cond, NULL); - GC_register_finalizer(cond, (void*)pthread_cond_destroy, NULL, NULL, NULL); - cond - ) - - func wait(cond:&pthread_cond_t, mutex:&pthread_mutex_t) - fail("Failed to wait on condition") unless C_code:Int32(pthread_cond_wait(@cond, @mutex)) == 0 - - func signal(cond:&pthread_cond_t) - fail("Failed to signal pthread_cond_t") unless C_code:Int32(pthread_cond_signal(@cond)) == 0 - - func broadcast(cond:&pthread_cond_t) - fail("Failed to broadcast pthread_cond_t") unless C_code:Int32(pthread_cond_broadcast(@cond)) == 0 - -struct pthread_rwlock_t(; extern, opaque) - func new(->@pthread_rwlock_t) - return C_code : @pthread_rwlock_t ( - pthread_rwlock_t *lock = GC_MALLOC(sizeof(pthread_rwlock_t)); - pthread_rwlock_init(lock, NULL); - GC_register_finalizer(lock, (void*)pthread_rwlock_destroy, NULL, NULL, NULL); - lock - ) - - func read_lock(lock:&pthread_rwlock_t) - C_code { pthread_rwlock_rdlock(@lock); } - - func write_lock(lock:&pthread_rwlock_t) - C_code { pthread_rwlock_wrlock(@lock); } - - func unlock(lock:&pthread_rwlock_t) - C_code { pthread_rwlock_unlock(@lock); } - -struct pthread_t(; extern, opaque) - func new(fn:func() -> @pthread_t) - return C_code:@pthread_t( - pthread_t *thread = GC_MALLOC(sizeof(pthread_t)); - pthread_create(thread, NULL, @fn.fn, @fn.userdata); - thread - ) - - func join(p:pthread_t) C_code { pthread_join(@p, NULL); } - func cancel(p:pthread_t) C_code { pthread_cancel(@p); } - func detatch(p:pthread_t) C_code { pthread_detach(@p); } - -struct IntQueue(_queue:@[Int], _mutex:@pthread_mutex_t, _cond:@pthread_cond_t) - func new(initial:[Int]=[] -> IntQueue) - return IntQueue(@initial, pthread_mutex_t.new(), pthread_cond_t.new()) - - func give(q:IntQueue, n:Int) - do q._mutex.lock() - q._queue.insert(n) - q._mutex.unlock() - q._cond.signal() - - func take(q:IntQueue -> Int) - do q._mutex.lock() - n := q._queue.pop(1) - while not n - q._cond.wait(q._mutex) - n = q._queue.pop(1) - q._mutex.unlock() - return n! - -func main() - jobs := IntQueue.new() - results := IntQueue.new() - - say_mutex := pthread_mutex_t.new() - announce := func(speaker:Text, text:Text) - do say_mutex.lock() - say("\[2][$speaker]\[] $text") - say_mutex.unlock() - - worker := pthread_t.new(func() - say("I'm in the thread!") - repeat - announce("worker", "waiting for job") - job := jobs.take() - result := job * 10 - announce("worker", "Jobbing $job into $result") - results.give(result) - announce("worker", "Signaled $result") - ) - - for i in 10 - announce("boss", "Pushing job $i") - jobs.give(i) - announce("boss", "Gave job $i") - - for i in 10 - announce("boss", "Getting result...") - result := results.take() - announce("boss", "Got result $result") - - >> worker.cancel() diff --git a/lib/random/CHANGES.md b/lib/random/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/random/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/random/README.md b/lib/random/README.md deleted file mode 100644 index 183b9d0b..00000000 --- a/lib/random/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# Random Number Generators (RNG) - -This library provides an `RNG` type (Random Number Generator). This type -represents a self-contained piece of data that encapsulates the state of a -relatively fast and relatively secure pseudo-random number generator. The -current implementation is based on the [ChaCha20 stream -cipher,](https://en.wikipedia.org/wiki/Salsa20#ChaCha_variant) inspired by -[`arc4random` in OpenBSD.](https://man.openbsd.org/arc4random.3) - -An `RNG` object can be used for deterministic, repeatable generation of -pseudorandom numbers (for example, to be used in a video game for creating -seeded levels). The default random number generator for Tomo is called `random` -and is, by default, initialized with random data from the operating system when -a Tomo program launches. - -## RNG Functions - -This documentation provides details on RNG functions available in the API. -Lists also have some methods which use RNG values: -`list.shuffle()`, `list.shuffled()`, `list.random()`, and `list.sample()`. - -- [`func bool(rng: RNG, p: Num = 0.5 -> Bool)`](#bool) -- [`func byte(rng: RNG -> Byte)`](#byte) -- [`func bytes(rng: RNG, count: Int -> [Byte])`](#bytes) -- [`func copy(rng: RNG -> RNG)`](#copy) -- [`func int(rng: RNG, min: Int, max: Int -> Int)`](#int`, `int64`, `int32`, `int16`, `int8) -- [`func new(seed: [Byte] = (/dev/urandom).read_bytes(40)! -> RNG)`](#new) -- [`func num(rng: RNG, min: Num = 0.0, max: Num = 1.0 -> Num)`](#num`, `num32) - -------------- - -### `bool` -Generate a random boolean value with a given probability. - -```tomo -func bool(rng: RNG, p: Num = 0.5 -> Bool) -``` - -- `rng`: The random number generator to use. -- `p`: The probability of returning a `yes` value. Values less than zero and - `NaN` values are treated as equal to zero and values larger than zero are - treated as equal to one. - -**Returns:** -`yes` with probability `p` and `no` with probability `1-p`. - -**Example:** -```tomo ->> random.bool() -= no ->> random.bool(1.0) -= yes -``` - ---- - -### `byte` -Generate a random byte with uniform probability. - -```tomo -func byte(rng: RNG -> Byte) -``` - -- `rng`: The random number generator to use. - -**Returns:** -A random byte (0-255). - -**Example:** -```tomo ->> random.byte() -= 103[B] -``` - ---- - -### `bytes` -Generate a list of uniformly random bytes with the given length. - -```tomo -func bytes(rng: RNG, count: Int -> [Byte]) -``` - -- `rng`: The random number generator to use. -- `count`: The number of random bytes to return. - -**Returns:** -A list of length `count` random bytes with uniform random distribution (0-255). - -**Example:** -```tomo ->> random.bytes(4) -= [135[B], 169[B], 103[B], 212[B]] -``` - ---- - -### `copy` -Return a copy of a random number generator. This copy will be a parallel version of -the given RNG with its own internal state. - -```tomo -func copy(rng: RNG -> RNG) -``` - -- `rng`: The random number generator to copy. - -**Returns:** -A copy of the given RNG. - -**Example:** -```tomo ->> rng := RNG.new([]) ->> copy := rng.copy() - ->> rng.bytes(10) -= [224[B], 102[B], 190[B], 59[B], 251[B], 50[B], 217[B], 170[B], 15[B], 221[B]] - -# The copy runs in parallel to the original RNG: ->> copy.bytes(10) -= [224[B], 102[B], 190[B], 59[B], 251[B], 50[B], 217[B], 170[B], 15[B], 221[B]] -``` - ---- - -### `int`, `int64`, `int32`, `int16`, `int8` -Generate a random integer value with the given range. - -```tomo -func int(rng: RNG, min: Int, max: Int -> Int) -func int64(rng: RNG, min: Int64 = Int64.min, max: Int64 = Int64.max -> Int) -func int32(rng: RNG, min: Int32 = Int32.min, max: Int32 = Int32.max -> Int) -func int16(rng: RNG, min: Int16 = Int16.min, max: Int16 = Int16.max -> Int) -func int8(rng: RNG, min: Int8 = Int8.min, max: Int8 = Int8.max -> Int) -``` - -- `rng`: The random number generator to use. -- `min`: The minimum value to be returned. -- `max`: The maximum value to be returned. - -**Returns:** -An integer uniformly chosen from the range `[min, max]` (inclusive). If `min` -is greater than `max`, an error will be raised. - -**Example:** -```tomo ->> random.int(1, 10) -= 8 -``` - ---- - -### `new` -Return a new random number generator. - -```tomo -func new(seed: [Byte] = (/dev/urandom).read_bytes(40)! -> RNG) -``` - -- `seed`: The seed use for the random number generator. A seed length of 40 - bytes is recommended. Seed lengths of less than 40 bytes are padded with - zeroes. - -**Returns:** -A new random number generator. - -**Example:** -```tomo ->> my_rng := RNG.new([1[B], 2[B], 3[B], 4[B]]) ->> my_rng.bool() -= yes -``` - ---- - -### `num`, `num32` -Generate a random floating point value with the given range. - -```tomo -func num(rng: RNG, min: Num = 0.0, max: Num = 1.0 -> Int) -func num32(rng: RNG, min: Num = 0.0_f32, max: Num = 1.0_f32 -> Int) -``` - -- `rng`: The random number generator to use. -- `min`: The minimum value to be returned. -- `max`: The maximum value to be returned. - -**Returns:** -A floating point number uniformly chosen from the range `[min, max]` -(inclusive). If `min` is greater than `max`, an error will be raised. - -**Example:** -```tomo ->> random.num(1, 10) -= 9.512830439975572 -``` diff --git a/lib/random/chacha.h b/lib/random/chacha.h deleted file mode 100644 index 22015819..00000000 --- a/lib/random/chacha.h +++ /dev/null @@ -1,192 +0,0 @@ -// The ChaCha stream cipher used for pseudorandom number generation - -#pragma once -/* -chacha-merged.c version 20080118 -D. J. Bernstein -Public domain. -*/ - -/* $OpenBSD: chacha_private.h,v 1.3 2022/02/28 21:56:29 dtucker Exp $ */ -/* Tomo: chacha.h,v 1.0 2024/11/03 Bruce Hill */ - -typedef unsigned char u8; -typedef unsigned int u32; - -typedef struct { - u32 input[16]; /* could be compressed */ -} chacha_ctx; - -#define KEYSZ 32 -#define IVSZ 8 - -#define U8C(v) (v##U) -#define U32C(v) (v##U) - -#define U8V(v) ((u8)(v) & U8C(0xFF)) -#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF)) - -#define ROTL32(v, n) (U32V((v) << (n)) | ((v) >> (32 - (n)))) - -#define U8TO32_LITTLE(p) (((u32)((p)[0])) | ((u32)((p)[1]) << 8) | ((u32)((p)[2]) << 16) | ((u32)((p)[3]) << 24)) - -#define U32TO8_LITTLE(p, v) \ - do { \ - (p)[0] = U8V((v)); \ - (p)[1] = U8V((v) >> 8); \ - (p)[2] = U8V((v) >> 16); \ - (p)[3] = U8V((v) >> 24); \ - } while (0) - -#define ROTATE(v, c) (ROTL32(v, c)) -#define XOR(v, w) ((v) ^ (w)) -#define PLUS(v, w) (U32V((v) + (w))) -#define PLUSONE(v) (PLUS((v), 1)) - -#define QUARTERROUND(a, b, c, d) \ - a = PLUS(a, b); \ - d = ROTATE(XOR(d, a), 16); \ - c = PLUS(c, d); \ - b = ROTATE(XOR(b, c), 12); \ - a = PLUS(a, b); \ - d = ROTATE(XOR(d, a), 8); \ - c = PLUS(c, d); \ - b = ROTATE(XOR(b, c), 7); - -static const char sigma[16] = "expand 32-byte k"; - -static void chacha_keysetup(chacha_ctx *chacha, const u8 *k) { - chacha->input[0] = U8TO32_LITTLE(sigma + 0); - chacha->input[1] = U8TO32_LITTLE(sigma + 4); - chacha->input[2] = U8TO32_LITTLE(sigma + 8); - chacha->input[3] = U8TO32_LITTLE(sigma + 12); - chacha->input[4] = U8TO32_LITTLE(k + 0); - chacha->input[5] = U8TO32_LITTLE(k + 4); - chacha->input[6] = U8TO32_LITTLE(k + 8); - chacha->input[7] = U8TO32_LITTLE(k + 12); - chacha->input[8] = U8TO32_LITTLE(k + 16); - chacha->input[9] = U8TO32_LITTLE(k + 20); - chacha->input[10] = U8TO32_LITTLE(k + 24); - chacha->input[11] = U8TO32_LITTLE(k + 28); -} - -static void chacha_ivsetup(chacha_ctx *chacha, const u8 *iv) { - chacha->input[12] = 0; - chacha->input[13] = 0; - chacha->input[14] = U8TO32_LITTLE(iv + 0); - chacha->input[15] = U8TO32_LITTLE(iv + 4); -} - -static void chacha_encrypt_bytes(chacha_ctx *chacha, const u8 *m, u8 *c, u32 bytes) { - u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; - u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; - u8 *ctarget = (u8 *)0; - u8 tmp[64]; - unsigned int i; - - if (!bytes) return; - - j0 = chacha->input[0]; - j1 = chacha->input[1]; - j2 = chacha->input[2]; - j3 = chacha->input[3]; - j4 = chacha->input[4]; - j5 = chacha->input[5]; - j6 = chacha->input[6]; - j7 = chacha->input[7]; - j8 = chacha->input[8]; - j9 = chacha->input[9]; - j10 = chacha->input[10]; - j11 = chacha->input[11]; - j12 = chacha->input[12]; - j13 = chacha->input[13]; - j14 = chacha->input[14]; - j15 = chacha->input[15]; - - for (;;) { - if (bytes < 64) { - for (i = 0; i < bytes; ++i) - tmp[i] = m[i]; - m = tmp; - ctarget = c; - c = tmp; - } - x0 = j0; - x1 = j1; - x2 = j2; - x3 = j3; - x4 = j4; - x5 = j5; - x6 = j6; - x7 = j7; - x8 = j8; - x9 = j9; - x10 = j10; - x11 = j11; - x12 = j12; - x13 = j13; - x14 = j14; - x15 = j15; - for (i = 20; i > 0; i -= 2) { - QUARTERROUND(x0, x4, x8, x12) - QUARTERROUND(x1, x5, x9, x13) - QUARTERROUND(x2, x6, x10, x14) - QUARTERROUND(x3, x7, x11, x15) - QUARTERROUND(x0, x5, x10, x15) - QUARTERROUND(x1, x6, x11, x12) - QUARTERROUND(x2, x7, x8, x13) - QUARTERROUND(x3, x4, x9, x14) - } - x0 = PLUS(x0, j0); - x1 = PLUS(x1, j1); - x2 = PLUS(x2, j2); - x3 = PLUS(x3, j3); - x4 = PLUS(x4, j4); - x5 = PLUS(x5, j5); - x6 = PLUS(x6, j6); - x7 = PLUS(x7, j7); - x8 = PLUS(x8, j8); - x9 = PLUS(x9, j9); - x10 = PLUS(x10, j10); - x11 = PLUS(x11, j11); - x12 = PLUS(x12, j12); - x13 = PLUS(x13, j13); - x14 = PLUS(x14, j14); - x15 = PLUS(x15, j15); - - j12 = PLUSONE(j12); - if (!j12) { - j13 = PLUSONE(j13); - /* stopping at 2^70 bytes per nonce is user's responsibility */ - } - - U32TO8_LITTLE(c + 0, x0); - U32TO8_LITTLE(c + 4, x1); - U32TO8_LITTLE(c + 8, x2); - U32TO8_LITTLE(c + 12, x3); - U32TO8_LITTLE(c + 16, x4); - U32TO8_LITTLE(c + 20, x5); - U32TO8_LITTLE(c + 24, x6); - U32TO8_LITTLE(c + 28, x7); - U32TO8_LITTLE(c + 32, x8); - U32TO8_LITTLE(c + 36, x9); - U32TO8_LITTLE(c + 40, x10); - U32TO8_LITTLE(c + 44, x11); - U32TO8_LITTLE(c + 48, x12); - U32TO8_LITTLE(c + 52, x13); - U32TO8_LITTLE(c + 56, x14); - U32TO8_LITTLE(c + 60, x15); - - if (bytes <= 64) { - if (bytes < 64) { - for (i = 0; i < bytes; ++i) - ctarget[i] = c[i]; - } - chacha->input[12] = j12; - chacha->input[13] = j13; - return; - } - bytes -= 64; - c += 64; - } -} diff --git a/lib/random/random.tm b/lib/random/random.tm deleted file mode 100644 index 107fad66..00000000 --- a/lib/random/random.tm +++ /dev/null @@ -1,234 +0,0 @@ -# Random Number Generator (RNG) implementation based on ChaCha - -use -use ./sysrandom.h -use ./chacha.h - -struct chacha_ctx(j0,j1,j2,j3,j4,j5,j6,j7,j8,j9,j10,j11,j12,j13,j14,j15:Int32; extern, secret) - func from_seed(seed:[Byte]=[] -> chacha_ctx) - return C_code:chacha_ctx( - chacha_ctx ctx; - uint8_t seed_bytes[KEYSZ + IVSZ] = {}; - for (int64_t i = 0; i < (int64_t)sizeof(seed_bytes); i++) - seed_bytes[i] = i < @seed.length ? *(uint8_t*)(@seed.data + i*@seed.stride) : 0; - chacha_keysetup(&ctx, seed_bytes); - chacha_ivsetup(&ctx, seed_bytes + KEYSZ); - ctx - ) - -random := RandomNumberGenerator.new() - -func _os_random_bytes(count:Int64 -> [Byte]) - return C_code:[Byte]( - uint8_t *random_bytes = GC_MALLOC_ATOMIC(@count); - assert(getrandom(random_bytes, (size_t)@count, 0) == (size_t)@count); - (List_t){.length=@count, .data=random_bytes, .stride=1, .atomic=1} - ) -struct RandomNumberGenerator(_chacha:chacha_ctx, _random_bytes:[Byte]=[]; secret) - func new(seed:[Byte]?=none, -> @RandomNumberGenerator) - ctx := chacha_ctx.from_seed(seed or _os_random_bytes(40)) - return @RandomNumberGenerator(ctx, []) - - func _rekey(rng:&RandomNumberGenerator) - rng._random_bytes = C_code:[Byte]( - Byte_t new_keystream[KEYSZ + IVSZ] = {}; - // Fill the buffer with the keystream - chacha_encrypt_bytes(&@rng->_chacha, new_keystream, new_keystream, sizeof(new_keystream)); - // Immediately reinitialize for backtracking resistance - chacha_keysetup(&@rng->_chacha, new_keystream); - chacha_ivsetup(&@rng->_chacha, new_keystream + KEYSZ); - List_t new_bytes = (List_t){.data=GC_MALLOC_ATOMIC(1024), .length=1024, .stride=1, .atomic=1}; - memset(new_bytes.data, 0, new_bytes.length); - chacha_encrypt_bytes(&@rng->_chacha, new_bytes.data, new_bytes.data, new_bytes.length); - new_bytes - ) - - func _fill_bytes(rng:&RandomNumberGenerator, dest:&Memory, needed:Int64) - C_code { - while (@needed > 0) { - if (@rng->_random_bytes.length == 0) - @(rng._rekey()); - - assert(@rng->_random_bytes.stride == 1); - - int64_t batch_size = MIN(@needed, @rng->_random_bytes.length); - uint8_t *batch_src = @rng->_random_bytes.data; - memcpy(@dest, batch_src, batch_size); - memset(batch_src, 0, batch_size); - @rng->_random_bytes.data += batch_size; - @rng->_random_bytes.length -= batch_size; - @dest += batch_size; - @needed -= batch_size; - } - } - - func bytes(rng:&RandomNumberGenerator, count:Int -> [Byte]) - count64 := Int64(count) - buf := C_code:@Memory(GC_MALLOC_ATOMIC(@count64)) - rng._fill_bytes(buf, count64) - return C_code:[Byte]((List_t){.data=@buf, .stride=1, .atomic=1, .length=@count64}) - - func byte(rng:&RandomNumberGenerator -> Byte) - byte : &Byte - rng._fill_bytes(byte, 1) - return byte[] - - func bool(rng:&RandomNumberGenerator, probability=0.5 -> Bool) - if probability == 0.5 - return rng.byte() < 0x80 - else - return rng.num(0., 1.) < 0.5 - - func int64(rng:&RandomNumberGenerator, min=Int64.min, max=Int64.max -> Int64) - fail("Random minimum value $min is larger than the maximum value $max") if min > max - return min if min == max - random_int64 : &Int64 - rng._fill_bytes(random_int64, 8) - if min == Int64.min and max == Int64.max - return random_int64 - - return C_code:Int64( - uint64_t range = (uint64_t)@max - (uint64_t)@min + 1; - uint64_t min_r = -range % range; - uint64_t r; - @random_int64 = (int64_t*)&r; - for (;;) { - @(rng._fill_bytes(random_int64, 8)); - if (r >= min_r) break; - } - (int64_t)((uint64_t)@min + (r % range)) - ) - - func int32(rng:&RandomNumberGenerator, min=Int32.min, max=Int32.max -> Int32) - fail("Random minimum value $min is larger than the maximum value $max") if min > max - return min if min == max - random_int32 : &Int32 - rng._fill_bytes(random_int32, 8) - if min == Int32.min and max == Int32.max - return random_int32 - - return C_code:Int32( - uint32_t range = (uint32_t)@max - (uint32_t)@min + 1; - uint32_t min_r = -range % range; - uint32_t r; - @random_int32 = (int32_t*)&r; - for (;;) { - @(rng._fill_bytes(random_int32, 8)); - if (r >= min_r) break; - } - (int32_t)((uint32_t)@min + (r % range)) - ) - - func int16(rng:&RandomNumberGenerator, min=Int16.min, max=Int16.max -> Int16) - fail("Random minimum value $min is larger than the maximum value $max") if min > max - return min if min == max - random_int16 : &Int16 - rng._fill_bytes(random_int16, 8) - if min == Int16.min and max == Int16.max - return random_int16 - - return C_code:Int16( - uint16_t range = (uint16_t)@max - (uint16_t)@min + 1; - uint16_t min_r = -range % range; - uint16_t r; - @random_int16 = (int16_t*)&r; - for (;;) { - @(rng._fill_bytes(random_int16, 8)); - if (r >= min_r) break; - } - (int16_t)((uint16_t)@min + (r % range)) - ) - - func int8(rng:&RandomNumberGenerator, min=Int8.min, max=Int8.max -> Int8) - fail("Random minimum value $min is larger than the maximum value $max") if min > max - return min if min == max - random_int8 : &Int8 - rng._fill_bytes(random_int8, 8) - if min == Int8.min and max == Int8.max - return random_int8 - - return C_code:Int8( - uint8_t range = (uint8_t)@max - (uint8_t)@min + 1; - uint8_t min_r = -range % range; - uint8_t r; - @random_int8 = (int8_t*)&r; - for (;;) { - @(rng._fill_bytes(random_int8, 8)); - if (r >= min_r) break; - } - (int8_t)((uint8_t)@min + (r % range)) - ) - - func num(rng:&RandomNumberGenerator, min=0., max=1. -> Num) - num_buf : &Num - return C_code:Num( - if (@min > @max) fail("Random minimum value (", @min, ") is larger than the maximum value (", @max, ")"); - if (@min == @max) return @min; - - union { - Num_t num; - uint64_t bits; - } r = {.bits=0}, one = {.num=1.0}; - @num_buf = &r.num; - @(rng._fill_bytes(num_buf, 8)); - - // Set r.num to 1. - r.bits &= ~(0xFFFULL << 52); - r.bits |= (one.bits & (0xFFFULL << 52)); - - r.num -= 1.0; - - (@min == 0.0 && @max == 1.0) ? r.num : ((1.0-r.num)*@min + r.num*@max) - ) - - func num32(rng:&RandomNumberGenerator, min=Num32(0.), max=Num32(1.) -> Num32) - return Num32(rng.num(Num(min), Num(max))) - - func int(rng:&RandomNumberGenerator, min:Int, max:Int -> Int) - return C_code:Int( - if (likely(((@min.small & @max.small) & 1) != 0)) { - int32_t r = @(rng.int32(Int32(min), Int32(max))); - return I_small(r); - } - - int32_t cmp = @(min <> max); - if (cmp > 0) - fail("Random minimum value (", @min, ") is larger than the maximum value (", @max, ")"); - if (cmp == 0) return @min; - - mpz_t range_size; - mpz_init_set_int(range_size, @max); - if (@min.small & 1) { - mpz_t min_mpz; - mpz_init_set_si(min_mpz, @min.small >> 2); - mpz_sub(range_size, range_size, min_mpz); - } else { - mpz_sub(range_size, range_size, *@min.big); - } - - gmp_randstate_t gmp_rng; - gmp_randinit_default(gmp_rng); - int64_t seed = @(rng.int64()); - gmp_randseed_ui(gmp_rng, (unsigned long)seed); - - mpz_t r; - mpz_init(r); - mpz_urandomm(r, gmp_rng, range_size); - - gmp_randclear(gmp_rng); - Int$plus(@min, Int$from_mpz(r)) - ) - - -func main() - >> rng := RandomNumberGenerator.new() - >> rng.num() - >> rng.num() - >> rng.num() - >> rng.num(0, 100) - >> rng.byte() - >> rng.bytes(20) - # >> rng.int(1, 100) - # >> rng.int(1, 100) - # >> rng.int(1, 100) - # >> rng.int(1, 100) diff --git a/lib/random/sysrandom.h b/lib/random/sysrandom.h deleted file mode 100644 index 3d6b800f..00000000 --- a/lib/random/sysrandom.h +++ /dev/null @@ -1,17 +0,0 @@ -// Logic for using system random numbers - -#pragma once - -#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) -#include -static ssize_t getrandom(void *buf, size_t buflen, unsigned int flags) { - (void)flags; - arc4random_buf(buf, buflen); - return buflen; -} -#elif defined(__linux__) -// Use getrandom() -#include -#else -#error "Unsupported platform for secure random number generation" -#endif diff --git a/lib/shell/CHANGES.md b/lib/shell/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/shell/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/shell/README.md b/lib/shell/README.md deleted file mode 100644 index d4bcd42e..00000000 --- a/lib/shell/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Shell - -This module defines a `lang` for running shell scripts: - -```tomo -use shell_v1.0 - ->> $Shell" - seq 5 - echo DONE -":get_output() -= "1$\n2$\n3$\n4$\n5$\nDONE" -``` diff --git a/lib/shell/shell.tm b/lib/shell/shell.tm deleted file mode 100644 index f9476161..00000000 --- a/lib/shell/shell.tm +++ /dev/null @@ -1,44 +0,0 @@ -use commands_v1.0 - -lang Shell - convert(text:Text -> Shell) - return Shell.from_text("'" ++ text.replace($/'/, `'"'"'`) ++ "'") - - convert(texts:[Text] -> Shell) - return Shell.from_text(" ".join([Shell(t).text for t in texts])) - - convert(path:Path -> Shell) - return Shell(Text(path.expand_home())) - - convert(paths:[Path] -> Shell) - return Shell.from_text(" ".join([Shell(Text(p)).text for p in paths])) - - convert(n:Int -> Shell) return Shell.from_text(Text(n)) - convert(n:Int64 -> Shell) return Shell.from_text(Text(n)) - convert(n:Int32 -> Shell) return Shell.from_text(Text(n)) - convert(n:Int16 -> Shell) return Shell.from_text(Text(n)) - convert(n:Int8 -> Shell) return Shell.from_text(Text(n)) - convert(n:Num -> Shell) return Shell.from_text(Text(n)) - convert(n:Num32 -> Shell) return Shell.from_text(Text(n)) - - func command(shell:Shell -> Command) - return Command("sh", ["-c", shell.text]) - - func result(shell:Shell, input="", input_bytes:[Byte]=[] -> ProgramResult) - return shell.command().result(input=input, input_bytes=input_bytes) - - func run(shell:Shell -> ExitType) - return shell.command().run() - - func get_output(shell:Shell, input="", trim_newline=yes -> Text?) - return shell.command().get_output(input=input, trim_newline=trim_newline) - - func get_output_bytes(shell:Shell, input="", input_bytes:[Byte]=[] -> [Byte]?) - return shell.command().get_output_bytes(input=input, input_bytes=input_bytes) - - func by_line(shell:Shell -> func(->Text?)?) - return shell.command().by_line() - -func main(command:Shell) - for line in command.by_line()! - >> line diff --git a/lib/time/CHANGES.md b/lib/time/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/time/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/time/README.md b/lib/time/README.md deleted file mode 100644 index 55f725f1..00000000 --- a/lib/time/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Time - -This is a Tomo library for working with dates and times. The `Time` type that -is provided is a date-and-time datatype that refers to a specific moment in -time. diff --git a/lib/time/time.tm b/lib/time/time.tm deleted file mode 100644 index d26d7218..00000000 --- a/lib/time/time.tm +++ /dev/null @@ -1,214 +0,0 @@ -# Time - a module for dealing with dates and times -use -use ./time_defs.h - -enum Weekday(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday) - -struct TimeInfo(year,month,day,hour,minute,second,nanosecond:Int, weekday:Weekday, day_of_year:Int, timezone:Text) - -struct Time(tv_sec:Int64, tv_usec:Int64; extern) - func now(->Time) - return C_code : Time ( - struct timespec ts; - if (clock_gettime(CLOCK_REALTIME, &ts) != 0) - fail("Couldn't get the time!"); - (Time){.tv_sec=ts.tv_sec, .tv_usec=ts.tv_nsec/1000} - ) - - func local_timezone(->Text) - C_code { - if (_local_timezone.length < 0) { - static char buf[PATH_MAX]; - ssize_t len = readlink("/etc/localtime", buf, sizeof(buf)); - if (len < 0) - fail("Could not get local tz!"); - - char *zoneinfo = strstr(buf, "/zoneinfo/"); - if (zoneinfo) - _local_timezone = Text$from_str(zoneinfo + strlen("/zoneinfo/")); - else - fail("Could not resolve local tz!"); - } - } - return C_code:Text(_local_timezone) - - func set_local_timezone(timezone:Text) - C_code { - setenv("TZ", @(CString(timezone)), 1); - _local_timezone = @timezone; - tzset(); - } - - func format(t:Time, format="%c", timezone=Time.local_timezone() -> Text) - return C_code : Text ( - struct tm result; - time_t time = @t.tv_sec; - struct tm *final_info; - WITH_TIMEZONE(@timezone, final_info = localtime_r(&time, &result)); - static char buf[256]; - size_t len = strftime(buf, sizeof(buf), String(@format), final_info); - Text$from_strn(buf, len) - ) - - func new(year,month,day:Int, hour=0, minute=0, second=0.0, timezone=Time.local_timezone() -> Time) - return C_code : Time( - struct tm info = { - .tm_min=Int32$from_int(@minute, false), - .tm_hour=Int32$from_int(@hour, false), - .tm_mday=Int32$from_int(@day, false), - .tm_mon=Int32$from_int(@month, false) - 1, - .tm_year=Int32$from_int(@year, false) - 1900, - .tm_isdst=-1, - }; - - time_t t; - WITH_TIMEZONE(@timezone, t = mktime(&info)); - (Time){.tv_sec=t + (time_t)@second, .tv_usec=(suseconds_t)(fmod(@second, 1.0) * 1e9)} - ) - - func unix_timestamp(t:Time -> Int64) - return C_code:Int64((int64_t)@t.tv_sec) - - func from_unix_timestamp(timestamp:Int64 -> Time) - return C_code:Time((Time){.tv_sec=@timestamp};) - - func seconds_till(t:Time, target:Time -> Num) - seconds := Num(target.tv_sec - t.tv_sec) - seconds += 1e-9*Num(target.tv_usec - t.tv_usec) - return seconds - - func minutes_till(t:Time, target:Time -> Num) - return t.seconds_till(target)/60. - - func hours_till(t:Time, target:Time -> Num) - return t.seconds_till(target)/3600. - - func relative(t:Time, relative_to=Time.now(), timezone=Time.local_timezone() -> Text) - C_code { - struct tm info = {}; - struct tm relative_info = {}; - WITH_TIMEZONE(@timezone, { - localtime_r(&@t.tv_sec, &info); - localtime_r(&@relative_to.tv_sec, &relative_info); - }); - double second_diff = @(relative_to.seconds_till(t)); - if (info.tm_year != relative_info.tm_year && fabs(second_diff) > 365.*24.*60.*60.) - return num_format((long)info.tm_year - (long)relative_info.tm_year, "year"); - else if (info.tm_mon != relative_info.tm_mon && fabs(second_diff) > 31.*24.*60.*60.) - return num_format(12*((long)info.tm_year - (long)relative_info.tm_year) + (long)info.tm_mon - (long)relative_info.tm_mon, "month"); - else if (info.tm_yday != relative_info.tm_yday && fabs(second_diff) > 24.*60.*60.) - return num_format(round(second_diff/(24.*60.*60.)), "day"); - else if (info.tm_hour != relative_info.tm_hour && fabs(second_diff) > 60.*60.) - return num_format(round(second_diff/(60.*60.)), "hour"); - else if (info.tm_min != relative_info.tm_min && fabs(second_diff) > 60.) - return num_format(round(second_diff/(60.)), "minute"); - else { - if (fabs(second_diff) < 1e-6) - return num_format((long)(second_diff*1e9), "nanosecond"); - else if (fabs(second_diff) < 1e-3) - return num_format((long)(second_diff*1e6), "microsecond"); - else if (fabs(second_diff) < 1.0) - return num_format((long)(second_diff*1e3), "millisecond"); - else - return num_format((long)(second_diff), "second"); - } - } - fail("Unreachable") - - func time(t:Time, seconds=no, am_pm=yes, timezone=Time.local_timezone() -> Text) - time := if seconds and am_pm - t.format("%l:%M:%S%P") - else if seconds and not am_pm - t.format("%T") - else if not seconds and am_pm - t.format("%l:%M%P") - else - t.format("%H:%M") - return time.trim() - - func date(t:Time, timezone=Time.local_timezone() -> Text) - return t.format("%F") - - func info(t:Time, timezone=Time.local_timezone() -> TimeInfo) - ret : TimeInfo - C_code { - struct tm info = {}; - WITH_TIMEZONE(@timezone, localtime_r(&@t.tv_sec, &info)); - @ret.year = I(info.tm_year + 1900); - @ret.month = I(info.tm_mon + 1); - @ret.day = I(info.tm_mday); - @ret.hour = I(info.tm_hour); - @ret.minute = I(info.tm_min); - @ret.second = I(info.tm_sec); - @ret.nanosecond = I(@t.tv_usec); - @ret.weekday = info.tm_wday + 1; - @ret.day_of_year = I(info.tm_yday); - @ret.timezone = @timezone; - } - return ret - - func after(t:Time, seconds=0.0, minutes=0.0, hours=0.0, days=0, weeks=0, months=0, years=0, timezone=Time.local_timezone() -> Time) - return C_code : Time ( - double offset = @seconds + 60.*@minutes + 3600.*@hours ; - @t.tv_sec += (time_t)offset; - - struct tm info = {}; - WITH_TIMEZONE(@timezone, localtime_r(&@t.tv_sec, &info)); - - info.tm_mday += Int32$from_int(@days, false) + 7*Int32$from_int(@weeks, false); - info.tm_mon += Int32$from_int(@months, false); - info.tm_year += Int32$from_int(@years, false); - - time_t t = mktime(&info); - (Time){ - .tv_sec=t, - .tv_usec=@t.tv_usec + (suseconds_t)(fmod(offset, 1.0) * 1e9), - } - ) - - func parse(text:Text, format="%Y-%m-%dT%H:%M:%S%z", timezone=Time.local_timezone() -> Time?) - ret : Time? - C_code { - struct tm info = {.tm_isdst=-1}; - const char *str = Text$as_c_string(@text); - const char *fmt = Text$as_c_string(@format); - if (strstr(fmt, "%Z")) - fail("The %Z specifier is not supported for time parsing!"); - - char *invalid; - WITH_TIMEZONE(@timezone, invalid = strptime(str, fmt, &info)); - if (!invalid || invalid[0] != '\0') { - @ret.is_none = true; - } else { - long offset = info.tm_gmtoff; // Need to cache this because mktime() mutates it to local tz >:( - time_t t; - WITH_TIMEZONE(@timezone, t = mktime(&info)); - @ret.value.tv_sec = t + offset - info.tm_gmtoff; - } - } - return ret - -func _run_tests() - >> Time.now().format() - >> Time.set_local_timezone("Europe/Paris") - >> Time.now().format() - >> Time.set_local_timezone("America/New_York") - >> Time.now().format() - # >> Time.now().format(timezone="Europe/Paris") - # >> Time.now().format() - # >> Time.now().format("%Y-%m-%d") - # >> Time.new(2023, 11, 5).format() - # >> Time.local_timezone() - - # >> Time.new(2023, 11, 5).seconds_till(Time.now()) - # >> Time.new(2023, 11, 5).relative() - - # >> Time.now().info() - # >> Time.now().time() - # >> Time.now().date() - - # >> Time.parse("2023-11-05 01:01", "%Y-%m-%d %H:%M") - # >> Time.parse("2023-11-05 01:01", "%Y-%m-%d %H:%M", timezone="Europe/Paris") - -func main() - _run_tests() diff --git a/lib/time/time_defs.h b/lib/time/time_defs.h deleted file mode 100644 index eac3f23a..00000000 --- a/lib/time/time_defs.h +++ /dev/null @@ -1,34 +0,0 @@ -// Some helper logic for working with times. - -#pragma once -#include -#include -#include - -typedef struct timeval Time; - -static OptionalText_t _local_timezone = NONE_TEXT; - -static INLINE Text_t num_format(long n, const char *unit) { - if (n == 0) return Text("now"); - return Text$from_str( - String((int64_t)labs(n), " ", unit, (n == -1 || n == 1) ? "" : "s", n <= 0 ? " ago" : " later")); -} - -static void set_local_timezone(Text_t tz) { - setenv("TZ", Text$as_c_string(tz), 1); - _local_timezone = tz; - tzset(); -} - -#define WITH_TIMEZONE(tz, body) \ - ({ \ - if (tz.length >= 0) { \ - OptionalText_t old_timezone = _local_timezone; \ - set_local_timezone(tz); \ - body; \ - set_local_timezone(old_timezone); \ - } else { \ - body; \ - } \ - }) diff --git a/lib/uuid/CHANGES.md b/lib/uuid/CHANGES.md deleted file mode 100644 index 42ae752c..00000000 --- a/lib/uuid/CHANGES.md +++ /dev/null @@ -1,5 +0,0 @@ -# Version History - -## v1.0 - -Initial version diff --git a/lib/uuid/uuid.tm b/lib/uuid/uuid.tm deleted file mode 100644 index f2be618e..00000000 --- a/lib/uuid/uuid.tm +++ /dev/null @@ -1,39 +0,0 @@ -use random_v1.0 -use time_v1.0 - -lang UUID - func v4(-> UUID) # Random UUID - bytes := &random.bytes(16) - bytes[7; unchecked] = 0x40 or (bytes[7; unchecked] and 0x0F) - bytes[9; unchecked] = (Byte(random.int8(0x8, 0xB)) << 4) or (bytes[9; unchecked] and 0x0F) - hex := "".join([b.hex() for b in bytes]) - uuid := "$(hex.slice(1, 8))-$(hex.slice(9, 12))-$(hex.slice(13, 16))-$(hex.slice(17, -1))" - return UUID.from_text(uuid) - - func v7(-> UUID) # Timestamp + random UUID - n := Time.now() - timestamp := n.tv_sec*1000 + n.tv_usec/1_000 - - bytes := [ - Byte((timestamp >> 40)), - Byte((timestamp >> 32)), - Byte((timestamp >> 24)), - Byte((timestamp >> 16)), - Byte((timestamp >> 8)), - Byte(timestamp), - (random.byte() and 0x0F) or 0x70, - random.byte(), - (random.byte() and 0x3F) or 0x80, - random.byte() for _ in 7, - ] - - hex := "".join([b.hex() for b in bytes]) - uuid := "$(hex.slice(1, 8))-$(hex.slice(9, 12))-$(hex.slice(13, 16))-$(hex.slice(17, -1))" - return UUID.from_text(uuid) - -enum UUIDVersion(v4, v7) -func main(version=UUIDVersion.v7) - when version is v4 - say(UUID.v4().text) - is v7 - say(UUID.v7().text) diff --git a/src/compile/headers.c b/src/compile/headers.c index be564144..d1131bde 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -177,7 +177,7 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { - if (!try_install_module(mod)) code_err(ast, "Could not find library"); + if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); } Text_t includes = EMPTY_TEXT; diff --git a/src/compile/statements.c b/src/compile/statements.c index 82a91a5e..af6a5223 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -207,7 +207,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { - if (!try_install_module(mod)) code_err(ast, "Could not find library"); + if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); } Text_t initialization = EMPTY_TEXT; diff --git a/src/modules.c b/src/modules.c index 5327f28b..6658f8a5 100644 --- a/src/modules.c +++ b/src/modules.c @@ -15,6 +15,7 @@ #include "stdlib/tables.h" #include "stdlib/text.h" #include "stdlib/types.h" +#include "stdlib/util.h" #define xsystem(...) \ ({ \ @@ -23,6 +24,33 @@ errx(1, "Failed to run command: %s", String(__VA_ARGS__)); \ }) +bool install_from_modules_ini(Path_t ini_file, bool ask_confirmation) { + OptionalClosure_t by_line = Path$by_line(ini_file); + if (by_line.fn == NULL) return false; + OptionalText_t (*next_line)(void *) = by_line.fn; + module_info_t info = {}; + for (Text_t line; (line = next_line(by_line.userdata)).length >= 0;) { + char *line_str = Text$as_c_string(line); + const char *next_section = NULL; + if (!strparse(line_str, "[", &next_section, "]")) { + if (info.name) { + if (!try_install_module(info, ask_confirmation)) return false; + } + print("Checking module ", next_section, "..."); + info = (module_info_t){.name = next_section}; + continue; + } + if (!strparse(line_str, "version=", &info.version) || !strparse(line_str, "url=", &info.url) + || !strparse(line_str, "git=", &info.git) || !strparse(line_str, "path=", &info.path) + || !strparse(line_str, "revision=", &info.revision)) + continue; + } + if (info.name) { + if (!try_install_module(info, ask_confirmation)) return false; + } + return true; +} + static void read_modules_ini(Path_t ini_file, module_info_t *info) { OptionalClosure_t by_line = Path$by_line(ini_file); if (by_line.fn == NULL) return; @@ -57,28 +85,35 @@ module_info_t get_module_info(ast_t *use) { return *info; } -bool try_install_module(module_info_t mod) { +bool try_install_module(module_info_t mod, bool ask_confirmation) { Path_t dest = Path$from_text( Texts(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", Text$from_str(mod.name), "_", Text$from_str(mod.version))); + if (Path$exists(dest)) return true; + if (mod.git) { - OptionalText_t answer = ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), - " is not installed.\nDo you want to install it from git URL ", - Text$from_str(mod.git), "? [Y/n] "), - true, true); - if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) - return false; + if (ask_confirmation) { + OptionalText_t answer = + ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), + " is not installed.\nDo you want to install it from git URL ", Text$from_str(mod.git), + "? [Y/n] "), + true, true); + if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) + return false; + } print("Installing ", mod.name, " from git..."); if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest); else xsystem("git clone --depth=1 ", mod.git, " ", dest); xsystem("tomo -L ", dest); return true; } else if (mod.url) { - OptionalText_t answer = - ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), + if (ask_confirmation) { + OptionalText_t answer = ask( + Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), " is not installed.\nDo you want to install it from URL ", Text$from_str(mod.url), "? [Y/n] "), true, true); - if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) - return false; + if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) + return false; + } print("Installing ", mod.name, " from URL..."); @@ -102,12 +137,14 @@ bool try_install_module(module_info_t mod) { Path$remove(tmpdir, true); return true; } else if (mod.path) { - OptionalText_t answer = - ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), + if (ask_confirmation) { + OptionalText_t answer = ask( + Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version), " is not installed.\nDo you want to install it from path ", Text$from_str(mod.path), "? [Y/n] "), true, true); - if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) - return false; + if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) + return false; + } print("Installing ", mod.name, " from path..."); xsystem("ln -s ", mod.path, " ", dest); diff --git a/src/modules.h b/src/modules.h index 1c3b2d8e..c36d96dd 100644 --- a/src/modules.h +++ b/src/modules.h @@ -11,4 +11,5 @@ typedef struct { } module_info_t; module_info_t get_module_info(ast_t *use); -bool try_install_module(module_info_t mod); +bool install_from_modules_ini(Path_t ini_file, bool ask_confirmation); +bool try_install_module(module_info_t mod, bool ask_confirmation); diff --git a/src/tomo.c b/src/tomo.c index b6440f10..a1f21dcf 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -260,8 +260,15 @@ int main(int argc, char *argv[]) { // This *could* be done in parallel, but there may be some dependency issues. pid_t child = fork(); if (child == 0) { - build_library(*lib); - if (should_install) install_library(*lib); + if (Text$equal_values(Path$extension(*lib, false), Text("ini"))) { + if (!install_from_modules_ini(*lib, false)) { + print("Failed to install modules from file: ", *lib); + _exit(1); + } + } else { + build_library(*lib); + if (should_install) install_library(*lib); + } _exit(0); } wait_for_child_success(child); diff --git a/src/typecheck.c b/src/typecheck.c index adb267df..80c270ef 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -189,7 +189,7 @@ static env_t *load_module(env_t *env, ast_t *module_ast) { if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { - if (!try_install_module(mod)) code_err(module_ast, "Couldn't find or install library: ", folder); + if (!try_install_module(mod, true)) code_err(module_ast, "Couldn't find or install library: ", folder); } env_t *module_env = fresh_scope(env); -- cgit v1.2.3 From 9a4491a2d51fde84823926eb56b50ca9f99008b0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 16:46:56 -0400 Subject: Add modules files to install --- modules/core.ini | 35 +++++++++++++++++++++++++++++++++++ modules/examples.ini | 27 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 modules/core.ini create mode 100644 modules/examples.ini diff --git a/modules/core.ini b/modules/core.ini new file mode 100644 index 00000000..ddad0a09 --- /dev/null +++ b/modules/core.ini @@ -0,0 +1,35 @@ +[patterns] +version=v1.1 +git=https://github.com/bruce-hill/tomo-patterns + +[base64] +version=v1.0 +git=https://github.com/bruce-hill/tomo-base64 + +[commands] +version=v1.0 +git=https://github.com/bruce-hill/tomo-commands + +[json] +version=v1.0 +git=https://github.com/bruce-hill/tomo-json + +[pthreads] +version=v1.0 +git=https://github.com/bruce-hill/tomo-pthreads + +[random] +version=v1.0 +git=https://github.com/bruce-hill/tomo-random + +[shell] +version=v1.0 +git=https://github.com/bruce-hill/tomo-shell + +[time] +version=v1.0 +git=https://github.com/bruce-hill/tomo-time + +[uuid] +version=v1.0 +git=https://github.com/bruce-hill/tomo-uuid diff --git a/modules/examples.ini b/modules/examples.ini new file mode 100644 index 00000000..20a04639 --- /dev/null +++ b/modules/examples.ini @@ -0,0 +1,27 @@ +[log] +version=v1.0 +git=https://github.com/bruce-hill/tomo-log + +[ini] +version=v1.0 +git=https://github.com/bruce-hill/tomo-ini + +[vectors] +version=v1.0 +git=https://github.com/bruce-hill/tomo-vectors + +[http] +version=v1.1 +git=https://github.com/bruce-hill/tomo-http + +[http-server] +version=v1.0 +git=https://github.com/bruce-hill/tomo-http-server + +[wrap] +version=v1.0 +git=https://github.com/bruce-hill/tomo-wrap + +[colorful] +version=v1.0 +git=https://github.com/bruce-hill/tomo-colorful -- cgit v1.2.3 From 6d48028d0fe7f4f5755f0bb9111edab57f7a7fad Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 16:53:04 -0400 Subject: Make symbolic link for local includes --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a7952628..1ee54a31 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,10 @@ TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/*.tm)) API_YAML=$(wildcard api/*.yaml) API_MD=$(patsubst %.yaml,%.md,$(API_YAML)) -all: config.mk check-c-compiler check-libs build/lib/$(LIB_FILE) build/lib/$(AR_FILE) build/bin/$(EXE_FILE) +all: config.mk check-c-compiler check-libs build/include/tomo_$(TOMO_VERSION) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) build/bin/$(EXE_FILE) + +build/include/tomo_$(TOMO_VERSION): + ln -s ../../src/stdlib $@ version: @echo $(TOMO_VERSION) -- cgit v1.2.3 From af50891b0eb51be61d0d2366875bf5b15eef5c80 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 17:31:32 -0400 Subject: Fix for import test --- Makefile | 2 +- test/_vectors.tm | 1 + test/import.tm | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 test/_vectors.tm diff --git a/Makefile b/Makefile index 1ee54a31..0fec67d1 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ EXE_FILE=tomo_$(TOMO_VERSION) COMPILER_OBJS=$(patsubst %.c,%.o,$(wildcard src/*.c src/compile/*.c src/parse/*.c)) STDLIB_OBJS=$(patsubst %.c,%.o,$(wildcard src/stdlib/*.c)) -TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/*.tm)) +TESTS=$(patsubst test/%.tm,test/results/%.tm.testresult,$(wildcard test/[!_]*.tm)) API_YAML=$(wildcard api/*.yaml) API_MD=$(patsubst %.yaml,%.md,$(API_YAML)) diff --git a/test/_vectors.tm b/test/_vectors.tm new file mode 100644 index 00000000..84646cc9 --- /dev/null +++ b/test/_vectors.tm @@ -0,0 +1 @@ +struct Vec2(x,y:Num) diff --git a/test/import.tm b/test/import.tm index 0686190b..edae08f6 100644 --- a/test/import.tm +++ b/test/import.tm @@ -1,4 +1,4 @@ -vectors := use ../examples/vectors/vectors.tm +vectors := use ./_vectors.tm use ./use_import.tm func returns_vec(->vectors.Vec2) -- cgit v1.2.3 From 081a26de86eca95ba3ee0887992cdc3d96190cce Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 17:32:42 -0400 Subject: Fixes for local-tomo so that it *actually* uses local header files, which don't need to be installed anymore. --- Makefile | 2 +- local-tomo | 5 +-- src/compile/headers.c | 2 +- src/compile/statements.c | 2 +- src/config.h | 6 ++-- src/modules.c | 4 +-- src/stdlib/stacktrace.c | 2 +- src/stdlib/stdlib.c | 3 ++ src/tomo.c | 81 ++++++++++++++++++++++++++---------------------- src/typecheck.c | 3 +- 10 files changed, 59 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index 0fec67d1..9274d828 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ O=-O3 TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print $$2; exit}' CHANGES.md) GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h") CFLAGS=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ - -DTOMO_PREFIX='"$(PREFIX)"' -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ + -DTOMO_INSTALL='"$(PREFIX)"' -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ -DTOMO_VERSION='"$(TOMO_VERSION)"' -DGIT_VERSION='"$(GIT_VERSION)"' CFLAGS_PLACEHOLDER="$$(printf '\033[2m\033[m\n')" LDLIBS=-lgc -lm -lunistring -lgmp diff --git a/local-tomo b/local-tomo index c7eaeb21..bdf1e0da 100755 --- a/local-tomo +++ b/local-tomo @@ -7,8 +7,5 @@ if [ ! -e "$here/build/bin/tomo_$version" ]; then fi PATH="$here/build/bin${PATH:+:$PATH}" \ -LD_LIBRARY_PATH="$here/build/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ -LIBRARY_PATH="$here/build/lib${LIBRARY_PATH:+:$LIBRARY_PATH}" \ -C_INCLUDE_PATH="$here/build/include${C_INCLUDE_PATH:+:$C_INCLUDE_PATH}" \ -CPATH="$here/build/include${CPATH:+:$CPATH}" \ +TOMO_PATH="$here/build" \ tomo_"$version" "$@" diff --git a/src/compile/headers.c b/src/compile/headers.c index d1131bde..6dc69f03 100644 --- a/src/compile/headers.c +++ b/src/compile/headers.c @@ -174,7 +174,7 @@ Text_t compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) module_info_t mod = get_module_info(ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/compile/statements.c b/src/compile/statements.c index af6a5223..a7c5214a 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -204,7 +204,7 @@ static Text_t _compile_statement(env_t *env, ast_t *ast) { module_info_t mod = get_module_info(ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, + if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(ast, "Could not find library"); diff --git a/src/config.h b/src/config.h index 1afbb3e5..8ee44200 100644 --- a/src/config.h +++ b/src/config.h @@ -8,10 +8,12 @@ #define GIT_VERSION "???" #endif -#ifndef TOMO_PREFIX -#define TOMO_PREFIX "/usr/local" +#ifndef TOMO_INSTALL +#define TOMO_INSTALL "/usr/local" #endif +extern const char *TOMO_PATH; + #ifndef DEFAULT_C_COMPILER #define DEFAULT_C_COMPILER "cc" #endif diff --git a/src/modules.c b/src/modules.c index 6658f8a5..fafbbf86 100644 --- a/src/modules.c +++ b/src/modules.c @@ -86,8 +86,8 @@ module_info_t get_module_info(ast_t *use) { } bool try_install_module(module_info_t mod, bool ask_confirmation) { - Path_t dest = Path$from_text( - Texts(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", Text$from_str(mod.name), "_", Text$from_str(mod.version))); + Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo_" TOMO_VERSION "/", Text$from_str(mod.name), + "_", Text$from_str(mod.version))); if (Path$exists(dest)) return true; if (mod.git) { diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c index c7ec54d3..0953e660 100644 --- a/src/stdlib/stacktrace.c +++ b/src/stdlib/stacktrace.c @@ -98,7 +98,7 @@ void print_stacktrace(FILE *out, int offset) { cwd[cwd_len++] = '/'; cwd[cwd_len] = '\0'; - const char *install_dir = TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/"; + const char *install_dir = String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/"); static void *stack[1024]; int64_t size = (int64_t)backtrace(stack, sizeof(stack) / sizeof(stack[0])); diff --git a/src/stdlib/stdlib.c b/src/stdlib/stdlib.c index bf36ca0d..5ea8cb79 100644 --- a/src/stdlib/stdlib.c +++ b/src/stdlib/stdlib.c @@ -41,6 +41,9 @@ bool USE_COLOR; public Text_t TOMO_VERSION_TEXT = Text(TOMO_VERSION); +public +const char *TOMO_PATH = TOMO_INSTALL; + static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) { (void)info, (void)userdata; assert(sig == SIGILL); diff --git a/src/tomo.c b/src/tomo.c index a1f21dcf..449bac37 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -34,6 +34,7 @@ #include "stdlib/siphash.h" #include "stdlib/tables.h" #include "stdlib/text.h" +#include "stdlib/util.h" #include "types.h" #define run_cmd(...) \ @@ -87,16 +88,13 @@ static OptionalText_t show_codegen = NONE_TEXT, " -D_BSD_SOURCE" #endif " -DGC_THREADS" - " -I'" TOMO_PREFIX "/include' -I'" TOMO_PREFIX "/lib/tomo_" TOMO_VERSION - "' -I/usr/local/include"), + " -I/usr/local/include"), ldlibs = Text("-lgc -lm -lgmp -lunistring -ltomo_" TOMO_VERSION), - ldflags = Text("-Wl,-rpath,'" TOMO_PREFIX "/lib',-rpath,/usr/local/lib" - " -L/usr/local/lib"), - optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER); + ldflags = Text(" -L/usr/local/lib"), optimization = Text("2"), cc = Text(DEFAULT_C_COMPILER); static Text_t config_summary, // This will be either "" or "sudo -u " or "doas -u " - // to allow a command to put stuff into TOMO_PREFIX as the owner + // to allow a command to put stuff into TOMO_PATH as the owner // of that directory. as_owner = Text(""); @@ -149,40 +147,48 @@ int main(int argc, char *argv[]) { #error "Unsupported platform for secure random number generation" #endif + if (getenv("TOMO_PATH")) TOMO_PATH = getenv("TOMO_PATH"); + + cflags = Texts("-I'", TOMO_PATH, "/include' -I'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "' ", cflags); + // Set up environment variables: const char *PATH = getenv("PATH"); - setenv("PATH", PATH ? String(TOMO_PREFIX "/bin:", PATH) : TOMO_PREFIX "/bin", 1); + setenv("PATH", PATH ? String(TOMO_PATH, "/bin:", PATH) : String(TOMO_PATH, "/bin"), 1); const char *LD_LIBRARY_PATH = getenv("LD_LIBRARY_PATH"); - setenv("LD_LIBRARY_PATH", LD_LIBRARY_PATH ? String(TOMO_PREFIX "/lib:", LD_LIBRARY_PATH) : TOMO_PREFIX "/lib", 1); + setenv("LD_LIBRARY_PATH", LD_LIBRARY_PATH ? String(TOMO_PATH, "/lib:", LD_LIBRARY_PATH) : String(TOMO_PATH, "/lib"), + 1); const char *LIBRARY_PATH = getenv("LIBRARY_PATH"); - setenv("LIBRARY_PATH", LIBRARY_PATH ? String(TOMO_PREFIX "/lib:", LIBRARY_PATH) : TOMO_PREFIX "/lib", 1); + setenv("LIBRARY_PATH", LIBRARY_PATH ? String(TOMO_PATH, "/lib:", LIBRARY_PATH) : String(TOMO_PATH, "/lib"), 1); const char *C_INCLUDE_PATH = getenv("C_INCLUDE_PATH"); - setenv("C_INCLUDE_PATH", C_INCLUDE_PATH ? String(TOMO_PREFIX "/include:", C_INCLUDE_PATH) : TOMO_PREFIX "/include", - 1); + setenv("C_INCLUDE_PATH", + C_INCLUDE_PATH ? String(TOMO_PATH, "/include:", C_INCLUDE_PATH) : String(TOMO_PATH, "/include"), 1); + const char *CPATH = getenv("CPATH"); + setenv("CPATH", CPATH ? String(TOMO_PATH, "/include:", CPATH) : String(TOMO_PATH, "/include"), 1); // Run a tool: if ((streq(argv[1], "-r") || streq(argv[1], "--run")) && argc >= 3) { if (strcspn(argv[2], "/;$") == strlen(argv[2])) { - const char *program = String("'" TOMO_PREFIX "'/lib/tomo_" TOMO_VERSION "/", argv[2], "/", argv[2]); + const char *program = String("'", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", argv[2], "/", argv[2]); execv(program, &argv[2]); } print_err("This is not an installed tomo program: ", argv[2]); } - Text_t usage = Text("\x1b[33;4;1mUsage:\x1b[m\n" - "\x1b[1mRun a program:\x1b[m tomo file.tm [-- args...]\n" - "\x1b[1mTranspile files:\x1b[m tomo -t file.tm...\n" - "\x1b[1mCompile object files:\x1b[m tomo -c file.tm...\n" - "\x1b[1mCompile executables:\x1b[m tomo -e file.tm...\n" - "\x1b[1mBuild libraries:\x1b[m tomo -L lib...\n" - "\x1b[1mUninstall libraries:\x1b[m tomo -u lib...\n" - "\x1b[1mOther flags:\x1b[m\n" - " --verbose|-v: verbose output\n" - " --quiet|-q: quiet output\n" - " --parse|-p: show parse tree\n" - " --install|-I: install the executable or library\n" - " --optimization|-O : set optimization level\n" - " --run|-r: run a program from " TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "\n"); + Text_t usage = Texts("\x1b[33;4;1mUsage:\x1b[m\n" + "\x1b[1mRun a program:\x1b[m tomo file.tm [-- args...]\n" + "\x1b[1mTranspile files:\x1b[m tomo -t file.tm...\n" + "\x1b[1mCompile object files:\x1b[m tomo -c file.tm...\n" + "\x1b[1mCompile executables:\x1b[m tomo -e file.tm...\n" + "\x1b[1mBuild libraries:\x1b[m tomo -L lib...\n" + "\x1b[1mUninstall libraries:\x1b[m tomo -u lib...\n" + "\x1b[1mOther flags:\x1b[m\n" + " --verbose|-v: verbose output\n" + " --quiet|-q: quiet output\n" + " --parse|-p: show parse tree\n" + " --install|-I: install the executable or library\n" + " --optimization|-O : set optimization level\n" + " --run|-r: run a program from ", + TOMO_PATH, "/lib/tomo_" TOMO_VERSION "\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}, @@ -203,7 +209,7 @@ int main(int argc, char *argv[]) { {"m", false, &Bool$info, &source_mapping}, {"changelog", false, &Bool$info, &show_changelog}, ); if (show_prefix) { - print(TOMO_PREFIX); + print(TOMO_PATH); return 0; } @@ -229,6 +235,8 @@ int main(int argc, char *argv[]) { cflags = Texts(cflags, Text(" -Wno-parentheses-equality")); } + ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib',-rpath,/usr/local/lib ", ldflags); + #ifdef __APPLE__ cflags = Texts(cflags, Text(" -I/opt/homebrew/include")); ldflags = Texts(ldflags, Text(" -L/opt/homebrew/lib -Wl,-rpath,/opt/homebrew/lib")); @@ -239,7 +247,7 @@ int main(int argc, char *argv[]) { config_summary = Text$from_str(String(cc, " ", cflags, " -O", optimization)); - Text_t owner = Path$owner(Path$from_str(TOMO_PREFIX), true); + Text_t owner = Path$owner(Path$from_str(TOMO_PATH), true); Text_t user = Text$from_str(getenv("USER")); if (!Text$equal_values(user, owner)) { as_owner = Texts(Text(SUDO " -u "), owner, Text(" ")); @@ -247,7 +255,7 @@ int main(int argc, char *argv[]) { for (int64_t i = 0; i < uninstall.length; i++) { Text_t *u = (Text_t *)(uninstall.data + i * uninstall.stride); - xsystem(as_owner, "rm -rvf '" TOMO_PREFIX "'/lib/tomo_" TOMO_VERSION "/", *u); + xsystem(as_owner, "rm -rvf '", TOMO_PATH, "'/lib/tomo_" TOMO_VERSION "/", *u); print("Uninstalled ", *u); } @@ -333,7 +341,7 @@ int main(int argc, char *argv[]) { for (int64_t i = 0; i < files.length; i++) { Path_t path = *(Path_t *)(files.data + i * files.stride); Path_t exe = Path$with_extension(path, Text(""), true); - xsystem(as_owner, "cp -v '", exe, "' '" TOMO_PREFIX "'/bin/"); + xsystem(as_owner, "cp -v '", exe, "' '", TOMO_PATH, "'/bin/"); } } return 0; @@ -420,7 +428,7 @@ void build_library(Path_t lib_dir) { void install_library(Path_t lib_dir) { Text_t lib_dir_name = Path$base_name(lib_dir); Text_t versioned_dir = Path$base_name(with_version_suffix(lib_dir)); - Path_t dest = Path$child(Path$from_str(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION), versioned_dir); + Path_t dest = Path$child(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION)), versioned_dir); print("Installing ", lib_dir, " into ", dest); if (!Path$equal_values(lib_dir, dest)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_dir_name); @@ -440,7 +448,7 @@ void install_library(Path_t lib_dir) { "' " ">/dev/null 2>/dev/null")); (void)result; - print("Installed \033[1m", lib_dir_name, "\033[m to " TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", versioned_dir); + print("Installed \033[1m", lib_dir_name, "\033[m to ", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", versioned_dir); } void compile_files(env_t *env, List_t to_compile, List_t *object_files, List_t *extra_ldlibs) { @@ -600,14 +608,13 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l case USE_MODULE: { module_info_t mod = get_module_info(stmt_ast); const char *full_name = mod.version ? String(mod.name, "_", mod.version) : mod.name; - Text_t lib = - Texts(Text("-Wl,-rpath,'"), Text(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/"), Text$from_str(full_name), - Text("' '" TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/"), Text$from_str(full_name), Text("/lib"), - Text$from_str(full_name), Text(SHARED_SUFFIX "'")); + Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", Text$from_str(full_name), + "' '", TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", Text$from_str(full_name), "/lib", + Text$from_str(full_name), SHARED_SUFFIX "'"); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); List_t children = - Path$glob(Path$from_str(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); + Path$glob(Path$from_str(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", full_name, "/[!._0-9]*.tm"))); for (int64_t i = 0; i < children.length; i++) { Path_t *child = (Path_t *)(children.data + i * children.stride); Table_t discarded = {.fallback = to_compile}; diff --git a/src/typecheck.c b/src/typecheck.c index 80c270ef..5ce0baec 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -186,8 +186,7 @@ static env_t *load_module(env_t *env, ast_t *module_ast) { module_info_t mod = get_module_info(module_ast); glob_t tm_files; const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; - if (glob(String(TOMO_PREFIX "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, - &tm_files) + if (glob(String(TOMO_PATH, "/lib/tomo_" TOMO_VERSION "/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { if (!try_install_module(mod, true)) code_err(module_ast, "Couldn't find or install library: ", folder); } -- cgit v1.2.3 From adc2d81b5683e611c5f3289be6157d4519a60632 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 17:35:55 -0400 Subject: Update changelog --- CHANGES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 97c80bdb..7a952e07 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,16 @@ # Version History +## v0.4 +- Tomo libraries are now installed to `$TOMO_PATH/lib/tomo_vX.Y/module_vZ.W` + instead of `$TOMO_PATH/share/tomo_vX.Y/installed/module_vZ.W` +- Core libraries are no longer shipped with the compiler, they have moved to + separate repositories. +- Library installation has been cleaned up a bit. + ## v0.3 - Added a versioning system based on `CHANGES.md` files and `modules.ini` - configuration for module aliases (now installed to - `$TOMO_PREFIX/lib/tomo_vX.Y/module_vZ.W`). + configuration for module aliases. - When attempting to run a program with a module that is not installed, Tomo can prompt the user to automatically install it. - Programs can use `--version` as a CLI flag to print a Tomo program's version -- 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(-) 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 b77850f63b8605d6e62215e7776f6e143e7ec94a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 17:44:19 -0400 Subject: Update wording --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a57e3f95..1c778750 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,12 +6,13 @@ - Core libraries are no longer shipped with the compiler, they have moved to separate repositories. - Library installation has been cleaned up a bit. +- Added a `--format` flag to the `tomo` binary that autoformats your code + (currently unstable, do not rely on it just yet). ## v0.3 - Added a versioning system based on `CHANGES.md` files and `modules.ini` configuration for module aliases. -- Added a `--format` flag to the `tomo` binary that autoformats your code. - When attempting to run a program with a module that is not installed, Tomo can prompt the user to automatically install it. - Programs can use `--version` as a CLI flag to print a Tomo program's version -- cgit v1.2.3 From d51460267940877ed9e14db261f26b0dee3c66f9 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 18:22:54 -0400 Subject: Add progress counter. --- Makefile | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 28e79760..86b5b76b 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,21 @@ else include config.mk +# Modified progress counter based on: https://stackoverflow.com/a/35320895 +ifndef NO_PROGRESS +ifndef ECHO +T := $(shell $(MAKE) ECHO="COUNTTHIS" $(MAKECMDGOALS) --no-print-directory \ + -n | grep -c "COUNTTHIS") +N := x +C = $(words $N)$(eval N := x $N) +ECHO = echo -e "[`expr $C '*' 100 / $T`%]" +endif +endif +ifndef ECHO +ECHO = echo +endif +# End of progress counter + CC=$(DEFAULT_C_COMPILER) CCONFIG=-std=c2x -fPIC \ -fno-signed-zeros -fno-trapping-math \ @@ -102,6 +117,7 @@ API_YAML=$(wildcard api/*.yaml) API_MD=$(patsubst %.yaml,%.md,$(API_YAML)) all: config.mk check-c-compiler check-libs build/include/tomo_$(TOMO_VERSION) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) build/bin/$(EXE_FILE) + @$(ECHO) "All done!" build/include/tomo_$(TOMO_VERSION): ln -s ../../src/stdlib $@ @@ -119,12 +135,12 @@ check-libs: check-c-compiler build/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS) @mkdir -p build/bin - @echo $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@ + @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@ @$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ build/lib/$(LIB_FILE): $(STDLIB_OBJS) @mkdir -p build/lib - @echo $(CC) $^ $(CFLAGS_PLACEHOLDER) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ + @$(ECHO) $(CC) $^ $(CFLAGS_PLACEHOLDER) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ @$(CC) $^ $(CFLAGS) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ build/lib/$(AR_FILE): $(STDLIB_OBJS) @@ -138,15 +154,16 @@ config.mk: configure.sh bash ./configure.sh %.o: %.c src/ast.h src/environment.h src/types.h config.mk - @echo $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@ + @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@ @$(CC) $(CFLAGS) -c $< -o $@ # Specifically src/tomo.c needs to recompile if CHANGES.md changes: src/tomo.o: src/tomo.c src/ast.h src/environment.h src/types.h config.mk src/changes.md.h - @echo $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@ + @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) -c $< -o $@ @$(CC) $(CFLAGS) -c $< -o $@ src/changes.md.h: CHANGES.md + @$(ECHO) "Embedding changes.md" xxd -i $< > $@ %: %.tm -- 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(-) 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 34347f99d28a0f4907b737f97df0a6b8da0df769 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 19:40:51 -0400 Subject: Tweak gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 71fa5258..78087392 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,7 @@ /src/changes.md.h .build -/build/bin -/build/lib +/build *.o *.so *.dylib -- cgit v1.2.3 From e156d74ee6ea4030776d97601bc930eae560086d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 1 Sep 2025 19:42:42 -0400 Subject: Add xxd to deps --- install_dependencies.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/install_dependencies.sh b/install_dependencies.sh index 9b9026e9..5ce6bd74 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -48,19 +48,19 @@ fi # Install packages case "$PKG_MGR" in - apt) $SUDO apt install libgc-dev libunistring-dev binutils libgmp-dev ;; - dnf) $SUDO dnf install gc-devel libunistring-devel binutils gmp-devel ;; - pacman) $SUDO pacman -S gc libunistring binutils gmp ;; - yay|paru) $PKG_MGR -S gc libunistring binutils gmp ;; - xbps) $SUDO xbps-install -S gc libunistring binutils gmp ;; - pkg_add) $SUDO pkg_add boehm-gc libunistring binutils gmp ;; - freebsd-pkg) $SUDO pkg install boehm-gc libunistring binutils gmp ;; - brew) brew install bdw-gc libunistring binutils llvm gmp ;; - macports) $SUDO port install boehm-gc libunistring binutils gmp ;; - zypper) $SUDO zypper install gc-devel libunistring-devel binutils gmp-devel ;; - nix) nix-env -iA nixpkgs.boehmgc.dev nixpkgs.libunistring nixpkgs.binutils nixpkgs.nixpkgs.gmp ;; - spack) spack install boehm-gc libunistring binutils gmp ;; - conda) conda install boehm-gc libunistring binutils gmp ;; + apt) $SUDO apt install libgc-dev libunistring-dev binutils libgmp-dev xxd ;; + dnf) $SUDO dnf install gc-devel libunistring-devel binutils gmp-devel xxd ;; + pacman) $SUDO pacman -S gc libunistring binutils gmp xxd ;; + yay|paru) $PKG_MGR -S gc libunistring binutils gmp xxd ;; + xbps) $SUDO xbps-install -S gc libunistring binutils gmp xxd ;; + pkg_add) $SUDO pkg_add boehm-gc libunistring binutils gmp xxd ;; + freebsd-pkg) $SUDO pkg install boehm-gc libunistring binutils gmp xxd ;; + brew) brew install bdw-gc libunistring binutils llvm gmp xxd ;; + macports) $SUDO port install boehm-gc libunistring binutils gmp xxd ;; + zypper) $SUDO zypper install gc-devel libunistring-devel binutils gmp-devel xxd ;; + nix) nix-env -iA nixpkgs.boehmgc.dev nixpkgs.libunistring nixpkgs.binutils nixpkgs.nixpkgs.gmp xxd ;; + spack) spack install boehm-gc libunistring binutils gmp xxd ;; + conda) conda install boehm-gc libunistring binutils gmp xxd ;; *) echo "Unknown package manager: $PKG_MGR" >&2 exit 1 -- 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() --- CHANGES.md | 2 ++ src/stdlib/integers.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1c778750..e2f69439 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ - Library installation has been cleaned up a bit. - Added a `--format` flag to the `tomo` binary that autoformats your code (currently unstable, do not rely on it just yet). +- Fixed bugs: + - `Int.parse()` had a memory bug. ## v0.3 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(-) 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(-) 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(-) 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