aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ast.c151
-rw-r--r--src/ast.h24
-rw-r--r--src/compile/assertions.c35
-rw-r--r--src/compile/assignments.c7
-rw-r--r--src/compile/binops.c2
-rw-r--r--src/compile/comparisons.c14
-rw-r--r--src/compile/conditionals.c2
-rw-r--r--src/compile/doctests.c17
-rw-r--r--src/compile/enums.c20
-rw-r--r--src/compile/expressions.c5
-rw-r--r--src/compile/files.c49
-rw-r--r--src/compile/functions.c44
-rw-r--r--src/compile/headers.c6
-rw-r--r--src/compile/indexing.c4
-rw-r--r--src/compile/lists.c2
-rw-r--r--src/compile/loops.c4
-rw-r--r--src/compile/optionals.c11
-rw-r--r--src/compile/promotions.c12
-rw-r--r--src/compile/reductions.c7
-rw-r--r--src/compile/sets.c2
-rw-r--r--src/compile/statements.c81
-rw-r--r--src/compile/structs.c9
-rw-r--r--src/compile/tables.c2
-rw-r--r--src/compile/types.c4
-rw-r--r--src/config.h6
-rw-r--r--src/environment.c6
-rw-r--r--src/environment.h2
-rw-r--r--src/formatter/args.c57
-rw-r--r--src/formatter/args.h11
-rw-r--r--src/formatter/enums.c51
-rw-r--r--src/formatter/enums.h11
-rw-r--r--src/formatter/formatter.c884
-rw-r--r--src/formatter/formatter.h13
-rw-r--r--src/formatter/types.c45
-rw-r--r--src/formatter/types.h8
-rw-r--r--src/formatter/utils.c154
-rw-r--r--src/formatter/utils.h30
-rw-r--r--src/modules.c119
-rw-r--r--src/modules.h3
-rw-r--r--src/parse/binops.c31
-rw-r--r--src/parse/containers.c32
-rw-r--r--src/parse/context.c8
-rw-r--r--src/parse/context.h5
-rw-r--r--src/parse/controlflow.c24
-rw-r--r--src/parse/expressions.c28
-rw-r--r--src/parse/files.c18
-rw-r--r--src/parse/functions.c41
-rw-r--r--src/parse/statements.c10
-rw-r--r--src/parse/suffixes.c30
-rw-r--r--src/parse/text.c18
-rw-r--r--src/parse/typedefs.c38
-rw-r--r--src/parse/types.c20
-rw-r--r--src/parse/utils.c16
-rw-r--r--src/parse/utils.h6
-rw-r--r--src/stdlib/integers.c4
-rw-r--r--src/stdlib/integers.h1
-rw-r--r--src/stdlib/nums.c36
-rw-r--r--src/stdlib/nums.h10
-rw-r--r--src/stdlib/pointers.c2
-rw-r--r--src/stdlib/stacktrace.c2
-rw-r--r--src/stdlib/stdlib.c3
-rw-r--r--src/stdlib/text.c50
-rw-r--r--src/stdlib/text.h15
-rw-r--r--src/tomo.c201
-rw-r--r--src/typecheck.c111
-rw-r--r--src/typecheck.h3
-rw-r--r--src/types.c5
67 files changed, 2037 insertions, 645 deletions
diff --git a/src/ast.c b/src/ast.c
index 53d80a81..37c5a514 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -5,77 +5,76 @@
#include "ast.h"
#include "stdlib/datatypes.h"
+#include "stdlib/optionals.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
-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;
- }
+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,
};
-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 Equals: return "==";
- case NotEquals: return "!=";
- case LessThan: return "<";
- case LessThanOrEquals: return "<=";
- case GreaterThan: return ">";
- case GreaterThanOrEquals: return ">=";
- 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);
@@ -86,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) {
@@ -175,8 +176,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), ")");
@@ -255,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_method_name(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), ")");
@@ -285,13 +286,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, (size_t)(ast->end - ast->start));
}
PUREFUNC bool is_idempotent(ast_t *ast) {
@@ -400,6 +397,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 bf22c9f6..7396555e 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, *alias;
type_ast_t *type;
ast_t *value;
@@ -88,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;
@@ -160,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: \
@@ -271,6 +279,7 @@ typedef enum {
Extend,
ExplicitlyTyped,
} ast_e;
+#define NUM_AST_TAGS (ExplicitlyTyped + 1)
struct ast_s {
ast_e tag;
@@ -470,7 +479,16 @@ struct ast_s {
} __data;
};
-const char *ast_source(ast_t *ast);
+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);
const char *ast_to_sexp_str(ast_t *ast);
@@ -479,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/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/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/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/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/expressions.c b/src/compile/expressions.c
index 2320474a..544b7723 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);
@@ -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/compile/files.c b/src/compile/files.c
index 2b0ac4bf..4d6fb1a8 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) {
@@ -34,17 +43,13 @@ static void initialize_vars_and_statics(env_t *env, ast_t *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) {
@@ -95,15 +105,15 @@ 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, "$", 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: {
@@ -111,24 +121,21 @@ 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);
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);
+ return Texts(code, compile_namespace(env, def->name, def->namespace));
}
case Extend: {
DeclareMatch(extend, ast, Extend);
diff --git a/src/compile/functions.c b/src/compile/functions.c
index 01de26e3..f04a3b59 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;
@@ -257,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
@@ -290,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);
@@ -624,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;
@@ -633,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,
@@ -690,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) {
@@ -736,7 +723,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 +759,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/headers.c b/src/compile/headers.c
index 8c0863ee..6dc69f03 100644
--- a/src/compile/headers.c
+++ b/src/compile/headers.c
@@ -174,10 +174,10 @@ 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_PATH, "/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/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/reductions.c b/src/compile/reductions.c
index e0477a9c..438e072b 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_info[op].operator;
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/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..bde9ae36 100644
--- a/src/compile/statements.c
+++ b/src/compile/statements.c
@@ -22,7 +22,15 @@ 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_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) {
@@ -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:
@@ -121,7 +100,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);
@@ -131,7 +110,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 +118,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;");
}
}
@@ -204,10 +191,10 @@ 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_PATH, "/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/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/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/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/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/formatter/args.c b/src/formatter/args.c
new file mode 100644
index 00000000..997a1e39
--- /dev/null
+++ b/src/formatter/args.c
@@ -0,0 +1,57 @@
+// 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"
+#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;
+ 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)));
+ 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));
+ 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 (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(arg, comments)));
+ if (arg->next) code = Texts(code, ", ");
+ }
+ if (arg->next && range_has_comment(arg->end, arg->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 (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(arg, comments, Texts(indent, single_indent)));
+ if (args->next) code = Texts(code, ",");
+ }
+ }
+ return code;
+}
diff --git a/src/formatter/args.h b/src/formatter/args.h
new file mode 100644
index 00000000..c902684b
--- /dev/null
+++ b/src/formatter/args.h
@@ -0,0 +1,11 @@
+// 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);
diff --git a/src/formatter/enums.c b/src/formatter/enums.c
new file mode 100644
index 00000000..893f055b
--- /dev/null
+++ b/src/formatter/enums.c
@@ -0,0 +1,51 @@
+// 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 "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;
+ 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 = 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) {
+ 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..b68b3874
--- /dev/null
+++ b/src/formatter/formatter.c
@@ -0,0 +1,884 @@
+// This code defines functions for transforming ASTs back into Tomo source text
+
+#include <assert.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unictype.h>
+
+#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"
+#include "args.h"
+#include "enums.h"
+#include "formatter.h"
+#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, fmt(namespace, comments, Texts(indent, single_indent)));
+}
+
+typedef struct {
+ Text_t quote, unquote, interp;
+} text_opts_t;
+
+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) {
+ 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 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) {
+ 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 {
+ 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);
+}
+
+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) {
+ /*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 fmt_inline(statements->ast, comments);
+ else return NONE_TEXT;
+ }
+ /*inline*/ case StructDef:
+ /*inline*/ case EnumDef:
+ /*inline*/ case LangDef:
+ /*inline*/ case Extend:
+ /*inline*/ case FunctionDef:
+ /*inline*/ case ConvertDef:
+ /*inline*/ case DocTest:
+ /*inline*/ case Extern:
+ 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 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));
+ 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);
+
+ 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);
+ if (!stmt) return Texts("pass ", if_condition);
+ switch (stmt->tag) {
+ case Return:
+ case Skip:
+ case Stop: return Texts(fmt_inline(stmt, comments), " ", if_condition);
+ default: break;
+ }
+ }
+
+ 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;
+ }
+ /*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: {
+ DeclareMatch(loop, ast, While);
+ 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;
+ }
+ /*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, fmt_inline(item->ast, comments));
+ if (item->next) code = Texts(code, ", ");
+ }
+ return ast->tag == List ? Texts("[", code, "]") : Texts("|", code, "|");
+ }
+ /*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 = fmt_inline(decl->var, 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;
+ }
+ /*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, 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, fmt_inline(value->ast, comments));
+ if (value->next) code = Texts(code, ", ");
+ }
+ return code;
+ }
+ /*inline*/ case Pass:
+ return Text("pass");
+ /*inline*/ case Return: {
+ 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("@", must(termify_inline(val, comments)));
+ }
+ /*inline*/ case StackReference: {
+ ast_t *val = Match(ast, StackReference)->value;
+ return Texts("&", must(termify_inline(val, comments)));
+ }
+ /*inline*/ case Optional: {
+ ast_t *val = Match(ast, Optional)->value;
+ return Texts(must(termify_inline(val, comments)), "?");
+ }
+ /*inline*/ case NonOptional: {
+ ast_t *val = Match(ast, NonOptional)->value;
+ return Texts(must(termify_inline(val, comments)), "!");
+ }
+ /*inline*/ case FieldAccess: {
+ DeclareMatch(access, ast, FieldAccess);
+ return Texts(must(termify_inline(access->fielded, comments)), ".", Text$from_str(access->field));
+ }
+ /*inline*/ case Index: {
+ DeclareMatch(index, ast, Index);
+ 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: {
+ 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;
+ 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");
+ 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$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");
+ }
+ /*inline*/ case Skip: {
+ 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 Reduction: {
+ DeclareMatch(reduction, ast, Reduction);
+ if (reduction->key) {
+ return Texts("(", fmt_inline(reduction->key, comments), ": ", fmt_inline(reduction->iter, comments));
+ } else {
+ return Texts("(", Text$from_str(binop_info[reduction->op].operator), ": ",
+ fmt_inline(reduction->iter, comments));
+ }
+ }
+ /*inline*/ case None:
+ return Text("none");
+ /*inline*/ case Bool:
+ 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)), ")");
+ }
+ /*inline*/ case MethodCall: {
+ DeclareMatch(call, ast, MethodCall);
+ 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);
+ const char *op = binop_info[ast->tag].operator;
+
+ 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);
+ }
+
+ 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_info[ast->tag].operator), 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));
+ }
+ }
+}
+
+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--) {
+ 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);
+
+ switch (ast->tag) {
+ /*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;
+ 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, Text$trim(comment, Text(" \t\r\n"), false, true), 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) {
+ 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;
+ }
+
+ 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;
+ }
+ /*multiline*/ case If: {
+ DeclareMatch(if_, ast, If);
+ 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,
+ 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: {
+ 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)));
+ }
+ /*multiline*/ case While: {
+ DeclareMatch(loop, ast, While);
+ 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;
+ }
+ /*multiline*/ case FunctionDef: {
+ DeclareMatch(func, ast, FunctionDef);
+ 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));
+ if (func->is_inline) code = Texts(code, "; inline");
+ 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: {
+ 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, 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: {
+ 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, 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 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");
+ 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, Text$has(code, Text("\n")) ? Texts("\n", indent, ")") : Text(")"),
+ format_namespace(def->namespace, comments, indent));
+ }
+ /*multiline*/ case LangDef: {
+ DeclareMatch(def, ast, LangDef);
+ return Texts("lang ", Text$from_str(def->name), format_namespace(def->namespace, comments, indent));
+ }
+ /*multiline*/ case Extend: {
+ DeclareMatch(extend, ast, Extend);
+ return Texts("lang ", Text$from_str(extend->name), format_namespace(extend->body, comments, indent));
+ }
+ /*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: {
+ if (inlined_fits) return inlined;
+ ast_list_t *items = ast->tag == List ? Match(ast, List)->items : Match(ast, Set)->items;
+ 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));
+ }
+ 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(code, "\n", indent, "]") : Texts(code, "\n", indent, "|");
+ }
+ /*multiline*/ case Table: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(table, ast, Table);
+ 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));
+ }
+
+ 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));
+ }
+
+ 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(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: {
+ 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));
+ if (decl->value)
+ code = Texts(code, decl->type ? Text(" = ") : Text(" := "), fmt(decl->value, comments, 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) {
+ 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, fmt(value->ast, comments, indent));
+ if (value->next) code = Texts(code, ", ");
+ }
+ return code;
+ }
+ /*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));
+ }
+ /*multiline*/ case HeapAllocate: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, HeapAllocate)->value;
+ return Texts("@", termify(val, comments, indent), "");
+ }
+ /*multiline*/ case StackReference: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, StackReference)->value;
+ return Texts("&(", termify(val, comments, indent), ")");
+ }
+ /*multiline*/ case Optional: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, Optional)->value;
+ return Texts(termify(val, comments, indent), "?");
+ }
+ /*multiline*/ case NonOptional: {
+ if (inlined_fits) return inlined;
+ ast_t *val = Match(ast, NonOptional)->value;
+ 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), "]");
+ else return Texts(termify(index->indexed, comments, indent), "[]");
+ }
+ /*multiline*/ case TextJoin: {
+ if (inlined_fits) return inlined;
+
+ 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;
+ return lang ? Texts("$", Text$from_str(lang), ret) : ret;
+ }
+ /*multiline*/ case InlineCCode: {
+ DeclareMatch(c_code, ast, InlineCCode);
+ 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: {
+ assert(inlined.length > 0);
+ return inlined;
+ }
+ /*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_info[reduction->op].operator, ": ",
+ fmt(reduction->iter, comments, Texts(indent, single_indent)));
+ }
+ }
+ /*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;
+ }
+ /*multiline*/ case FunctionCall: {
+ if (inlined_fits) return inlined;
+ DeclareMatch(call, ast, FunctionCall);
+ 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);
+ 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);
+ Text_t expr = fmt(test->expr, comments, indent);
+ Text_t code = Texts(">> ", expr);
+ if (test->expected) {
+ Text_t expected = fmt(test->expected, comments, indent);
+ code = Texts(code, "\n", indent, "= ", expected);
+ }
+ 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);
+ const char *op = binop_info[ast->tag].operator;
+ 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);
+ }
+
+ 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_info[ast->tag].operator), 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));
+ }
+ }
+}
+
+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, 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");
+ }
+ return code;
+}
diff --git a/src/formatter/formatter.h b/src/formatter/formatter.h
new file mode 100644
index 00000000..a8f9013a
--- /dev/null
+++ b/src/formatter/formatter.h
@@ -0,0 +1,13 @@
+// This code defines functions for transforming ASTs back into Tomo source text
+
+#pragma once
+
+#include <stdbool.h>
+
+#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);
+Text_t format_namespace(ast_t *namespace, Table_t comments, Text_t indent);
+OptionalText_t format_inline_code(ast_t *ast, Table_t comments);
diff --git a/src/formatter/types.c b/src/formatter/types.c
new file mode 100644
index 00000000..e52faf70
--- /dev/null
+++ b/src/formatter/types.c
@@ -0,0 +1,45 @@
+// Logic for formatting types
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/stdlib.h"
+#include "../stdlib/text.h"
+#include "args.h"
+#include "formatter.h"
+
+Text_t format_type(type_ast_t *type) {
+ switch (type->tag) {
+ 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), "]");
+ }
+ case SetTypeAST: {
+ return Texts("|", format_type(Match(type, SetTypeAST)->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
new file mode 100644
index 00000000..2571f880
--- /dev/null
+++ b/src/formatter/types.h
@@ -0,0 +1,8 @@
+// Logic for formatting types
+
+#pragma once
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+
+Text_t format_type(type_ast_t *type);
diff --git a/src/formatter/utils.c b/src/formatter/utils.c
new file mode 100644
index 00000000..bbe74d7f
--- /dev/null
+++ b/src/formatter/utils.c
@@ -0,0 +1,154 @@
+// This file defines utility functions for autoformatting code
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "../ast.h"
+#include "../parse/context.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.h"
+#include "../stdlib/tables.h"
+#include "../stdlib/text.h"
+#include "formatter.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, (size_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 int suggested_blank_lines(ast_t *first, ast_t *second) {
+ if (second == NULL) return 0;
+
+ 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:
+ case When:
+ case Repeat:
+ case While:
+ case For:
+ case Block:
+ 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;
+ }
+
+ switch (second->tag) {
+ case If:
+ case When:
+ case Repeat:
+ case While:
+ case For:
+ case Block:
+ case Defer:
+ case ConvertDef:
+ case FunctionDef:
+ case Lambda:
+ case StructDef:
+ case EnumDef:
+ case LangDef:
+ case Extend: return 1;
+ default: break;
+ }
+ return 0;
+}
+
+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;
+ }
+ if (ast->tag == Block && Match(ast, Block)->statements == NULL) return NULL;
+ return ast;
+}
+
+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:
+ case HeapAllocate:
+ case StackReference: 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:
+ case HeapAllocate:
+ case StackReference: 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
new file mode 100644
index 00000000..880da0a9
--- /dev/null
+++ b/src/formatter/utils.h
@@ -0,0 +1,30 @@
+// This file defines utility functions for autoformatting code
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "../ast.h"
+#include "../stdlib/datatypes.h"
+#include "../stdlib/optionals.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 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);
+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/modules.c b/src/modules.c
index 40b3daf3..fafbbf86 100644
--- a/src/modules.c
+++ b/src/modules.c
@@ -5,6 +5,7 @@
#include <string.h>
#include <sys/wait.h>
+#include "config.h"
#include "modules.h"
#include "stdlib/memory.h"
#include "stdlib/paths.h"
@@ -14,6 +15,7 @@
#include "stdlib/tables.h"
#include "stdlib/text.h"
#include "stdlib/types.h"
+#include "stdlib/util.h"
#define xsystem(...) \
({ \
@@ -22,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;
@@ -50,47 +79,41 @@ 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) {
+bool try_install_module(module_info_t mod, bool ask_confirmation) {
+ 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) {
- 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] ")),
- 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...");
- Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX"));
- 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 = 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);
- 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 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;
+ }
print("Installing ", mod.name, " from URL...");
@@ -101,25 +124,31 @@ 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);
+ 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) {
- 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);
- 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 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;
+ }
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/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/parse/binops.c b/src/parse/binops.c
index 7ccf1379..4676b249 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 '+': {
@@ -94,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 =
@@ -108,7 +81,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..cd8d16bc
--- /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"
+
+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 6008060e..f1e3be2f 100644
--- a/src/parse/context.h
+++ b/src/parse/context.h
@@ -4,10 +4,15 @@
#include <setjmp.h>
#include <stdint.h>
+#include "../stdlib/datatypes.h"
#include "../stdlib/files.h"
+#include "../stdlib/types.h"
+
+extern 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 <start pos> -> <end pos>
} parse_ctx_t;
diff --git a/src/parse/controlflow.c b/src/parse/controlflow.c
index 6f6292af..1087e20e 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))) {
@@ -131,18 +131,8 @@ ast_t *parse_while(parse_ctx_t *ctx, const char *pos) {
// while condition ["do"] [<indent>] 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, While, .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);
}
@@ -174,7 +164,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 +188,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 +207,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 +245,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..df0a10a7 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,11 +45,13 @@ 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;
- ast_t *key = NewAST(ctx->file, pos, pos, Var, .name = "$");
+ 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;) {
ast_t *new_term;
progress =
@@ -61,7 +63,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 +75,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 +166,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 +240,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 37505ac5..ceb0a8bc 100644
--- a/src/parse/functions.c
+++ b/src/parse/functions.c
@@ -26,37 +26,42 @@ 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, *alias;
struct name_list_s *next;
} name_list_t;
name_list_t *names = NULL;
for (;;) {
- whitespace(pos);
+ whitespace(ctx, pos);
const char *name = get_id(pos);
if (!name) break;
- whitespace(pos);
+ const char *name_start = *pos;
+ whitespace(ctx, 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 `|`");
}
if (match(pos, ":")) {
type = expect(ctx, *pos - 1, pos, parse_type, "I expected a type here");
- names = new (name_list_t, .name = name, .alias = alias, .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 '='");
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .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, .alias = alias, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
break;
} else if (name) {
- names = new (name_list_t, .name = name, .alias = alias, .next = names);
+ names =
+ new (name_list_t, .start = name_start, .end = *pos, .name = name, .alias = alias, .next = names);
spaces(pos);
if (!match(pos, ",")) break;
} else {
@@ -71,10 +76,10 @@ 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, .alias = names->alias, .type = type, .value = default_val,
- .next = args);
+ 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(pos)) break;
+ if (!match_separator(ctx, pos)) break;
}
REVERSE_LIST(args);
@@ -95,19 +100,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");
}
}
@@ -129,19 +134,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..cb54b2f6 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;
@@ -132,12 +132,12 @@ 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);
- if (!match_separator(&pos)) break;
+ 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);
- 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/text.c b/src/parse/text.c
index 8897fd34..30ff8656 100644
--- a/src/parse/text.c
+++ b/src/parse/text.c
@@ -148,21 +148,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);
- chunks = _parse_text_helper(ctx, &pos);
- if (type) {
- chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("({")), .next = chunks);
- REVERSE_LIST(chunks);
- chunks = new (ast_list_t, .ast = NewAST(ctx->file, pos, pos, TextLiteral, Text("; })")), .next = chunks);
- REVERSE_LIST(chunks);
- }
- } else {
- chunks = _parse_text_helper(ctx, &pos);
}
+ 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/parse/typedefs.c b/src/parse/typedefs.c
index 73fe9d7c..6e5e40d0 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,9 +110,10 @@ 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_start = pos;
const char *tag_name = get_id(&pos);
if (!tag_name) break;
@@ -120,25 +121,26 @@ 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 {
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(&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 +149,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 +172,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 +194,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 <unictype.h>
#include <uniname.h>
+#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);
diff --git a/src/stdlib/integers.c b/src/stdlib/integers.c
index 7dda77bd..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);
@@ -618,6 +618,8 @@ void Int32$deserialize(FILE *in, void *outval, List_t *pointers, const TypeInfo_
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; \
return (*(c_type *)x > *(c_type *)y) - (*(c_type *)x < *(c_type *)y); \
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/stacktrace.c b/src/stdlib/stacktrace.c
index 266dc4ef..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 "/share/tomo_" TOMO_VERSION "/installed/";
+ 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/stdlib/text.c b/src/stdlib/text.c
index ed4023a4..d3757a0d 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;
}
@@ -1726,10 +1729,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..7f7fc2c6 100644
--- a/src/stdlib/text.h
+++ b/src/stdlib/text.h
@@ -7,7 +7,9 @@
#include <stdint.h>
#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__})
@@ -54,6 +66,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);
diff --git a/src/tomo.c b/src/tomo.c
index da10df87..e6636cfc 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/formatter.h"
#include "modules.h"
#include "naming.h"
#include "parse/files.h"
@@ -34,6 +35,7 @@
#include "stdlib/siphash.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
+#include "stdlib/util.h"
#include "types.h"
#define run_cmd(...) \
@@ -74,9 +76,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, 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;
static OptionalText_t show_codegen = NONE_TEXT,
cflags = Text("-Werror -fdollars-in-identifiers -std=c2x -Wno-trigraphs "
@@ -87,16 +89,13 @@ 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/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 <user>" or "doas -u <user>"
- // 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,67 +148,103 @@ 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 "'/share/tomo_" TOMO_VERSION "/installed/", 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 <level>: set optimization level\n"
- " --run|-r: run a program from " TOMO_PREFIX "/share/tomo_" TOMO_VERSION "/installed\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"
+ " --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 <pager>: 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 <level>: set optimization level\n"
+ " --force-rebuild|-f: force rebuilding\n"
+ " --source-mapping|-m <yes|no>: toggle source mapping in generated code\n"
+ " --changelog: show the Tomo changelog\n"
+ " --run|-r: run a program from ",
+ TOMO_PATH, "/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}, //
+ {"format-inplace", false, &Bool$info, &format_inplace}, //
+ {"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);
+ print(TOMO_PATH);
return 0;
}
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;
}
@@ -230,6 +265,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"));
@@ -240,7 +277,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(" "));
@@ -248,7 +285,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_PATH, "'/lib/tomo_" TOMO_VERSION "/", *u);
print("Uninstalled ", *u);
}
@@ -261,8 +298,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);
@@ -299,6 +343,17 @@ int main(int argc, char *argv[]) {
continue;
}
+ if (do_format_code || format_inplace) {
+ Text_t formatted = format_file(Path$as_c_string(path));
+ if (format_inplace) {
+ print("Formatted ", path);
+ Path$write(path, formatted, 0644);
+ } else {
+ print(formatted);
+ }
+ continue;
+ }
+
Path_t exe_path = compile_exe ? Path$with_extension(path, Text(""), true)
: build_file(Path$with_extension(path, Text(""), true), "");
@@ -327,7 +382,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;
@@ -369,21 +424,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;
@@ -391,10 +450,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 ",
@@ -409,9 +468,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 "/share/tomo_" TOMO_VERSION "/installed"),
- 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(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);
xsystem(as_owner, "rm -rf '", dest, "'");
@@ -426,12 +485,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 "/share/tomo_" TOMO_VERSION "/installed/",
- lib_dir_name, version_suffix);
+ 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) {
@@ -591,14 +649,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 "/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 "'"));
+ 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 "/share/tomo_" TOMO_VERSION "/installed/", full_name, "/[!._0-9]*.tm")));
+ List_t children =
+ 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 50df9327..07f8aac4 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -186,10 +186,9 @@ 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_PATH, "/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);
@@ -337,20 +336,20 @@ 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)
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));
@@ -578,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) {
@@ -591,10 +604,40 @@ 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;
+
+ 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);
+
+ 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) {
@@ -1086,7 +1129,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:
@@ -1315,7 +1358,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)) {
@@ -1374,7 +1417,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_info[reduction->op].operator;
+ 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);
@@ -1394,36 +1438,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);
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);
}
}
}