about summary refs log tree commit diff
path: root/boot/lower.c
diff options
context:
space:
mode:
Diffstat (limited to 'boot/lower.c')
-rw-r--r--boot/lower.c770
1 files changed, 419 insertions, 351 deletions
diff --git a/boot/lower.c b/boot/lower.c
index b2b2349..01762a5 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -679,7 +679,6 @@ lower_pass_1_fill_bodies(struct Lower_Context* ctx, struct Tree* tree)
         struct Tree_Expression* fn_expr;
         if (lower_match_function_decl(stmt, &name, &fn_expr)) {
             Function_Id id;
-            // TODO: actually go into the body, only the signature for now.
             if (lower_function_lookup_by_name(ctx->unit, name, &id)) {
                 struct Function* fn =
                     *array_at(struct Function*, &ctx->unit->functions.entries, id);
@@ -719,162 +718,199 @@ lower_pass_1(struct Lower_Context* ctx, struct Tree* tree)
 
 struct Block* lower_block(struct Lower_Context* ctx, struct Tree_Block* tree_block);
 struct Statement* lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt);
+struct Expression* lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr);
 
-// turns a source expression into the lowered form.
 struct Expression*
-lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+lower_expression_integer_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
 {
-    switch (tree_expr->kind) {
-    case TREE_EXPRESSION_INTEGER_LITERAL: {
-        union Expression_Value v = { 0 };
-        v.integer_literal.value = tree_expr->value.integer_literal.value;
+    (void)ctx;
+    return ir_make_integer(tree_expr->value.integer_literal.value, tree_expr->span);
+}
 
-        return expression_new(EXPRESSION_INTEGER_LITERAL, v, tree_expr->span);
-    }
-    case TREE_EXPRESSION_FLOAT_LITERAL: {
-        union Expression_Value v = { 0 };
-        v.float_literal.value = tree_expr->value.float_literal.value;
+struct Expression*
+lower_expression_float_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    (void)ctx;
+    return ir_make_float(tree_expr->value.float_literal.value, tree_expr->span);
+}
 
-        return expression_new(EXPRESSION_FLOAT_LITERAL, v, tree_expr->span);
-    }
-    case TREE_EXPRESSION_STRING_LITERAL: {
-        union Expression_Value v = { 0 };
-        v.string_literal.value = tree_expr->value.string_literal.value;
+struct Expression*
+lower_expression_string_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    (void)ctx;
+    return ir_make_string(tree_expr->value.string_literal.value, tree_expr->span);
+}
 
-        return expression_new(EXPRESSION_STRING_LITERAL, v, tree_expr->span);
-    }
-    case TREE_EXPRESSION_BOOLEAN_LITERAL: {
-        union Expression_Value v = { 0 };
-        v.bool_literal.value = tree_expr->value.bool_literal.value;
+struct Expression*
+lower_expression_boolean_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    (void)ctx;
+    return ir_make_bool(tree_expr->value.bool_literal.value, tree_expr->span);
+}
 
