From 7cb55ec0061246685ffe8c6d1d92307495c28943 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 24 Aug 2025 17:59:37 -0400 Subject: Finally, split compile.c into compile/expressions.c --- src/compile.c | 735 --------------------------------------------- src/compile.h | 12 - src/compile/assignments.c | 2 +- src/compile/binops.c | 2 +- src/compile/blocks.c | 2 +- src/compile/cli.c | 2 +- src/compile/declarations.c | 2 +- src/compile/enums.c | 2 +- src/compile/expressions.c | 732 ++++++++++++++++++++++++++++++++++++++++++++ src/compile/expressions.h | 11 + src/compile/functions.c | 2 +- src/compile/integers.c | 2 +- src/compile/lists.c | 2 +- src/compile/optionals.c | 2 +- src/compile/pointers.c | 2 +- src/compile/promotions.c | 2 +- src/compile/sets.c | 2 +- src/compile/statements.c | 2 +- src/compile/structs.c | 2 +- src/compile/tables.c | 2 +- src/compile/text.c | 2 +- 21 files changed, 760 insertions(+), 764 deletions(-) delete mode 100644 src/compile.c delete mode 100644 src/compile.h create mode 100644 src/compile/expressions.c create mode 100644 src/compile/expressions.h (limited to 'src') diff --git a/src/compile.c b/src/compile.c deleted file mode 100644 index 38f40ba1..00000000 --- a/src/compile.c +++ /dev/null @@ -1,735 +0,0 @@ -// Compilation logic -#include -#include -#include - -#include "ast.h" -#include "compile.h" -#include "compile/binops.h" -#include "compile/blocks.h" -#include "compile/declarations.h" -#include "compile/enums.h" -#include "compile/functions.h" -#include "compile/integers.h" -#include "compile/lists.h" -#include "compile/optionals.h" -#include "compile/pointers.h" -#include "compile/promotions.h" -#include "compile/sets.h" -#include "compile/statements.h" -#include "compile/structs.h" -#include "compile/tables.h" -#include "compile/text.h" -#include "compile/types.h" -#include "config.h" -#include "environment.h" -#include "stdlib/tables.h" -#include "stdlib/text.h" -#include "stdlib/util.h" -#include "typecheck.h" - -public -Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { - if (is_idempotent(ast) && can_be_mutated(env, ast)) { - if (t->tag == ListType) return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); - else if (t->tag == TableType || t->tag == SetType) - return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")"); - } - return compile_to_type(env, ast, t); -} - -public -Text_t compile_empty(type_t *t) { - if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a value with no type"); - - if (t->tag == OptionalType) return compile_none(t); - - if (t == PATH_TYPE) return Text("NONE_PATH"); - else if (t == PATH_TYPE_TYPE) return Text("((OptionalPathType_t){})"); - - switch (t->tag) { - case BigIntType: return Text("I(0)"); - case IntType: { - switch (Match(t, IntType)->bits) { - case TYPE_IBITS8: return Text("I8(0)"); - case TYPE_IBITS16: return Text("I16(0)"); - case TYPE_IBITS32: return Text("I32(0)"); - case TYPE_IBITS64: return Text("I64(0)"); - default: errx(1, "Invalid integer bit size"); - } - break; - } - case ByteType: return Text("((Byte_t)0)"); - case BoolType: return Text("((Bool_t)no)"); - case ListType: return Text("((List_t){})"); - case TableType: - case SetType: return Text("((Table_t){})"); - case TextType: return Text("Text(\"\")"); - case CStringType: return Text("\"\""); - case PointerType: { - DeclareMatch(ptr, t, PointerType); - Text_t empty_pointed = compile_empty(ptr->pointed); - return empty_pointed.length == 0 ? EMPTY_TEXT - : Texts(ptr->is_stack ? Text("stack(") : Text("heap("), empty_pointed, ")"); - } - case NumType: { - return Match(t, NumType)->bits == TYPE_NBITS32 ? Text("N32(0.0f)") : Text("N64(0.0)"); - } - case StructType: return compile_empty_struct(t); - case EnumType: return compile_empty_enum(t); - default: return EMPTY_TEXT; - } - return EMPTY_TEXT; -} - -Text_t compile(env_t *env, ast_t *ast) { - switch (ast->tag) { - case None: { - code_err(ast, "I can't figure out what this `none`'s type is!"); - } - case Bool: return Match(ast, Bool)->b ? Text("yes") : Text("no"); - case Var: { - binding_t *b = get_binding(env, Match(ast, Var)->name); - if (b) return b->code.length > 0 ? b->code : Texts("_$", Match(ast, Var)->name); - // return Texts("_$", Match(ast, Var)->name); - code_err(ast, "I don't know of any variable by this name"); - } - case Int: return compile_int(ast); - case Num: { - return Text$from_str(String(hex_double(Match(ast, Num)->n))); - } - case Not: { - ast_t *value = Match(ast, Not)->value; - type_t *t = get_type(env, value); - - binding_t *b = get_namespace_binding(env, value, "negated"); - if (b && b->type->tag == FunctionType) { - DeclareMatch(fn, b->type, FunctionType); - if (fn->args && can_compile_to_type(env, value, get_arg_type(env, fn->args))) - return Texts(b->code, "(", compile_arguments(env, ast, fn->args, new (arg_ast_t, .value = value)), ")"); - } - - if (t->tag == BoolType) return Texts("!(", compile(env, value), ")"); - else if (t->tag == IntType || t->tag == ByteType) return Texts("~(", compile(env, value), ")"); - else if (t->tag == ListType) return Texts("((", compile(env, value), ").length == 0)"); - else if (t->tag == SetType || t->tag == TableType) - return Texts("((", compile(env, value), ").entries.length == 0)"); - else if (t->tag == TextType) return Texts("(", compile(env, value), ".length == 0)"); - else if (t->tag == OptionalType) return check_none(t, compile(env, value)); - - code_err(ast, "I don't know how to negate values of type ", type_to_str(t)); - } - case Negative: { - ast_t *value = Match(ast, Negative)->value; - type_t *t = get_type(env, value); - binding_t *b = get_namespace_binding(env, value, "negative"); - if (b && b->type->tag == FunctionType) { - DeclareMatch(fn, b->type, FunctionType); - if (fn->args && can_compile_to_type(env, value, get_arg_type(env, fn->args))) - return Texts(b->code, "(", compile_arguments(env, ast, fn->args, new (arg_ast_t, .value = value)), ")"); - } - - if (t->tag == IntType || t->tag == NumType) return Texts("-(", compile(env, value), ")"); - - code_err(ast, "I don't know how to get the negative value of type ", type_to_str(t)); - } - case HeapAllocate: - case StackReference: return compile_typed_allocation(env, ast, get_type(env, ast)); - case Optional: return compile_optional(env, ast); - case NonOptional: return compile_non_optional(env, ast); - case Power: - case Multiply: - case Divide: - case Mod: - case Mod1: - case Plus: - case Minus: - case Concat: - case LeftShift: - case UnsignedLeftShift: - case RightShift: - case UnsignedRightShift: - case And: - case Or: - case Xor: { - return compile_binary_op(env, ast); - } - case Equals: - case NotEquals: { - binary_operands_t binop = BINARY_OPERANDS(ast); - - type_t *lhs_t = get_type(env, binop.lhs); - type_t *rhs_t = get_type(env, binop.rhs); - type_t *operand_t; - if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) { - operand_t = rhs_t; - } else if (binop.rhs->tag == Int && is_numeric_type(lhs_t)) { - operand_t = lhs_t; - } else if (can_compile_to_type(env, binop.rhs, lhs_t)) { - operand_t = lhs_t; - } else if (can_compile_to_type(env, binop.lhs, rhs_t)) { - operand_t = rhs_t; - } else { - code_err(ast, "I can't do comparisons between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t)); - } - - Text_t lhs, rhs; - lhs = compile_to_type(env, binop.lhs, operand_t); - rhs = compile_to_type(env, binop.rhs, operand_t); - - switch (operand_t->tag) { - case BigIntType: - return Texts(ast->tag == Equals ? EMPTY_TEXT : Text("!"), "Int$equal_value(", lhs, ", ", rhs, ")"); - case BoolType: - case ByteType: - case IntType: - case NumType: - case PointerType: - case FunctionType: return Texts("(", lhs, ast->tag == Equals ? " == " : " != ", rhs, ")"); - default: - return Texts(ast->tag == Equals ? EMPTY_TEXT : Text("!"), "generic_equal(stack(", lhs, "), stack(", rhs, - "), ", compile_type_info(operand_t), ")"); - } - } - case LessThan: - case LessThanOrEquals: - case GreaterThan: - case GreaterThanOrEquals: - case Compare: { - binary_operands_t cmp = BINARY_OPERANDS(ast); - - type_t *lhs_t = get_type(env, cmp.lhs); - type_t *rhs_t = get_type(env, cmp.rhs); - type_t *operand_t; - if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { - operand_t = rhs_t; - } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { - operand_t = lhs_t; - } else if (can_compile_to_type(env, cmp.rhs, lhs_t)) { - operand_t = lhs_t; - } else if (can_compile_to_type(env, cmp.lhs, rhs_t)) { - operand_t = rhs_t; - } else { - code_err(ast, "I can't do comparisons between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t)); - } - - Text_t lhs = compile_to_type(env, cmp.lhs, operand_t); - Text_t rhs = compile_to_type(env, cmp.rhs, operand_t); - - if (ast->tag == Compare) - return Texts("generic_compare(stack(", lhs, "), stack(", rhs, "), ", compile_type_info(operand_t), ")"); - - const char *op = binop_operator(ast->tag); - switch (operand_t->tag) { - case BigIntType: return Texts("(Int$compare_value(", lhs, ", ", rhs, ") ", op, " 0)"); - case BoolType: - case ByteType: - case IntType: - case NumType: - case PointerType: - case FunctionType: return Texts("(", lhs, " ", op, " ", rhs, ")"); - default: - return Texts("(generic_compare(stack(", lhs, "), stack(", rhs, "), ", compile_type_info(operand_t), ") ", - op, " 0)"); - } - } - case TextLiteral: - case TextJoin: return compile_text_ast(env, ast); - case Path: { - return Texts("Path(", compile_text_literal(Text$from_str(Match(ast, Path)->path)), ")"); - } - case Block: return compile_block_expression(env, ast); - case Min: - case Max: { - type_t *t = get_type(env, 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 = "$"; - if (key == NULL) key = FakeAST(Var, key_name); - - env_t *expr_env = fresh_scope(env); - set_binding(expr_env, key_name, t, Text("ternary$lhs")); - Text_t lhs_key = compile(expr_env, key); - - set_binding(expr_env, key_name, t, Text("ternary$rhs")); - Text_t rhs_key = compile(expr_env, key); - - type_t *key_t = get_type(expr_env, key); - Text_t comparison; - if (key_t->tag == BigIntType) - comparison = - Texts("(Int$compare_value(", lhs_key, ", ", rhs_key, ")", (ast->tag == Min ? "<=" : ">="), "0)"); - else if (key_t->tag == IntType || key_t->tag == NumType || key_t->tag == BoolType || key_t->tag == PointerType - || key_t->tag == ByteType) - comparison = Texts("((", lhs_key, ")", (ast->tag == Min ? "<=" : ">="), "(", rhs_key, "))"); - else - comparison = Texts("generic_compare(stack(", lhs_key, "), stack(", rhs_key, "), ", compile_type_info(key_t), - ")", (ast->tag == Min ? "<=" : ">="), "0"); - - return Texts("({\n", compile_type(t), " ternary$lhs = ", compile(env, lhs), - ", ternary$rhs = ", compile(env, rhs), ";\n", comparison, - " ? ternary$lhs : ternary$rhs;\n" - "})"); - } - case List: { - DeclareMatch(list, ast, List); - if (!list->items) return Text("(List_t){.length=0}"); - - type_t *list_type = get_type(env, ast); - return compile_typed_list(env, ast, list_type); - } - case Table: { - DeclareMatch(table, ast, Table); - if (!table->entries) { - Text_t code = Text("((Table_t){"); - if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), ")"); - return Texts(code, "})"); - } - - type_t *table_type = get_type(env, ast); - return compile_typed_table(env, ast, table_type); - } - case Set: { - DeclareMatch(set, ast, Set); - if (!set->items) return Text("((Table_t){})"); - - type_t *set_type = get_type(env, ast); - return compile_typed_set(env, ast, set_type); - } - case Comprehension: { - ast_t *base = Match(ast, Comprehension)->expr; - while (base->tag == Comprehension) - base = Match(ast, Comprehension)->expr; - if (base->tag == TableEntry) return compile(env, WrapAST(ast, Table, .entries = new (ast_list_t, .ast = ast))); - else return compile(env, WrapAST(ast, List, .items = new (ast_list_t, .ast = ast))); - } - case Lambda: return compile_lambda(env, ast); - case MethodCall: return compile_method_call(env, ast); - case FunctionCall: return compile_function_call(env, ast); - case Deserialize: { - ast_t *value = Match(ast, Deserialize)->value; - type_t *value_type = get_type(env, value); - if (!type_eq(value_type, Type(ListType, Type(ByteType)))) - code_err(value, "This value should be a list of bytes, not a ", type_to_str(value_type)); - type_t *t = parse_type_ast(env, Match(ast, Deserialize)->type); - return Texts("({ ", compile_declaration(t, Text("deserialized")), - ";\n" - "generic_deserialize(", - compile(env, value), ", &deserialized, ", compile_type_info(t), - ");\n" - "deserialized; })"); - } - case ExplicitlyTyped: { - return compile_to_type(env, Match(ast, ExplicitlyTyped)->ast, get_type(env, ast)); - } - case When: { - DeclareMatch(original, ast, When); - ast_t *when_var = WrapAST(ast, Var, .name = "when"); - when_clause_t *new_clauses = NULL; - type_t *subject_t = get_type(env, original->subject); - for (when_clause_t *clause = original->clauses; clause; clause = clause->next) { - type_t *clause_type = get_clause_type(env, subject_t, clause); - if (clause_type->tag == AbortType || clause_type->tag == ReturnType) { - new_clauses = - new (when_clause_t, .pattern = clause->pattern, .body = clause->body, .next = new_clauses); - } else { - ast_t *assign = WrapAST(clause->body, Assign, .targets = new (ast_list_t, .ast = when_var), - .values = new (ast_list_t, .ast = clause->body)); - new_clauses = new (when_clause_t, .pattern = clause->pattern, .body = assign, .next = new_clauses); - } - } - REVERSE_LIST(new_clauses); - ast_t *else_body = original->else_body; - if (else_body) { - type_t *clause_type = get_type(env, else_body); - if (clause_type->tag != AbortType && clause_type->tag != ReturnType) { - else_body = WrapAST(else_body, Assign, .targets = new (ast_list_t, .ast = when_var), - .values = new (ast_list_t, .ast = else_body)); - } - } - - type_t *t = get_type(env, ast); - env_t *when_env = fresh_scope(env); - set_binding(when_env, "when", t, Text("when")); - return Texts("({ ", compile_declaration(t, Text("when")), ";\n", - compile_statement(when_env, WrapAST(ast, When, .subject = original->subject, - .clauses = new_clauses, .else_body = else_body)), - "when; })"); - } - case If: { - DeclareMatch(if_, ast, If); - ast_t *condition = if_->condition; - Text_t decl_code = EMPTY_TEXT; - env_t *truthy_scope = env, *falsey_scope = env; - - Text_t condition_code; - if (condition->tag == Declare) { - DeclareMatch(decl, condition, Declare); - if (decl->value == NULL) code_err(condition, "This declaration must have a value"); - type_t *condition_type = - decl->type ? parse_type_ast(env, decl->type) : get_type(env, Match(condition, Declare)->value); - if (condition_type->tag != OptionalType) - code_err(condition, - "This `if var := ...:` declaration should be an " - "optional " - "type, not ", - type_to_str(condition_type)); - - if (is_incomplete_type(condition_type)) code_err(condition, "This type is incomplete!"); - - decl_code = compile_statement(env, condition); - ast_t *var = Match(condition, Declare)->var; - truthy_scope = fresh_scope(env); - bind_statement(truthy_scope, condition); - condition_code = compile_condition(truthy_scope, var); - set_binding(truthy_scope, Match(var, Var)->name, Match(condition_type, OptionalType)->type, - optional_into_nonnone(condition_type, compile(truthy_scope, var))); - } else if (condition->tag == Var) { - type_t *condition_type = get_type(env, condition); - condition_code = compile_condition(env, condition); - if (condition_type->tag == OptionalType) { - truthy_scope = fresh_scope(env); - set_binding(truthy_scope, Match(condition, Var)->name, Match(condition_type, OptionalType)->type, - optional_into_nonnone(condition_type, compile(truthy_scope, condition))); - } - } else { - condition_code = compile_condition(env, condition); - } - - type_t *true_type = get_type(truthy_scope, if_->body); - type_t *false_type = get_type(falsey_scope, if_->else_body); - if (true_type->tag == AbortType || true_type->tag == ReturnType) - return Texts("({ ", decl_code, "if (", condition_code, ") ", compile_statement(truthy_scope, if_->body), - "\n", compile(falsey_scope, if_->else_body), "; })"); - else if (false_type->tag == AbortType || false_type->tag == ReturnType) - return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", - compile_statement(falsey_scope, if_->else_body), "\n", compile(truthy_scope, if_->body), - "; })"); - else if (decl_code.length > 0) - return Texts("({ ", decl_code, "(", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", - compile(falsey_scope, if_->else_body), ";})"); - else - return Texts("((", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", - compile(falsey_scope, if_->else_body), ")"); - } - case Reduction: { - DeclareMatch(reduction, ast, Reduction); - ast_e op = reduction->op; - - type_t *iter_t = get_type(env, reduction->iter); - type_t *item_t = get_iterated_type(iter_t); - if (!item_t) - code_err(reduction->iter, "I couldn't figure out how to iterate over this type: ", type_to_str(iter_t)); - - static int64_t next_id = 1; - ast_t *item = FakeAST(Var, String("$it", next_id++)); - ast_t *body = LiteralCode(Text("{}")); // placeholder - ast_t *loop = FakeAST(For, .vars = new (ast_list_t, .ast = item), .iter = reduction->iter, .body = body); - env_t *body_scope = for_scope(env, loop); - if (op == Equals || op == NotEquals || op == LessThan || op == LessThanOrEquals || op == GreaterThan - || op == GreaterThanOrEquals) { - // Chained comparisons like ==, <, etc. - 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)); - item_value = reduction->key; - item_value_type = get_type(body_scope, reduction->key); - } - - Text_t code = Texts("({ // Reduction:\n", compile_declaration(item_value_type, Text("prev")), - ";\n" - "OptionalBool_t result = NONE_BOOL;\n"); - - ast_t *comparison = new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = op, - .__data.Plus.lhs = LiteralCode(Text("prev"), .type = item_value_type), - .__data.Plus.rhs = item_value); - body->__data.InlineCCode.chunks = new ( - ast_list_t, .ast = FakeAST(TextLiteral, Texts("if (result == NONE_BOOL) {\n" - " prev = ", - compile(body_scope, item_value), - ";\n" - " result = yes;\n" - "} else {\n" - " if (", - compile(body_scope, comparison), ") {\n", - " prev = ", compile(body_scope, item_value), ";\n", - " } else {\n" - " result = no;\n", - " break;\n", " }\n", "}\n"))); - code = Texts(code, compile_statement(env, loop), "\nresult;})"); - return code; - } else if (op == Min || op == Max) { - // Min/max: - Text_t superlative = op == Min ? Text("min") : Text("max"); - Text_t code = Texts("({ // Reduction:\n", compile_declaration(item_t, superlative), - ";\n" - "Bool_t has_value = no;\n"); - - Text_t item_code = compile(body_scope, item); - 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); - 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"); - - ast_t *comparison = new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = cmp_op, - .__data.Plus.lhs = LiteralCode(Text("key"), .type = key_type), - .__data.Plus.rhs = LiteralCode(superlative_key, .type = key_type)); - - body->__data.InlineCCode.chunks = new ( - ast_list_t, .ast = FakeAST(TextLiteral, Texts(compile_declaration(key_type, Text("key")), " = ", - compile(key_scope, reduction->key), ";\n", - "if (!has_value || ", compile(body_scope, comparison), - ") {\n" - " ", - superlative, " = ", compile(body_scope, item), - ";\n" - " ", - superlative_key, - " = key;\n" - " has_value = yes;\n" - "}\n"))); - } else { - ast_t *comparison = - new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = cmp_op, - .__data.Plus.lhs = item, .__data.Plus.rhs = LiteralCode(superlative, .type = item_t)); - body->__data.InlineCCode.chunks = new ( - ast_list_t, .ast = FakeAST(TextLiteral, Texts("if (!has_value || ", compile(body_scope, comparison), - ") {\n" - " ", - superlative, " = ", compile(body_scope, item), - ";\n" - " has_value = yes;\n" - "}\n"))); - } - - code = Texts(code, compile_statement(env, loop), "\nhas_value ? ", promote_to_optional(item_t, superlative), - " : ", compile_none(item_t), ";})"); - return code; - } else { - // Accumulator-style reductions like +, ++, *, etc. - 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)); - item_value = reduction->key; - } - - Text_t code = Texts("({ // Reduction:\n", compile_declaration(reduction_type, Text("reduction")), - ";\n" - "Bool_t has_value = no;\n"); - - // For the special case of (or)/(and), we need to early out if we - // can: - Text_t early_out = EMPTY_TEXT; - if (op == Compare) { - if (reduction_type->tag != IntType || Match(reduction_type, IntType)->bits != TYPE_IBITS32) - code_err(ast, "<> reductions are only supported for Int32 " - "values"); - } else if (op == And) { - if (reduction_type->tag == BoolType) early_out = Text("if (!reduction) break;"); - else if (reduction_type->tag == OptionalType) - early_out = Texts("if (", check_none(reduction_type, Text("reduction")), ") break;"); - } else if (op == Or) { - if (reduction_type->tag == BoolType) early_out = Text("if (reduction) break;"); - else if (reduction_type->tag == OptionalType) - early_out = Texts("if (!", check_none(reduction_type, Text("reduction")), ") break;"); - } - - ast_t *combination = new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = op, - .__data.Plus.lhs = LiteralCode(Text("reduction"), .type = reduction_type), - .__data.Plus.rhs = item_value); - body->__data.InlineCCode.chunks = - new (ast_list_t, - .ast = FakeAST(TextLiteral, Texts("if (!has_value) {\n" - " reduction = ", - compile(body_scope, item_value), - ";\n" - " has_value = yes;\n" - "} else {\n" - " reduction = ", - compile(body_scope, combination), ";\n", early_out, "}\n"))); - - code = Texts(code, compile_statement(env, loop), "\nhas_value ? ", - promote_to_optional(reduction_type, Text("reduction")), " : ", compile_none(reduction_type), - ";})"); - return code; - } - } - case FieldAccess: { - DeclareMatch(f, ast, FieldAccess); - type_t *fielded_t = get_type(env, f->fielded); - type_t *value_t = value_type(fielded_t); - switch (value_t->tag) { - case TypeInfoType: { - DeclareMatch(info, value_t, TypeInfoType); - if (f->field[0] == '_') { - if (!type_eq(env->current_type, info->type)) - code_err(ast, "Fields that start with underscores are not " - "accessible " - "on types outside of the type definition."); - } - binding_t *b = get_binding(info->env, f->field); - if (!b) code_err(ast, "I couldn't find the field '", f->field, "' on this type"); - if (b->code.length == 0) code_err(ast, "I couldn't figure out how to compile this field"); - return b->code; - } - case TextType: { - const char *lang = Match(value_t, TextType)->lang; - if (lang && streq(f->field, "text")) { - Text_t text = compile_to_pointer_depth(env, f->fielded, 0, false); - return Texts("((Text_t)", text, ")"); - } else if (streq(f->field, "length")) { - return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)"); - } - code_err(ast, "There is no '", f->field, "' field on ", type_to_str(value_t), " values"); - } - case StructType: { - return compile_struct_field_access(env, ast); - } - case EnumType: { - return compile_enum_field_access(env, ast); - } - case ListType: { - if (streq(f->field, "length")) - return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)"); - code_err(ast, "There is no ", f->field, " field on lists"); - } - case SetType: { - if (streq(f->field, "items")) - return Texts("LIST_COPY((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries)"); - else if (streq(f->field, "length")) - return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), - ").entries.length)"); - code_err(ast, "There is no '", f->field, "' field on sets"); - } - case TableType: { - if (streq(f->field, "length")) { - return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), - ").entries.length)"); - } else if (streq(f->field, "keys")) { - return Texts("LIST_COPY((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries)"); - } else if (streq(f->field, "values")) { - DeclareMatch(table, value_t, TableType); - Text_t offset = Texts("offsetof(struct { ", compile_declaration(table->key_type, Text("k")), "; ", - compile_declaration(table->value_type, Text("v")), "; }, v)"); - return Texts("({ List_t *entries = &(", compile_to_pointer_depth(env, f->fielded, 0, false), - ").entries;\n" - "LIST_INCREF(*entries);\n" - "List_t values = *entries;\n" - "values.data += ", - offset, - ";\n" - "values; })"); - } else if (streq(f->field, "fallback")) { - return Texts("({ Table_t *_fallback = (", compile_to_pointer_depth(env, f->fielded, 0, false), - ").fallback; _fallback ? *_fallback : NONE_TABLE; })"); - } - code_err(ast, "There is no '", f->field, "' field on tables"); - } - case ModuleType: { - const char *name = Match(value_t, ModuleType)->name; - env_t *module_env = Table$str_get(*env->imports, name); - return compile(module_env, WrapAST(ast, Var, f->field)); - } - default: code_err(ast, "Field accesses are not supported on ", type_to_str(fielded_t), " values"); - } - } - case Index: { - DeclareMatch(indexing, ast, Index); - type_t *indexed_type = get_type(env, indexing->indexed); - if (!indexing->index) { - if (indexed_type->tag != PointerType) - code_err(ast, "Only pointers can use the '[]' operator to " - "dereference " - "the entire value."); - DeclareMatch(ptr, indexed_type, PointerType); - if (ptr->pointed->tag == ListType) { - return Texts("*({ List_t *list = ", compile(env, indexing->indexed), "; LIST_INCREF(*list); list; })"); - } else if (ptr->pointed->tag == TableType || ptr->pointed->tag == SetType) { - return Texts("*({ Table_t *t = ", compile(env, indexing->indexed), "; TABLE_INCREF(*t); t; })"); - } else { - return Texts("*(", compile(env, indexing->indexed), ")"); - } - } - - type_t *container_t = value_type(indexed_type); - type_t *index_t = get_type(env, indexing->index); - if (container_t->tag == ListType) { - if (index_t->tag != IntType && index_t->tag != BigIntType && index_t->tag != ByteType) - code_err(indexing->index, "Lists can only be indexed by integers, not ", type_to_str(index_t)); - type_t *item_type = Match(container_t, ListType)->item_type; - Text_t list = compile_to_pointer_depth(env, indexing->indexed, 0, false); - file_t *f = indexing->index->file; - Text_t index_code = - indexing->index->tag == Int - ? compile_int_to_type(env, indexing->index, Type(IntType, .bits = TYPE_IBITS64)) - : (index_t->tag == BigIntType ? Texts("Int64$from_int(", compile(env, indexing->index), ", no)") - : Texts("(Int64_t)(", compile(env, indexing->index), ")")); - if (indexing->unchecked) - 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)), ")"); - } else if (container_t->tag == TableType) { - DeclareMatch(table_type, container_t, TableType); - if (indexing->unchecked) code_err(ast, "Table indexes cannot be unchecked"); - if (table_type->default_value) { - return Texts("Table$get_or_default(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ", - compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ", - compile(env, indexing->index), ", ", - compile_to_type(env, table_type->default_value, table_type->value_type), ", ", - compile_type_info(container_t), ")"); - } else { - return Texts("Table$get_optional(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ", - compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ", - compile(env, indexing->index), - ", " - "_, ", - promote_to_optional(table_type->value_type, Text("(*_)")), ", ", - compile_none(table_type->value_type), ", ", compile_type_info(container_t), ")"); - } - } else if (container_t->tag == TextType) { - return Texts("Text$cluster(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ", - compile_to_type(env, indexing->index, Type(BigIntType)), ")"); - } else { - code_err(ast, "Indexing is not supported for type: ", type_to_str(container_t)); - } - } - case InlineCCode: { - type_t *t = get_type(env, ast); - 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!"); - case Defer: code_err(ast, "Compiling 'defer' as expression!"); - case Extern: code_err(ast, "Externs are not supported as expressions"); - case TableEntry: code_err(ast, "Table entries should not be compiled directly"); - case Declare: - case Assign: - case UPDATE_CASES: - case For: - case While: - case Repeat: - case StructDef: - case LangDef: - case Extend: - case EnumDef: - case FunctionDef: - case ConvertDef: - case Skip: - case Stop: - case Pass: - case Return: - case DocTest: - case Assert: code_err(ast, "This is not a valid expression"); - case Unknown: - default: code_err(ast, "Unknown AST: ", ast_to_sexp_str(ast)); - } - return EMPTY_TEXT; -} diff --git a/src/compile.h b/src/compile.h deleted file mode 100644 index 18143e25..00000000 --- a/src/compile.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -// Compilation functions - -#include "ast.h" -#include "environment.h" -#include "stdlib/datatypes.h" -#include "types.h" - -Text_t compile(env_t *env, ast_t *ast); -Text_t compile_empty(type_t *t); -Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t); diff --git a/src/compile/assignments.c b/src/compile/assignments.c index c7e61e26..e0bbc9fe 100644 --- a/src/compile/assignments.c +++ b/src/compile/assignments.c @@ -1,7 +1,7 @@ // This file defines how to compile assignments #include "assignments.h" #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/binops.c b/src/compile/binops.c index 38fb52db..469ef60c 100644 --- a/src/compile/binops.c +++ b/src/compile/binops.c @@ -1,7 +1,7 @@ // This file defines how to compile binary operations #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/blocks.c b/src/compile/blocks.c index eea73562..c533c801 100644 --- a/src/compile/blocks.c +++ b/src/compile/blocks.c @@ -2,7 +2,7 @@ #include "blocks.h" #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/cli.c b/src/compile/cli.c index cccee5a5..d9299bae 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -1,6 +1,6 @@ // This file defines how to compile CLI argument parsing -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/declarations.c b/src/compile/declarations.c index cb961db0..8ea58ce2 100644 --- a/src/compile/declarations.c +++ b/src/compile/declarations.c @@ -1,6 +1,6 @@ // This file defines how to compile variable declarations #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/enums.c b/src/compile/enums.c index 12b58b26..85b29e4b 100644 --- a/src/compile/enums.c +++ b/src/compile/enums.c @@ -1,7 +1,7 @@ // This file defines how to compile enums #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../naming.h" #include "../stdlib/tables.h" diff --git a/src/compile/expressions.c b/src/compile/expressions.c new file mode 100644 index 00000000..28db606b --- /dev/null +++ b/src/compile/expressions.c @@ -0,0 +1,732 @@ +// This file defines logic for compiling expressions + +#include "expressions.h" +#include "../ast.h" +#include "../config.h" +#include "../environment.h" +#include "../stdlib/tables.h" +#include "../stdlib/text.h" +#include "../stdlib/util.h" +#include "../typecheck.h" +#include "binops.h" +#include "blocks.h" +#include "declarations.h" +#include "enums.h" +#include "functions.h" +#include "integers.h" +#include "lists.h" +#include "optionals.h" +#include "pointers.h" +#include "promotions.h" +#include "sets.h" +#include "statements.h" +#include "structs.h" +#include "tables.h" +#include "text.h" +#include "types.h" + +public +Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t) { + if (is_idempotent(ast) && can_be_mutated(env, ast)) { + if (t->tag == ListType) return Texts("LIST_COPY(", compile_to_type(env, ast, t), ")"); + else if (t->tag == TableType || t->tag == SetType) + return Texts("TABLE_COPY(", compile_to_type(env, ast, t), ")"); + } + return compile_to_type(env, ast, t); +} + +public +Text_t compile_empty(type_t *t) { + if (t == NULL) compiler_err(NULL, NULL, NULL, "I can't compile a value with no type"); + + if (t->tag == OptionalType) return compile_none(t); + + if (t == PATH_TYPE) return Text("NONE_PATH"); + else if (t == PATH_TYPE_TYPE) return Text("((OptionalPathType_t){})"); + + switch (t->tag) { + case BigIntType: return Text("I(0)"); + case IntType: { + switch (Match(t, IntType)->bits) { + case TYPE_IBITS8: return Text("I8(0)"); + case TYPE_IBITS16: return Text("I16(0)"); + case TYPE_IBITS32: return Text("I32(0)"); + case TYPE_IBITS64: return Text("I64(0)"); + default: errx(1, "Invalid integer bit size"); + } + break; + } + case ByteType: return Text("((Byte_t)0)"); + case BoolType: return Text("((Bool_t)no)"); + case ListType: return Text("((List_t){})"); + case TableType: + case SetType: return Text("((Table_t){})"); + case TextType: return Text("Text(\"\")"); + case CStringType: return Text("\"\""); + case PointerType: { + DeclareMatch(ptr, t, PointerType); + Text_t empty_pointed = compile_empty(ptr->pointed); + return empty_pointed.length == 0 ? EMPTY_TEXT + : Texts(ptr->is_stack ? Text("stack(") : Text("heap("), empty_pointed, ")"); + } + case NumType: { + return Match(t, NumType)->bits == TYPE_NBITS32 ? Text("N32(0.0f)") : Text("N64(0.0)"); + } + case StructType: return compile_empty_struct(t); + case EnumType: return compile_empty_enum(t); + default: return EMPTY_TEXT; + } + return EMPTY_TEXT; +} + +Text_t compile(env_t *env, ast_t *ast) { + switch (ast->tag) { + case None: { + code_err(ast, "I can't figure out what this `none`'s type is!"); + } + case Bool: return Match(ast, Bool)->b ? Text("yes") : Text("no"); + case Var: { + binding_t *b = get_binding(env, Match(ast, Var)->name); + if (b) return b->code.length > 0 ? b->code : Texts("_$", Match(ast, Var)->name); + // return Texts("_$", Match(ast, Var)->name); + code_err(ast, "I don't know of any variable by this name"); + } + case Int: return compile_int(ast); + case Num: { + return Text$from_str(String(hex_double(Match(ast, Num)->n))); + } + case Not: { + ast_t *value = Match(ast, Not)->value; + type_t *t = get_type(env, value); + + binding_t *b = get_namespace_binding(env, value, "negated"); + if (b && b->type->tag == FunctionType) { + DeclareMatch(fn, b->type, FunctionType); + if (fn->args && can_compile_to_type(env, value, get_arg_type(env, fn->args))) + return Texts(b->code, "(", compile_arguments(env, ast, fn->args, new (arg_ast_t, .value = value)), ")"); + } + + if (t->tag == BoolType) return Texts("!(", compile(env, value), ")"); + else if (t->tag == IntType || t->tag == ByteType) return Texts("~(", compile(env, value), ")"); + else if (t->tag == ListType) return Texts("((", compile(env, value), ").length == 0)"); + else if (t->tag == SetType || t->tag == TableType) + return Texts("((", compile(env, value), ").entries.length == 0)"); + else if (t->tag == TextType) return Texts("(", compile(env, value), ".length == 0)"); + else if (t->tag == OptionalType) return check_none(t, compile(env, value)); + + code_err(ast, "I don't know how to negate values of type ", type_to_str(t)); + } + case Negative: { + ast_t *value = Match(ast, Negative)->value; + type_t *t = get_type(env, value); + binding_t *b = get_namespace_binding(env, value, "negative"); + if (b && b->type->tag == FunctionType) { + DeclareMatch(fn, b->type, FunctionType); + if (fn->args && can_compile_to_type(env, value, get_arg_type(env, fn->args))) + return Texts(b->code, "(", compile_arguments(env, ast, fn->args, new (arg_ast_t, .value = value)), ")"); + } + + if (t->tag == IntType || t->tag == NumType) return Texts("-(", compile(env, value), ")"); + + code_err(ast, "I don't know how to get the negative value of type ", type_to_str(t)); + } + case HeapAllocate: + case StackReference: return compile_typed_allocation(env, ast, get_type(env, ast)); + case Optional: return compile_optional(env, ast); + case NonOptional: return compile_non_optional(env, ast); + case Power: + case Multiply: + case Divide: + case Mod: + case Mod1: + case Plus: + case Minus: + case Concat: + case LeftShift: + case UnsignedLeftShift: + case RightShift: + case UnsignedRightShift: + case And: + case Or: + case Xor: { + return compile_binary_op(env, ast); + } + case Equals: + case NotEquals: { + binary_operands_t binop = BINARY_OPERANDS(ast); + + type_t *lhs_t = get_type(env, binop.lhs); + type_t *rhs_t = get_type(env, binop.rhs); + type_t *operand_t; + if (binop.lhs->tag == Int && is_numeric_type(rhs_t)) { + operand_t = rhs_t; + } else if (binop.rhs->tag == Int && is_numeric_type(lhs_t)) { + operand_t = lhs_t; + } else if (can_compile_to_type(env, binop.rhs, lhs_t)) { + operand_t = lhs_t; + } else if (can_compile_to_type(env, binop.lhs, rhs_t)) { + operand_t = rhs_t; + } else { + code_err(ast, "I can't do comparisons between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t)); + } + + Text_t lhs, rhs; + lhs = compile_to_type(env, binop.lhs, operand_t); + rhs = compile_to_type(env, binop.rhs, operand_t); + + switch (operand_t->tag) { + case BigIntType: + return Texts(ast->tag == Equals ? EMPTY_TEXT : Text("!"), "Int$equal_value(", lhs, ", ", rhs, ")"); + case BoolType: + case ByteType: + case IntType: + case NumType: + case PointerType: + case FunctionType: return Texts("(", lhs, ast->tag == Equals ? " == " : " != ", rhs, ")"); + default: + return Texts(ast->tag == Equals ? EMPTY_TEXT : Text("!"), "generic_equal(stack(", lhs, "), stack(", rhs, + "), ", compile_type_info(operand_t), ")"); + } + } + case LessThan: + case LessThanOrEquals: + case GreaterThan: + case GreaterThanOrEquals: + case Compare: { + binary_operands_t cmp = BINARY_OPERANDS(ast); + + type_t *lhs_t = get_type(env, cmp.lhs); + type_t *rhs_t = get_type(env, cmp.rhs); + type_t *operand_t; + if (cmp.lhs->tag == Int && is_numeric_type(rhs_t)) { + operand_t = rhs_t; + } else if (cmp.rhs->tag == Int && is_numeric_type(lhs_t)) { + operand_t = lhs_t; + } else if (can_compile_to_type(env, cmp.rhs, lhs_t)) { + operand_t = lhs_t; + } else if (can_compile_to_type(env, cmp.lhs, rhs_t)) { + operand_t = rhs_t; + } else { + code_err(ast, "I can't do comparisons between ", type_to_str(lhs_t), " and ", type_to_str(rhs_t)); + } + + Text_t lhs = compile_to_type(env, cmp.lhs, operand_t); + Text_t rhs = compile_to_type(env, cmp.rhs, operand_t); + + if (ast->tag == Compare) + return Texts("generic_compare(stack(", lhs, "), stack(", rhs, "), ", compile_type_info(operand_t), ")"); + + const char *op = binop_operator(ast->tag); + switch (operand_t->tag) { + case BigIntType: return Texts("(Int$compare_value(", lhs, ", ", rhs, ") ", op, " 0)"); + case BoolType: + case ByteType: + case IntType: + case NumType: + case PointerType: + case FunctionType: return Texts("(", lhs, " ", op, " ", rhs, ")"); + default: + return Texts("(generic_compare(stack(", lhs, "), stack(", rhs, "), ", compile_type_info(operand_t), ") ", + op, " 0)"); + } + } + case TextLiteral: + case TextJoin: return compile_text_ast(env, ast); + case Path: { + return Texts("Path(", compile_text_literal(Text$from_str(Match(ast, Path)->path)), ")"); + } + case Block: return compile_block_expression(env, ast); + case Min: + case Max: { + type_t *t = get_type(env, 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 = "$"; + if (key == NULL) key = FakeAST(Var, key_name); + + env_t *expr_env = fresh_scope(env); + set_binding(expr_env, key_name, t, Text("ternary$lhs")); + Text_t lhs_key = compile(expr_env, key); + + set_binding(expr_env, key_name, t, Text("ternary$rhs")); + Text_t rhs_key = compile(expr_env, key); + + type_t *key_t = get_type(expr_env, key); + Text_t comparison; + if (key_t->tag == BigIntType) + comparison = + Texts("(Int$compare_value(", lhs_key, ", ", rhs_key, ")", (ast->tag == Min ? "<=" : ">="), "0)"); + else if (key_t->tag == IntType || key_t->tag == NumType || key_t->tag == BoolType || key_t->tag == PointerType + || key_t->tag == ByteType) + comparison = Texts("((", lhs_key, ")", (ast->tag == Min ? "<=" : ">="), "(", rhs_key, "))"); + else + comparison = Texts("generic_compare(stack(", lhs_key, "), stack(", rhs_key, "), ", compile_type_info(key_t), + ")", (ast->tag == Min ? "<=" : ">="), "0"); + + return Texts("({\n", compile_type(t), " ternary$lhs = ", compile(env, lhs), + ", ternary$rhs = ", compile(env, rhs), ";\n", comparison, + " ? ternary$lhs : ternary$rhs;\n" + "})"); + } + case List: { + DeclareMatch(list, ast, List); + if (!list->items) return Text("(List_t){.length=0}"); + + type_t *list_type = get_type(env, ast); + return compile_typed_list(env, ast, list_type); + } + case Table: { + DeclareMatch(table, ast, Table); + if (!table->entries) { + Text_t code = Text("((Table_t){"); + if (table->fallback) code = Texts(code, ".fallback=heap(", compile(env, table->fallback), ")"); + return Texts(code, "})"); + } + + type_t *table_type = get_type(env, ast); + return compile_typed_table(env, ast, table_type); + } + case Set: { + DeclareMatch(set, ast, Set); + if (!set->items) return Text("((Table_t){})"); + + type_t *set_type = get_type(env, ast); + return compile_typed_set(env, ast, set_type); + } + case Comprehension: { + ast_t *base = Match(ast, Comprehension)->expr; + while (base->tag == Comprehension) + base = Match(ast, Comprehension)->expr; + if (base->tag == TableEntry) return compile(env, WrapAST(ast, Table, .entries = new (ast_list_t, .ast = ast))); + else return compile(env, WrapAST(ast, List, .items = new (ast_list_t, .ast = ast))); + } + case Lambda: return compile_lambda(env, ast); + case MethodCall: return compile_method_call(env, ast); + case FunctionCall: return compile_function_call(env, ast); + case Deserialize: { + ast_t *value = Match(ast, Deserialize)->value; + type_t *value_type = get_type(env, value); + if (!type_eq(value_type, Type(ListType, Type(ByteType)))) + code_err(value, "This value should be a list of bytes, not a ", type_to_str(value_type)); + type_t *t = parse_type_ast(env, Match(ast, Deserialize)->type); + return Texts("({ ", compile_declaration(t, Text("deserialized")), + ";\n" + "generic_deserialize(", + compile(env, value), ", &deserialized, ", compile_type_info(t), + ");\n" + "deserialized; })"); + } + case ExplicitlyTyped: { + return compile_to_type(env, Match(ast, ExplicitlyTyped)->ast, get_type(env, ast)); + } + case When: { + DeclareMatch(original, ast, When); + ast_t *when_var = WrapAST(ast, Var, .name = "when"); + when_clause_t *new_clauses = NULL; + type_t *subject_t = get_type(env, original->subject); + for (when_clause_t *clause = original->clauses; clause; clause = clause->next) { + type_t *clause_type = get_clause_type(env, subject_t, clause); + if (clause_type->tag == AbortType || clause_type->tag == ReturnType) { + new_clauses = + new (when_clause_t, .pattern = clause->pattern, .body = clause->body, .next = new_clauses); + } else { + ast_t *assign = WrapAST(clause->body, Assign, .targets = new (ast_list_t, .ast = when_var), + .values = new (ast_list_t, .ast = clause->body)); + new_clauses = new (when_clause_t, .pattern = clause->pattern, .body = assign, .next = new_clauses); + } + } + REVERSE_LIST(new_clauses); + ast_t *else_body = original->else_body; + if (else_body) { + type_t *clause_type = get_type(env, else_body); + if (clause_type->tag != AbortType && clause_type->tag != ReturnType) { + else_body = WrapAST(else_body, Assign, .targets = new (ast_list_t, .ast = when_var), + .values = new (ast_list_t, .ast = else_body)); + } + } + + type_t *t = get_type(env, ast); + env_t *when_env = fresh_scope(env); + set_binding(when_env, "when", t, Text("when")); + return Texts("({ ", compile_declaration(t, Text("when")), ";\n", + compile_statement(when_env, WrapAST(ast, When, .subject = original->subject, + .clauses = new_clauses, .else_body = else_body)), + "when; })"); + } + case If: { + DeclareMatch(if_, ast, If); + ast_t *condition = if_->condition; + Text_t decl_code = EMPTY_TEXT; + env_t *truthy_scope = env, *falsey_scope = env; + + Text_t condition_code; + if (condition->tag == Declare) { + DeclareMatch(decl, condition, Declare); + if (decl->value == NULL) code_err(condition, "This declaration must have a value"); + type_t *condition_type = + decl->type ? parse_type_ast(env, decl->type) : get_type(env, Match(condition, Declare)->value); + if (condition_type->tag != OptionalType) + code_err(condition, + "This `if var := ...:` declaration should be an " + "optional " + "type, not ", + type_to_str(condition_type)); + + if (is_incomplete_type(condition_type)) code_err(condition, "This type is incomplete!"); + + decl_code = compile_statement(env, condition); + ast_t *var = Match(condition, Declare)->var; + truthy_scope = fresh_scope(env); + bind_statement(truthy_scope, condition); + condition_code = compile_condition(truthy_scope, var); + set_binding(truthy_scope, Match(var, Var)->name, Match(condition_type, OptionalType)->type, + optional_into_nonnone(condition_type, compile(truthy_scope, var))); + } else if (condition->tag == Var) { + type_t *condition_type = get_type(env, condition); + condition_code = compile_condition(env, condition); + if (condition_type->tag == OptionalType) { + truthy_scope = fresh_scope(env); + set_binding(truthy_scope, Match(condition, Var)->name, Match(condition_type, OptionalType)->type, + optional_into_nonnone(condition_type, compile(truthy_scope, condition))); + } + } else { + condition_code = compile_condition(env, condition); + } + + type_t *true_type = get_type(truthy_scope, if_->body); + type_t *false_type = get_type(falsey_scope, if_->else_body); + if (true_type->tag == AbortType || true_type->tag == ReturnType) + return Texts("({ ", decl_code, "if (", condition_code, ") ", compile_statement(truthy_scope, if_->body), + "\n", compile(falsey_scope, if_->else_body), "; })"); + else if (false_type->tag == AbortType || false_type->tag == ReturnType) + return Texts("({ ", decl_code, "if (!(", condition_code, ")) ", + compile_statement(falsey_scope, if_->else_body), "\n", compile(truthy_scope, if_->body), + "; })"); + else if (decl_code.length > 0) + return Texts("({ ", decl_code, "(", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", + compile(falsey_scope, if_->else_body), ";})"); + else + return Texts("((", condition_code, ") ? ", compile(truthy_scope, if_->body), " : ", + compile(falsey_scope, if_->else_body), ")"); + } + case Reduction: { + DeclareMatch(reduction, ast, Reduction); + ast_e op = reduction->op; + + type_t *iter_t = get_type(env, reduction->iter); + type_t *item_t = get_iterated_type(iter_t); + if (!item_t) + code_err(reduction->iter, "I couldn't figure out how to iterate over this type: ", type_to_str(iter_t)); + + static int64_t next_id = 1; + ast_t *item = FakeAST(Var, String("$it", next_id++)); + ast_t *body = LiteralCode(Text("{}")); // placeholder + ast_t *loop = FakeAST(For, .vars = new (ast_list_t, .ast = item), .iter = reduction->iter, .body = body); + env_t *body_scope = for_scope(env, loop); + if (op == Equals || op == NotEquals || op == LessThan || op == LessThanOrEquals || op == GreaterThan + || op == GreaterThanOrEquals) { + // Chained comparisons like ==, <, etc. + 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)); + item_value = reduction->key; + item_value_type = get_type(body_scope, reduction->key); + } + + Text_t code = Texts("({ // Reduction:\n", compile_declaration(item_value_type, Text("prev")), + ";\n" + "OptionalBool_t result = NONE_BOOL;\n"); + + ast_t *comparison = new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = op, + .__data.Plus.lhs = LiteralCode(Text("prev"), .type = item_value_type), + .__data.Plus.rhs = item_value); + body->__data.InlineCCode.chunks = new ( + ast_list_t, .ast = FakeAST(TextLiteral, Texts("if (result == NONE_BOOL) {\n" + " prev = ", + compile(body_scope, item_value), + ";\n" + " result = yes;\n" + "} else {\n" + " if (", + compile(body_scope, comparison), ") {\n", + " prev = ", compile(body_scope, item_value), ";\n", + " } else {\n" + " result = no;\n", + " break;\n", " }\n", "}\n"))); + code = Texts(code, compile_statement(env, loop), "\nresult;})"); + return code; + } else if (op == Min || op == Max) { + // Min/max: + Text_t superlative = op == Min ? Text("min") : Text("max"); + Text_t code = Texts("({ // Reduction:\n", compile_declaration(item_t, superlative), + ";\n" + "Bool_t has_value = no;\n"); + + Text_t item_code = compile(body_scope, item); + 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); + 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"); + + ast_t *comparison = new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = cmp_op, + .__data.Plus.lhs = LiteralCode(Text("key"), .type = key_type), + .__data.Plus.rhs = LiteralCode(superlative_key, .type = key_type)); + + body->__data.InlineCCode.chunks = new ( + ast_list_t, .ast = FakeAST(TextLiteral, Texts(compile_declaration(key_type, Text("key")), " = ", + compile(key_scope, reduction->key), ";\n", + "if (!has_value || ", compile(body_scope, comparison), + ") {\n" + " ", + superlative, " = ", compile(body_scope, item), + ";\n" + " ", + superlative_key, + " = key;\n" + " has_value = yes;\n" + "}\n"))); + } else { + ast_t *comparison = + new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = cmp_op, + .__data.Plus.lhs = item, .__data.Plus.rhs = LiteralCode(superlative, .type = item_t)); + body->__data.InlineCCode.chunks = new ( + ast_list_t, .ast = FakeAST(TextLiteral, Texts("if (!has_value || ", compile(body_scope, comparison), + ") {\n" + " ", + superlative, " = ", compile(body_scope, item), + ";\n" + " has_value = yes;\n" + "}\n"))); + } + + code = Texts(code, compile_statement(env, loop), "\nhas_value ? ", promote_to_optional(item_t, superlative), + " : ", compile_none(item_t), ";})"); + return code; + } else { + // Accumulator-style reductions like +, ++, *, etc. + 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)); + item_value = reduction->key; + } + + Text_t code = Texts("({ // Reduction:\n", compile_declaration(reduction_type, Text("reduction")), + ";\n" + "Bool_t has_value = no;\n"); + + // For the special case of (or)/(and), we need to early out if we + // can: + Text_t early_out = EMPTY_TEXT; + if (op == Compare) { + if (reduction_type->tag != IntType || Match(reduction_type, IntType)->bits != TYPE_IBITS32) + code_err(ast, "<> reductions are only supported for Int32 " + "values"); + } else if (op == And) { + if (reduction_type->tag == BoolType) early_out = Text("if (!reduction) break;"); + else if (reduction_type->tag == OptionalType) + early_out = Texts("if (", check_none(reduction_type, Text("reduction")), ") break;"); + } else if (op == Or) { + if (reduction_type->tag == BoolType) early_out = Text("if (reduction) break;"); + else if (reduction_type->tag == OptionalType) + early_out = Texts("if (!", check_none(reduction_type, Text("reduction")), ") break;"); + } + + ast_t *combination = new (ast_t, .file = ast->file, .start = ast->start, .end = ast->end, .tag = op, + .__data.Plus.lhs = LiteralCode(Text("reduction"), .type = reduction_type), + .__data.Plus.rhs = item_value); + body->__data.InlineCCode.chunks = + new (ast_list_t, + .ast = FakeAST(TextLiteral, Texts("if (!has_value) {\n" + " reduction = ", + compile(body_scope, item_value), + ";\n" + " has_value = yes;\n" + "} else {\n" + " reduction = ", + compile(body_scope, combination), ";\n", early_out, "}\n"))); + + code = Texts(code, compile_statement(env, loop), "\nhas_value ? ", + promote_to_optional(reduction_type, Text("reduction")), " : ", compile_none(reduction_type), + ";})"); + return code; + } + } + case FieldAccess: { + DeclareMatch(f, ast, FieldAccess); + type_t *fielded_t = get_type(env, f->fielded); + type_t *value_t = value_type(fielded_t); + switch (value_t->tag) { + case TypeInfoType: { + DeclareMatch(info, value_t, TypeInfoType); + if (f->field[0] == '_') { + if (!type_eq(env->current_type, info->type)) + code_err(ast, "Fields that start with underscores are not " + "accessible " + "on types outside of the type definition."); + } + binding_t *b = get_binding(info->env, f->field); + if (!b) code_err(ast, "I couldn't find the field '", f->field, "' on this type"); + if (b->code.length == 0) code_err(ast, "I couldn't figure out how to compile this field"); + return b->code; + } + case TextType: { + const char *lang = Match(value_t, TextType)->lang; + if (lang && streq(f->field, "text")) { + Text_t text = compile_to_pointer_depth(env, f->fielded, 0, false); + return Texts("((Text_t)", text, ")"); + } else if (streq(f->field, "length")) { + return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)"); + } + code_err(ast, "There is no '", f->field, "' field on ", type_to_str(value_t), " values"); + } + case StructType: { + return compile_struct_field_access(env, ast); + } + case EnumType: { + return compile_enum_field_access(env, ast); + } + case ListType: { + if (streq(f->field, "length")) + return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), ").length)"); + code_err(ast, "There is no ", f->field, " field on lists"); + } + case SetType: { + if (streq(f->field, "items")) + return Texts("LIST_COPY((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries)"); + else if (streq(f->field, "length")) + return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), + ").entries.length)"); + code_err(ast, "There is no '", f->field, "' field on sets"); + } + case TableType: { + if (streq(f->field, "length")) { + return Texts("Int$from_int64((", compile_to_pointer_depth(env, f->fielded, 0, false), + ").entries.length)"); + } else if (streq(f->field, "keys")) { + return Texts("LIST_COPY((", compile_to_pointer_depth(env, f->fielded, 0, false), ").entries)"); + } else if (streq(f->field, "values")) { + DeclareMatch(table, value_t, TableType); + Text_t offset = Texts("offsetof(struct { ", compile_declaration(table->key_type, Text("k")), "; ", + compile_declaration(table->value_type, Text("v")), "; }, v)"); + return Texts("({ List_t *entries = &(", compile_to_pointer_depth(env, f->fielded, 0, false), + ").entries;\n" + "LIST_INCREF(*entries);\n" + "List_t values = *entries;\n" + "values.data += ", + offset, + ";\n" + "values; })"); + } else if (streq(f->field, "fallback")) { + return Texts("({ Table_t *_fallback = (", compile_to_pointer_depth(env, f->fielded, 0, false), + ").fallback; _fallback ? *_fallback : NONE_TABLE; })"); + } + code_err(ast, "There is no '", f->field, "' field on tables"); + } + case ModuleType: { + const char *name = Match(value_t, ModuleType)->name; + env_t *module_env = Table$str_get(*env->imports, name); + return compile(module_env, WrapAST(ast, Var, f->field)); + } + default: code_err(ast, "Field accesses are not supported on ", type_to_str(fielded_t), " values"); + } + } + case Index: { + DeclareMatch(indexing, ast, Index); + type_t *indexed_type = get_type(env, indexing->indexed); + if (!indexing->index) { + if (indexed_type->tag != PointerType) + code_err(ast, "Only pointers can use the '[]' operator to " + "dereference " + "the entire value."); + DeclareMatch(ptr, indexed_type, PointerType); + if (ptr->pointed->tag == ListType) { + return Texts("*({ List_t *list = ", compile(env, indexing->indexed), "; LIST_INCREF(*list); list; })"); + } else if (ptr->pointed->tag == TableType || ptr->pointed->tag == SetType) { + return Texts("*({ Table_t *t = ", compile(env, indexing->indexed), "; TABLE_INCREF(*t); t; })"); + } else { + return Texts("*(", compile(env, indexing->indexed), ")"); + } + } + + type_t *container_t = value_type(indexed_type); + type_t *index_t = get_type(env, indexing->index); + if (container_t->tag == ListType) { + if (index_t->tag != IntType && index_t->tag != BigIntType && index_t->tag != ByteType) + code_err(indexing->index, "Lists can only be indexed by integers, not ", type_to_str(index_t)); + type_t *item_type = Match(container_t, ListType)->item_type; + Text_t list = compile_to_pointer_depth(env, indexing->indexed, 0, false); + file_t *f = indexing->index->file; + Text_t index_code = + indexing->index->tag == Int + ? compile_int_to_type(env, indexing->index, Type(IntType, .bits = TYPE_IBITS64)) + : (index_t->tag == BigIntType ? Texts("Int64$from_int(", compile(env, indexing->index), ", no)") + : Texts("(Int64_t)(", compile(env, indexing->index), ")")); + if (indexing->unchecked) + 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)), ")"); + } else if (container_t->tag == TableType) { + DeclareMatch(table_type, container_t, TableType); + if (indexing->unchecked) code_err(ast, "Table indexes cannot be unchecked"); + if (table_type->default_value) { + return Texts("Table$get_or_default(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ", + compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ", + compile(env, indexing->index), ", ", + compile_to_type(env, table_type->default_value, table_type->value_type), ", ", + compile_type_info(container_t), ")"); + } else { + return Texts("Table$get_optional(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ", + compile_type(table_type->key_type), ", ", compile_type(table_type->value_type), ", ", + compile(env, indexing->index), + ", " + "_, ", + promote_to_optional(table_type->value_type, Text("(*_)")), ", ", + compile_none(table_type->value_type), ", ", compile_type_info(container_t), ")"); + } + } else if (container_t->tag == TextType) { + return Texts("Text$cluster(", compile_to_pointer_depth(env, indexing->indexed, 0, false), ", ", + compile_to_type(env, indexing->index, Type(BigIntType)), ")"); + } else { + code_err(ast, "Indexing is not supported for type: ", type_to_str(container_t)); + } + } + case InlineCCode: { + type_t *t = get_type(env, ast); + 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!"); + case Defer: code_err(ast, "Compiling 'defer' as expression!"); + case Extern: code_err(ast, "Externs are not supported as expressions"); + case TableEntry: code_err(ast, "Table entries should not be compiled directly"); + case Declare: + case Assign: + case UPDATE_CASES: + case For: + case While: + case Repeat: + case StructDef: + case LangDef: + case Extend: + case EnumDef: + case FunctionDef: + case ConvertDef: + case Skip: + case Stop: + case Pass: + case Return: + case DocTest: + case Assert: code_err(ast, "This is not a valid expression"); + case Unknown: + default: code_err(ast, "Unknown AST: ", ast_to_sexp_str(ast)); + } + return EMPTY_TEXT; +} diff --git a/src/compile/expressions.h b/src/compile/expressions.h new file mode 100644 index 00000000..86bc110f --- /dev/null +++ b/src/compile/expressions.h @@ -0,0 +1,11 @@ +// This file defines logic for compiling expressions +#pragma once + +#include "../ast.h" +#include "../environment.h" +#include "../stdlib/datatypes.h" +#include "../types.h" + +Text_t compile(env_t *env, ast_t *ast); +Text_t compile_empty(type_t *t); +Text_t compile_maybe_incref(env_t *env, ast_t *ast, type_t *t); diff --git a/src/compile/functions.c b/src/compile/functions.c index 33ea9f98..6fc626cf 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -2,7 +2,7 @@ #include "functions.h" #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../naming.h" #include "../stdlib/datatypes.h" diff --git a/src/compile/integers.c b/src/compile/integers.c index 40351afa..10955fe1 100644 --- a/src/compile/integers.c +++ b/src/compile/integers.c @@ -3,7 +3,7 @@ #include #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" diff --git a/src/compile/lists.c b/src/compile/lists.c index e5c5dcca..a44d9098 100644 --- a/src/compile/lists.c +++ b/src/compile/lists.c @@ -6,7 +6,7 @@ #include #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../config.h" #include "../environment.h" #include "../stdlib/text.h" diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 6dd5c2b7..0c844c75 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -1,6 +1,6 @@ // This file defines how to compile optionals and null -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../naming.h" #include "../stdlib/datatypes.h" diff --git a/src/compile/pointers.c b/src/compile/pointers.c index 317863af..54e24f63 100644 --- a/src/compile/pointers.c +++ b/src/compile/pointers.c @@ -6,7 +6,7 @@ #include #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../config.h" #include "../environment.h" #include "../stdlib/text.h" diff --git a/src/compile/promotions.c b/src/compile/promotions.c index d017568a..eff946b9 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -2,7 +2,7 @@ #include "promotions.h" #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/sets.c b/src/compile/sets.c index b6144662..f9ffa0ae 100644 --- a/src/compile/sets.c +++ b/src/compile/sets.c @@ -1,7 +1,7 @@ // This file defines how to compile sets #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/statements.c b/src/compile/statements.c index 2e46e42b..7683b552 100644 --- a/src/compile/statements.c +++ b/src/compile/statements.c @@ -3,7 +3,7 @@ #include #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../config.h" #include "../environment.h" #include "../modules.h" diff --git a/src/compile/structs.c b/src/compile/structs.c index 2dc4a60b..20f40c4d 100644 --- a/src/compile/structs.c +++ b/src/compile/structs.c @@ -3,7 +3,7 @@ #include #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../naming.h" #include "../stdlib/tables.h" diff --git a/src/compile/tables.c b/src/compile/tables.c index 8ea86a27..e9067c0f 100644 --- a/src/compile/tables.c +++ b/src/compile/tables.c @@ -1,7 +1,7 @@ // This file defines how to compile tables #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../stdlib/datatypes.h" #include "../stdlib/text.h" diff --git a/src/compile/text.c b/src/compile/text.c index a6d739ad..c08ef31e 100644 --- a/src/compile/text.c +++ b/src/compile/text.c @@ -2,7 +2,7 @@ #include #include "../ast.h" -#include "../compile.h" +#include "expressions.h" #include "../environment.h" #include "../naming.h" #include "../stdlib/datatypes.h" -- cgit v1.2.3