-        return expression_new(EXPRESSION_BOOLEAN_LITERAL, v, tree_expr->span);
-    }
-    case TREE_EXPRESSION_NAME: {
-        union Expression_Value v = { 0 };
-        v.name.name = tree_expr->value.name.name;
+struct Expression*
+lower_expression_name(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    (void)ctx;
+    return ir_make_name(tree_expr->value.name.name, tree_expr->span);
+}
+
+// any groups are discarded in the lowered representation, their presence
+// just yields different expression constructions.
+// if required by precedence the final transpiler will handle them by itself.
+struct Expression*
+lower_expression_group(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    return lower_expression(ctx, tree_expr->value.group.inner_expression);
+}
+
+struct Expression*
+lower_expression_unary_operation(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    return ir_make_unary(
+        tree_expr->value.unary_operator.operation,
+        lower_expression(ctx, tree_expr->value.unary_operator.operand), tree_expr->span);
+}
 
-        return expression_new(EXPRESSION_NAME, v, tree_expr->span);
+struct Expression*
+lower_expression_binary_operation(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    enum Binary_Operation op = tree_expr->value.binary_operator.operation;
+    // TODO: maybe we want to support assignment expressions some day. today they aren't.
+    if (op >= BINARY_ASSIGN) {
+        lower_emit_error_c(ctx->unit, tree_expr->span, "assignment cannot appear as an expression");
+        return nil;
     }
-    case TREE_EXPRESSION_GROUP:
-        // any groups are discarded in the lowered representation, their presence
-        // just yields different expression constructions.
-        // if required by precedence the final transpiler will handle them by itself.
-        return lower_expression(ctx, tree_expr->value.group.inner_expression);
-    case TREE_EXPRESSION_UNARY_OPERATION: {
-        union Expression_Value v = { 0 };
-        v.unary_operator.operation = tree_expr->value.unary_operator.operation;
-        v.unary_operator.operand = lower_expression(ctx, tree_expr->value.unary_operator.operand);
-
-        return expression_new(EXPRESSION_UNARY_OPERATION, v, tree_expr->span);
+    if (op == BINARY_RANGE) {
+        lower_emit_error_c(
+            ctx->unit, tree_expr->span, "range expressions are only valid in loop initializers");
+        return nil;
     }
-    case TREE_EXPRESSION_BINARY_OPERATION: {
-        enum Binary_Operation op = tree_expr->value.binary_operator.operation;
-        // TODO: maybe we want to support assignment expressions? for now they're not supported.
-        if (op >= BINARY_ASSIGN) {
-            lower_emit_error_c(
-                ctx->unit, tree_expr->span, "assignment cannot appear as an expression");
-            return nil;
-        }
-        if (op == BINARY_RANGE) {
-            lower_emit_error_c(
-                ctx->unit, tree_expr->span,
-                "range expressions are only valid in loop initializers");
+    return ir_make_binary(
+        op, lower_expression(ctx, tree_expr->value.binary_operator.left_operand),
+        lower_expression(ctx, tree_expr->value.binary_operator.right_operand), tree_expr->span);
+}
+
+struct Expression*
+lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    struct Tree_Argument_Group* group = &tree_expr->value.call.argument_group;
+
+    // TODO: named arguments need lookup against the callee's parameter
+    // table to reorder, we only handle positional arguments today.
+    FOR_EACH_ARRAY (struct String, name, &group->argument_names) {
+        if (name->length > 0) {
+            lower_emit_error_c(ctx->unit, tree_expr->span, "unimplemented: named call arguments");
             return nil;
         }
-        union Expression_Value v = { 0 };
-        v.binary_operator.operation = op;
-        v.binary_operator.left_operand =
-            lower_expression(ctx, tree_expr->value.binary_operator.left_operand);
-        v.binary_operator.right_operand =
-            lower_expression(ctx, tree_expr->value.binary_operator.right_operand);
-
-        return expression_new(EXPRESSION_BINARY_OPERATION, v, tree_expr->span);
     }
-    case TREE_EXPRESSION_CALL: {
-        struct Tree_Argument_Group* group = &tree_expr->value.call.argument_group;
 
-        // named arguments need lookup against the callee's parameter table to
-        // reorder; not in this commit. positional-only is fine for now.
-        FOR_EACH_ARRAY (struct String, name, &group->argument_names) {
-            if (name->length > 0) {
-                lower_emit_error_c(
-                    ctx->unit, tree_expr->span, "unimplemented: named call arguments");
-                return nil;
-            }
-        }
+    struct Expression* subject = lower_expression(ctx, tree_expr->value.call.subject);
+    Array(struct Expression*) arguments = array_new(struct Expression*, 16);
+    FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
+        struct Expression* lowered = lower_expression(ctx, arg);
+        array_push(&arguments, &lowered);
+    }
+    return ir_make_call(subject, arguments, tree_expr->span);
+}
 
-        union Expression_Value v = { 0 };
-        v.call.subject = lower_expression(ctx, tree_expr->value.call.subject);
-        v.call.arguments = array_new(struct Expression*, 16);
-        FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
-            struct Expression* lowered = lower_expression(ctx, arg);
-            array_push(&v.call.arguments, &lowered);
-        }
+struct Expression*
+lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    struct Tree_Expression* subject = tree_expr->value.construct.subject;
+    if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
+        lower_emit_error_c(ctx->unit, tree_expr->span, "construction subject must be a type name");
+        return nil;
+    }
+    Type_Id type_id;
+    if (!lower_type_lookup_by_name(ctx->unit, subject->value.name.name, &type_id)) {
+        lower_emit_error(
+            ctx->unit, subject->span,
+            string_concatenate(
+                ARG_ASCII, "undefined type '", ARG_STRING, subject->value.name.name, ARG_ASCII, "'",
+                ARG_END));
+        return nil;
+    }
 
-        return expression_new(EXPRESSION_CALL, v, tree_expr->span);
+    Array(struct Construct_Field) fields = array_new(struct Construct_Field, 16);
+    struct Tree_Argument_Group* group = &tree_expr->value.construct.argument_group;
+    uint i = 0;
+    FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
+        struct String name = string_empty();
+        if (i < array_length(&group->argument_names))
+            name = *array_at(struct String, &group->argument_names, i);
+        struct Construct_Field field = {
+            .name = name,
+            .value = lower_expression(ctx, arg),
+        };
+        array_push(&fields, &field);
+        ++i;
     }
-    case TREE_EXPRESSION_CONSTRUCT: {
-        struct Tree_Expression* subject = tree_expr->value.construct.subject;
-        if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
-            lower_emit_error_c(
-                ctx->unit, tree_expr->span, "construction subject must be a type name");
-            return nil;
-        }
-        Type_Id type_id;
-        if (!lower_type_lookup_by_name(ctx->unit, subject->value.name.name, &type_id)) {
-            lower_emit_error(
-                ctx->unit, subject->span,
-                string_concatenate(
-                    ARG_ASCII, "undefined type '", ARG_STRING, subject->value.name.name, ARG_ASCII,
-                    "'", ARG_END));
-            return nil;
-        }
+    return ir_make_construct(type_id, fields, tree_expr->span);
+}
 
-        union Expression_Value v = { 0 };
-        v.construct.type_id = type_id;
-        v.construct.fields = array_new(struct Construct_Field, 16);
-
-        struct Tree_Argument_Group* group = &tree_expr->value.construct.argument_group;
-        uint i = 0;
-        FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
-            struct String name = string_empty();
-            if (i < array_length(&group->argument_names))
-                name = *array_at(struct String, &group->argument_names, i);
-            struct Construct_Field field = {
-                .name = name,
-                .value = lower_expression(ctx, arg),
-            };
-            array_push(&v.construct.fields, &field);
-            ++i;
-        }
+// type-as-expression is only valid as the right-side of a top-level binding;
+// anywhere else it is an error.
+struct Expression*
+lower_expression_type(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    lower_emit_error_c(
+        ctx->unit, tree_expr->span, "type expression not allowed inside a function body");
+    return nil;
+}
 
-        return expression_new(EXPRESSION_CONSTRUCT, v, tree_expr->span);
-    }
-    case TREE_EXPRESSION_TYPE:
-        // type-as-expression is only valid as the right-sided of a top-level binding.
-        lower_emit_error_c(
-            ctx->unit, tree_expr->span, "type expression not allowed inside a function body");
-        return nil;
-    case TREE_EXPRESSION_FUNCTION: {
-        // anonymous function: lift it up as a synthetic function table entry,
-        // replace the expression with a name reference.
-        // TODO: should we have a better kind of reference here than a name?
-        // maybe a function reference somehow?
-        struct String name = lower_synthesize_name(ctx, "lambda");
+// anonymous function: lift it up as a synthetic function table entry,
+// replace the expression with a name reference.
+// TODO: should we have a better kind of reference here than a name?
+// maybe a function reference somehow?
+struct Expression*
+lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    struct String name = lower_synthesize_name(ctx, "lambda");
 
-        // NOTE: we are allowed to register functions whenever we want,
-        // even while iterating over them, the pass will eventually get to them.
-        if (!lower_register_function_shell(ctx->unit, name, tree_expr->span)) return nil;
+    // NOTE: we are allowed to register functions whenever we want,
+    // even while iterating over them, the pass will eventually get to them.
+    if (!lower_register_function_shell(ctx->unit, name, tree_expr->span)) return nil;
 
-        Function_Id id;
-        lower_function_lookup_by_name(ctx->unit, name, &id);
+    Function_Id id;
+    lower_function_lookup_by_name(ctx->unit, name, &id);
 
-        struct Function* fn = *array_at(struct Function*, &ctx->unit->functions.entries, id);
-        fn->synthetic = true;
-        lower_fill_function_signature(ctx, fn, tree_expr);
+    struct Function* fn = *array_at(struct Function*, &ctx->unit->functions.entries, id);
+    fn->synthetic = true;
+    lower_fill_function_signature(ctx, fn, tree_expr);
 
-        return ir_make_name(name, tree_expr->span);
-    }
+    return ir_make_name(name, tree_expr->span);
+}
+
+// turns a source expression into the lowered form.
+struct Expression*
+lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
+{
+    switch (tree_expr->kind) {
+    case TREE_EXPRESSION_INTEGER_LITERAL:
+        return lower_expression_integer_literal(ctx, tree_expr);
+    case TREE_EXPRESSION_FLOAT_LITERAL:
+        return lower_expression_float_literal(ctx, tree_expr);
+    case TREE_EXPRESSION_STRING_LITERAL:
+        return lower_expression_string_literal(ctx, tree_expr);
+    case TREE_EXPRESSION_BOOLEAN_LITERAL:
+        return lower_expression_boolean_literal(ctx, tree_expr);
+    case TREE_EXPRESSION_NAME:
+        return lower_expression_name(ctx, tree_expr);
+    case TREE_EXPRESSION_GROUP:
+        return lower_expression_group(ctx, tree_expr);
+    case TREE_EXPRESSION_UNARY_OPERATION:
+        return lower_expression_unary_operation(ctx, tree_expr);
+    case TREE_EXPRESSION_BINARY_OPERATION:
+        return lower_expression_binary_operation(ctx, tree_expr);
+    case TREE_EXPRESSION_CALL:
+        return lower_expression_call(ctx, tree_expr);
+    case TREE_EXPRESSION_CONSTRUCT:
+        return lower_expression_construct(ctx, tree_expr);
+    case TREE_EXPRESSION_TYPE:
+        return lower_expression_type(ctx, tree_expr);
+    case TREE_EXPRESSION_FUNCTION:
+        return lower_expression_function(ctx, tree_expr);
     case TREE_EXPRESSION_SUBSCRIPT:
     case TREE_EXPRESSION_MEMBER:
     case TREE_EXPRESSION_INCREMENT_DECREMENT:
@@ -943,261 +979,293 @@ lower_is_shadowing(struct Lower_Context* ctx, struct String name)
     return false;
 }
 
-// turns a source statement into the lowered form.
 struct Statement*
-lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+lower_statement_return(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
 {
-    switch (tree_stmt->kind) {
-    case TREE_STATEMENT_RETURN: {
-        union Statement_Value v = { 0 };
-        if (tree_stmt->value.return_value.value)
-            v.return_value.value = lower_expression(ctx, tree_stmt->value.return_value.value);
+    struct Expression* value =
+        tree_stmt->value.return_value.value
+            ? lower_expression(ctx, tree_stmt->value.return_value.value)
+            : nil;
+    return ir_make_return(value, tree_stmt->span);
+}
+
+struct Statement*
+lower_statement_declaration(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    struct Tree_Bare_Declaration* decl = &tree_stmt->value.declaration.inner;
 
-        return statement_new(STATEMENT_RETURN, v, tree_stmt->span);
+    // TODO: multi-name decls like `var a, b int = ...` need to split into
+    // N sibling declarations or introduce a temporary for the initializer.
+    if (array_length(&decl->names) != 1) {
+        lower_emit_error_c(ctx->unit, tree_stmt->span, "unimplemented: multi-name declarations");
+        return nil;
     }
-    case TREE_STATEMENT_DECLARATION: {
-        struct Tree_Bare_Declaration* decl = &tree_stmt->value.declaration.inner;
 
-        // multi-name decls (`var a, b int = …`) need either splitting into N
-        // sibling declarations (no side effect on the initializer) or a
-        // temporary; both are deferred for now.
-        if (array_length(&decl->names) != 1) {
-            lower_emit_error_c(
-                ctx->unit, tree_stmt->span, "unimplemented: multi-name declarations");
-            return nil;
-        }
+    struct String name = *array_at(struct String, &decl->names, 0);
+    if (lower_is_shadowing(ctx, name)) {
+        lower_emit_error(
+            ctx->unit, tree_stmt->span,
+            string_concatenate(
+                ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII, "' shadows an existing binding",
+                ARG_END));
+        return nil;
+    }
+    struct Type_Ref type = lower_intern_type_ref(ctx, decl->type);
+    lower_declare_local(ctx, name, type);
 
-        struct String name = *array_at(struct String, &decl->names, 0);
-        if (lower_is_shadowing(ctx, name)) {
-            lower_emit_error(
-                ctx->unit, tree_stmt->span,
-                string_concatenate(
-                    ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII,
-                    "' shadows an existing binding", ARG_END));
-            return nil;
-        }
-        struct Type_Ref type = lower_intern_type_ref(ctx, decl->type);
-        lower_declare_local(ctx, name, type);
+    struct Expression* initializer =
+        decl->initializer ? lower_expression(ctx, decl->initializer) : nil;
+    return ir_make_declaration(name, type, initializer, tree_stmt->span);
+}
+
+struct Statement*
+lower_statement_expression(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    struct Tree_Expression* inner = tree_stmt->value.expression.inner;
 
-        union Statement_Value v = { 0 };
-        v.declaration.name = name;
-        v.declaration.type = type;
-        if (decl->initializer) v.declaration.initializer = lower_expression(ctx, decl->initializer);
+    if (inner && inner->kind == TREE_EXPRESSION_BINARY_OPERATION) {
+        enum Binary_Operation op = inner->value.binary_operator.operation;
+        struct Tree_Expression* lhs_tree = inner->value.binary_operator.left_operand;
+        struct Tree_Expression* rhs_tree = inner->value.binary_operator.right_operand;
 
-        return statement_new(STATEMENT_DECLARATION, v, tree_stmt->span);
-    }
-    case TREE_STATEMENT_EXPRESSION: {
-        struct Tree_Expression* inner = tree_stmt->value.expression.inner;
-
-        if (inner && inner->kind == TREE_EXPRESSION_BINARY_OPERATION) {
-            enum Binary_Operation op = inner->value.binary_operator.operation;
-            struct Tree_Expression* lhs_tree = inner->value.binary_operator.left_operand;
-            struct Tree_Expression* rhs_tree = inner->value.binary_operator.right_operand;
-
-            if (op == BINARY_ASSIGN) {
-                struct Expression* lhs = lower_expression(ctx, lhs_tree);
-                struct Expression* rhs = lower_expression(ctx, rhs_tree);
-                return ir_make_assign(lhs, rhs, tree_stmt->span);
-            }
-            if (op > BINARY_ASSIGN) {
-                // compound assigns always synthesize into a basic assignment
-                // to a simple binary operation.
-                if (!lhs_tree || lhs_tree->kind != TREE_EXPRESSION_NAME) {
-                    // TODO: implement non-trivial lhs assignments like function
-                    // calls for example. not too common but sometimes necessary.
-                    lower_emit_error_c(
-                        ctx->unit, tree_stmt->span,
-                        "unimplemented: compound assignment with non-trivial lvalue");
-                    return nil;
-                }
-
-                enum Binary_Operation simple_op = binary_operation_strip_assign(op);
-                if (simple_op == BINARY_NONE) {
-                    lower_emit_error_c(
-                        ctx->unit, tree_stmt->span, "unknown compound assignment operator");
-                    return nil;
-                }
-
-                struct Expression* binary = ir_make_binary(
-                    simple_op, lower_expression(ctx, lhs_tree), lower_expression(ctx, rhs_tree),
-                    inner->span);
-                return ir_make_assign(lower_expression(ctx, lhs_tree), binary, tree_stmt->span);
-            }
+        if (op == BINARY_ASSIGN) {
+            struct Expression* lhs = lower_expression(ctx, lhs_tree);
+            struct Expression* rhs = lower_expression(ctx, rhs_tree);
+            return ir_make_assign(lhs, rhs, tree_stmt->span);
         }
-        if (inner && inner->kind == TREE_EXPRESSION_INCREMENT_DECREMENT) {
-            // synthesize into assignment and binary operation
-            struct Tree_Expression_Increment_Decrement* incdec = &inner->value.increment_decrement;
-            struct Tree_Expression* subject = incdec->subject;
-            if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
-                // TODO: handle this too, like compound assignments
+        if (op > BINARY_ASSIGN) {
+            // compound assigns always synthesize into a basic assignment
+            // to a simple binary operation.
+            if (!lhs_tree || lhs_tree->kind != TREE_EXPRESSION_NAME) {
+                // TODO: implement non-trivial lhs assignments like function
+                // calls for example. not too common but sometimes necessary.
                 lower_emit_error_c(
                     ctx->unit, tree_stmt->span,
-                    "unimplemented: increment/decrement on non-trivial lvalue");
+                    "unimplemented: compound assignment with non-trivial lvalue");
                 return nil;
             }
 
-            enum Binary_Operation simple_op =
-                incdec->operation == INCREMENT_DECREMENT_INCREMENT ? BINARY_PLUS : BINARY_MINUS;
+            enum Binary_Operation simple_op = binary_operation_strip_assign(op);
+            if (simple_op == BINARY_NONE) {
+                lower_emit_error_c(
+                    ctx->unit, tree_stmt->span, "unknown compound assignment operator");
+                return nil;
+            }
 
-            struct Expression* one = ir_make_integer(1, inner->span);
-            struct Expression* binary =
-                ir_make_binary(simple_op, lower_expression(ctx, subject), one, inner->span);
-            return ir_make_assign(lower_expression(ctx, subject), binary, tree_stmt->span);
+            struct Expression* binary = ir_make_binary(
+                simple_op, lower_expression(ctx, lhs_tree), lower_expression(ctx, rhs_tree),
+                inner->span);
+            return ir_make_assign(lower_expression(ctx, lhs_tree), binary, tree_stmt->span);
+        }
+    }
+    if (inner && inner->kind == TREE_EXPRESSION_INCREMENT_DECREMENT) {
+        // synthesize into assignment and binary operation
+        struct Tree_Expression_Increment_Decrement* incdec = &inner->value.increment_decrement;
+        struct Tree_Expression* subject = incdec->subject;
+        if (!subject || subject->kind != TREE_EXPRESSION_NAME) {
+            // TODO: handle this too, like compound assignments
+            lower_emit_error_c(
+                ctx->unit, tree_stmt->span,
+                "unimplemented: increment/decrement on non-trivial lvalue");
+            return nil;
         }
 
-        return ir_make_expression_statement(lower_expression(ctx, inner), tree_stmt->span);
+        enum Binary_Operation simple_op =
+            incdec->operation == INCREMENT_DECREMENT_INCREMENT ? BINARY_PLUS : BINARY_MINUS;
+
+        struct Expression* one = ir_make_integer(1, inner->span);
+        struct Expression* binary =
+            ir_make_binary(simple_op, lower_expression(ctx, subject), one, inner->span);
+        return ir_make_assign(lower_expression(ctx, subject), binary, tree_stmt->span);
     }
-    case TREE_STATEMENT_BLOCK: {
-        union Statement_Value v = { 0 };
-        v.block.inner = lower_block(ctx, &tree_stmt->value.block.inner);
 
-        return statement_new(STATEMENT_BLOCK, v, tree_stmt->span);
+    return ir_make_expression_statement(lower_expression(ctx, inner), tree_stmt->span);
+}
+
+struct Statement*
+lower_statement_block(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    return ir_make_block_statement(
+        lower_block(ctx, &tree_stmt->value.block.inner), tree_stmt->span);
+}
+
+struct Statement*
+lower_statement_conditional(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    struct Tree_Statement_Value_Conditional* cond = &tree_stmt->value.conditional;
+    Array(struct If_Branch) branches = array_new(struct If_Branch, 8);
+
+    for (uint i = 0; i < cond->condition_count; ++i) {
+        struct If_Branch branch = {
+            .condition =
+                cond->conditions[i].when ? lower_expression(ctx, cond->conditions[i].when) : nil,
+            .body = lower_block(ctx, &cond->conditions[i].then),
+        };
+        array_push(&branches, &branch);
     }
-    case TREE_STATEMENT_CONDITIONAL: {
-        struct Tree_Statement_Value_Conditional* cond = &tree_stmt->value.conditional;
-        union Statement_Value v = { 0 };
-        v.conditional.branches = array_new(struct If_Branch, 8);
-
-        for (uint i = 0; i < cond->condition_count; ++i) {
-            struct If_Branch branch = {
-                .condition =
-                    cond->conditions[i].when
-                        ? lower_expression(ctx, cond->conditions[i].when)
-                        : nil,
-                .body = lower_block(ctx, &cond->conditions[i].then),
-            };
-            array_push(&v.conditional.branches, &branch);
-        }
 
-        return statement_new(STATEMENT_CONDITIONAL, v, tree_stmt->span);
+    return ir_make_conditional(branches, tree_stmt->span);
+}
+
+struct Statement*
+lower_statement_loop(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    struct Tree_Statement_Value_Loop* loop = &tree_stmt->value.loop;
+
+    switch (loop->style) {
+    case TREE_STATEMENT_LOOP_STYLE_WHILE: {
+        struct Expression* cond = lower_expression(ctx, loop->condition);
+        struct Block* body = lower_block(ctx, &loop->body);
+        return ir_make_loop(cond, body, tree_stmt->span);
     }
-    case TREE_STATEMENT_LOOP: {
-        struct Tree_Statement_Value_Loop* loop = &tree_stmt->value.loop;
-
-        switch (loop->style) {
-        case TREE_STATEMENT_LOOP_STYLE_WHILE: {
-            struct Expression* cond = lower_expression(ctx, loop->condition);
-            struct Block* body = lower_block(ctx, &loop->body);
-            return ir_make_loop(cond, body, tree_stmt->span);
-        }
-        case TREE_STATEMENT_LOOP_STYLE_ENDLESS: {
-            // synthesize a `true` literal so the ir always has a real
-            // condition to emit.
-            struct Expression* cond = ir_make_bool(true, tree_stmt->span);
-            struct Block* body = lower_block(ctx, &loop->body);
-            return ir_make_loop(cond, body, tree_stmt->span);
-        }
-        case TREE_STATEMENT_LOOP_STYLE_C:
-        case TREE_STATEMENT_LOOP_STYLE_FOR_EACH: {
-            // both styles become a basic while loop
-            // TODO: right now we only support for-each for ranges,
-            // we want to add iteration over real containers later.
-
-            struct Tree_Bare_Declaration* decl = &loop->declaration;
-
-            struct Tree_Expression* init = nil;
-            struct Tree_Expression* cond_tree = loop->condition;
-            struct Tree_Expression* iter_tree = loop->iteration;
-
-            if (loop->style == TREE_STATEMENT_LOOP_STYLE_FOR_EACH) {
-                if (!decl->initializer
-                    || decl->initializer->kind != TREE_EXPRESSION_BINARY_OPERATION
-                    || decl->initializer->value.binary_operator.operation != BINARY_RANGE) {
-                    lower_emit_error_c(
-                        ctx->unit, tree_stmt->span,
-                        "unimplemented: for-each over non-range collections");
-                    return nil;
-                }
-                init = decl->initializer->value.binary_operator.left_operand;
-                // condition and iteration step are synthesized below
-            } else {
-                init = decl->initializer;
-            }
+    case TREE_STATEMENT_LOOP_STYLE_ENDLESS: {
+        // synthesize a `true` literal so the ir always has a real
+        // condition to emit.
+        struct Expression* cond = ir_make_bool(true, tree_stmt->span);
+        struct Block* body = lower_block(ctx, &loop->body);
+        return ir_make_loop(cond, body, tree_stmt->span);
+    }
+    case TREE_STATEMENT_LOOP_STYLE_C:
+    case TREE_STATEMENT_LOOP_STYLE_FOR_EACH: {
+        // both styles become a basic while loop
+        // TODO: right now we only support for-each for ranges,
+        // we want to add iteration over real containers later.
+
+        struct Tree_Bare_Declaration* decl = &loop->declaration;
 
-            if (array_length(&decl->names) != 1) {
+        struct Tree_Expression* init = nil;
+        struct Tree_Expression* cond_tree = loop->condition;
+        struct Tree_Expression* iter_tree = loop->iteration;
+
+        if (loop->style == TREE_STATEMENT_LOOP_STYLE_FOR_EACH) {
+            if (!decl->initializer || decl->initializer->kind != TREE_EXPRESSION_BINARY_OPERATION
+                || decl->initializer->value.binary_operator.operation != BINARY_RANGE) {
                 lower_emit_error_c(
                     ctx->unit, tree_stmt->span,
-                    "unimplemented: for-loop with multi-name declaration");
+                    "unimplemented: for-each over non-range collections");
                 return nil;
             }
-            struct String name = *array_at(struct String, &decl->names, 0);
+            init = decl->initializer->value.binary_operator.left_operand;
+            // condition and iteration step are synthesized below
+        } else {
+            init = decl->initializer;
+        }
 
-            // open outer block scope.
-            // this stores the iterator variable.
-            lower_push_scope(ctx, SCOPE_TYPE_BLOCK);
+        if (array_length(&decl->names) != 1) {
+            lower_emit_error_c(
+                ctx->unit, tree_stmt->span, "unimplemented: for-loop with multi-name declaration");
+            return nil;
+        }
+        struct String name = *array_at(struct String, &decl->names, 0);
 
-            if (lower_is_shadowing(ctx, name)) {
-                lower_emit_error(
-                    ctx->unit, tree_stmt->span,
-                    string_concatenate(
-                        ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII,
-                        "' shadows an existing binding", ARG_END));
-                lower_pop_scope(ctx, SCOPE_TYPE_BLOCK);
-                return nil;
-            }
+        // open outer block scope.
+        // this stores the iterator variable.
+        lower_push_scope(ctx, SCOPE_TYPE_BLOCK);
+
+        if (lower_is_shadowing(ctx, name)) {
+            lower_emit_error(
+                ctx->unit, tree_stmt->span,
+                string_concatenate(
+                    ARG_ASCII, "name '", ARG_STRING, name, ARG_ASCII,
+                    "' shadows an existing binding", ARG_END));
+            lower_pop_scope(ctx, SCOPE_TYPE_BLOCK);
+            return nil;
+        }
 
-            struct Type_Ref iter_type = lower_intern_type_ref(ctx, decl->type);
-            lower_declare_local(ctx, name, iter_type);
-
-            // build the iteration variable declaration
-            struct Statement* decl_stmt = ir_make_declaration(
-                name, iter_type, init ? lower_expression(ctx, init) : nil, decl->span);
-
-            // condition and iteration step: for c-style we lower the source
-            // ones, for for-each over a range we synthesize `i < hi` and
-            // `i = i + 1`.
-            struct Expression* cond_expr = nil;
-            struct Statement* iter_stmt = nil;
-            if (loop->style == TREE_STATEMENT_LOOP_STYLE_FOR_EACH) {
-                struct Tree_Expression* hi = decl->initializer->value.binary_operator.right_operand;
-                // condition: i < hi
-                cond_expr = ir_make_binary(
-                    BINARY_LESS_THAN, ir_make_name(name, tree_stmt->span),
-                    lower_expression(ctx, hi), tree_stmt->span);
-                // step: i = i + 1
-                iter_stmt = ir_make_assign(
-                    ir_make_name(name, tree_stmt->span),
-                    ir_make_binary(
-                        BINARY_PLUS, ir_make_name(name, tree_stmt->span),
-                        ir_make_integer(1, tree_stmt->span), tree_stmt->span),
-                    tree_stmt->span);
-            } else {
-                cond_expr = cond_tree ? lower_expression(ctx, cond_tree) : nil;
-                if (iter_tree) {
-                    struct Tree_Statement iter_node = {
-                        .kind = TREE_STATEMENT_EXPRESSION,
-                        .value = { .expression = { .inner = iter_tree } },
-                        .span = iter_tree->span,
-                    };
-                    iter_stmt = lower_statement(ctx, &iter_node);
-                }
+        struct Type_Ref iter_type = lower_intern_type_ref(ctx, decl->type);
+        lower_declare_local(ctx, name, iter_type);
+
+        // build the iteration variable declaration
+        struct Statement* decl_stmt = ir_make_declaration(
+            name, iter_type, init ? lower_expression(ctx, init) : nil, decl->span);
+
+        // condition and iteration step: for c-style we lower the source
+        // ones, for for-each over a range we synthesize `i < hi` and
+        // `i = i + 1`.
+        struct Expression* cond_expr = nil;
+        struct Statement* iter_stmt = nil;
+        if (loop->style == TREE_STATEMENT_LOOP_STYLE_FOR_EACH) {
+            struct Tree_Expression* hi = decl->initializer->value.binary_operator.right_operand;
+            // condition: i < hi
+            cond_expr = ir_make_binary(
+                BINARY_LESS_THAN, ir_make_name(name, tree_stmt->span), lower_expression(ctx, hi),
+                tree_stmt->span);
+            // step: i = i + 1
+            iter_stmt = ir_make_assign(
+                ir_make_name(name, tree_stmt->span),
+                ir_make_binary(
+                    BINARY_PLUS, ir_make_name(name, tree_stmt->span),
+                    ir_make_integer(1, tree_stmt->span), tree_stmt->span),
+                tree_stmt->span);
+        } else {
+            cond_expr = cond_tree ? lower_expression(ctx, cond_tree) : nil;
+            if (iter_tree) {
+                struct Tree_Statement iter_node = {
+                    .kind = TREE_STATEMENT_EXPRESSION,
+                    .value = { .expression = { .inner = iter_tree } },
+                    .span = iter_tree->span,
+                };
+                iter_stmt = lower_statement(ctx, &iter_node);
             }
+        }
 
-            // assemble the body, appending the iteration step at the end so
-            // both for-styles share the same `{ decl; while { body; iter; } }`
-            // shape.
-            struct Block* body = lower_block(ctx, &loop->body);
-            if (iter_stmt) array_push(&body->statements, &iter_stmt);
+        // assemble the body, appending the iteration step at the end so
+        // both for-styles share the same `{ decl; while { body; iter; } }`
+        // shape.
+        struct Block* body = lower_block(ctx, &loop->body);
+        if (iter_stmt) array_push(&body->statements, &iter_stmt);
 
-            struct Statement* while_stmt = ir_make_loop(cond_expr, body, tree_stmt->span);
+        struct Statement* while_stmt = ir_make_loop(cond_expr, body, tree_stmt->span);
 
-            struct Block* outer = block_new();
-            outer->statements = array_new(struct Statement*, 4);
-            array_push(&outer->statements, &decl_stmt);
-            array_push(&outer->statements, &while_stmt);
+        struct Block* outer = block_new();
+        outer->statements = array_new(struct Statement*, 4);
+        array_push(&outer->statements, &decl_stmt);
+        array_push(&outer->statements, &while_stmt);
 
-            lower_pop_scope(ctx, SCOPE_TYPE_BLOCK);
-            return ir_make_block_statement(outer, tree_stmt->span);
-        }
-        default:
-            lower_emit_error_c(ctx->unit, tree_stmt->span, "unknown loop style");
-            return nil;
-        }
+        lower_pop_scope(ctx, SCOPE_TYPE_BLOCK);
+        return ir_make_block_statement(outer, tree_stmt->span);
     }
+    default:
+        lower_emit_error_c(ctx->unit, tree_stmt->span, "unknown loop style");
+        return nil;
+    }
+}
+
+struct Statement*
+lower_statement_break(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    (void)ctx;
+    return ir_make_break(tree_stmt->span);
+}
+
+struct Statement*
+lower_statement_continue(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    (void)ctx;
+    return ir_make_continue(tree_stmt->span);
+}
+
+// turns a source statement into the lowered form.
+struct Statement*
+lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt)
+{
+    switch (tree_stmt->kind) {
+    case TREE_STATEMENT_RETURN:
+        return lower_statement_return(ctx, tree_stmt);
+    case TREE_STATEMENT_DECLARATION:
+        return lower_statement_declaration(ctx, tree_stmt);
+    case TREE_STATEMENT_EXPRESSION:
+        return lower_statement_expression(ctx, tree_stmt);
+    case TREE_STATEMENT_BLOCK:
+        return lower_statement_block(ctx, tree_stmt);
+    case TREE_STATEMENT_CONDITIONAL:
+        return lower_statement_conditional(ctx, tree_stmt);
+    case TREE_STATEMENT_LOOP:
+        return lower_statement_loop(ctx, tree_stmt);
     case TREE_STATEMENT_BREAK:
-        return ir_make_break(tree_stmt->span);
+        return lower_statement_break(ctx, tree_stmt);
     case TREE_STATEMENT_CONTINUE:
-        return ir_make_continue(tree_stmt->span);
+        return lower_statement_continue(ctx, tree_stmt);
     default:
         lower_emit_error_c(ctx->unit, tree_stmt->span, "unimplemented: this statement kind");
         return nil;