aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-03 19:36:41 +0200
committerMel <mel@rnrd.eu>2026-05-03 19:36:41 +0200
commit5211970448a05205b67d7604ad5e4e3925e4ddef (patch)
tree49e563f39a7304b59d6ccb018d3e07d3d0eca4bd /boot
parent1512abed55dd3f779eb774eb03f605397a4a45fe (diff)
downloadcatskill-5211970448a05205b67d7604ad5e4e3925e4ddef.tar.zst
catskill-5211970448a05205b67d7604ad5e4e3925e4ddef.zip
Intermediate representation taxonomy
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot')
-rw-r--r--boot/catboot.h1
-rw-r--r--boot/ir.c543
2 files changed, 544 insertions, 0 deletions
diff --git a/boot/catboot.h b/boot/catboot.h
index 00764f0..e5ffe5e 100644
--- a/boot/catboot.h
+++ b/boot/catboot.h
@@ -18,5 +18,6 @@
#include "tree.c"
#include "parse.c"
#include "visit/tree.c"
+#include "ir.c"
#include "transpile.c"
#include "build.c"
diff --git a/boot/ir.c b/boot/ir.c
new file mode 100644
index 0000000..54ee2b3
--- /dev/null
+++ b/boot/ir.c
@@ -0,0 +1,543 @@
+/*
+ * intermediate representation produced by the lowering pass.
+ *
+ * the ir is the canonical form every later phase operates on.
+ * it is 100% sugar-free (healthy!) and shaped (vaguely) like c, as the
+ * primary target of our bootstrapping transpiler.
+ *
+ * Copyright (c) 2026, Mel G. <mel@rnrd.eu>
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+#pragma once
+
+#include "catboot.h"
+
+// per-unit stable identifier for a type, never becomes invalid.
+typedef uint Type_Id;
+// per-unit stable identifier for a function, never becomes invalid.
+typedef uint Function_Id;
+
+struct Type;
+struct Type_Ref;
+struct Function;
+struct Statement;
+struct Expression;
+struct Block;
+
+// modifier applied to a type reference.
+enum Type_Modifier
+{
+ TYPE_MOD_REFERENCE, // &T
+ // TODO: arrays, optionals, everything else...
+};
+
+// a use of a type.
+struct Type_Ref
+{
+ Type_Id type_id;
+
+ // modifiers are sorted outer-to-inner, for example:
+ // `&[Thing?]`: reference -> array -> maybe -> Thing
+ Array(enum Type_Modifier) mods;
+};
+
+// what kind of declaration is this type?
+// represents both the declaration and the type itself.
+enum Type_Kind
+{
+ TYPE_NONE,
+ TYPE_PRIMITIVE,
+ TYPE_ALIAS,
+ TYPE_STRUCTURE,
+ TYPE_VARIANT,
+ TYPE_FUNCTION,
+ // TODO: add rest of possible types
+};
+
+// a member of a structure.
+struct Field
+{
+ struct String name;
+ struct Type_Ref type;
+};
+
+// one case of a tagged union.
+struct Variant_Case
+{
+ struct String name;
+ // tag to distinguish each case at runtime
+ uint32 tag;
+
+ // if no payload, the case is just a tag
+ bool has_payload;
+ struct Type_Ref payload;
+};
+
+struct Type_Alias
+{
+ Type_Id target_id;
+};
+
+struct Type_Structure
+{
+ Array(struct Field) fields;
+};
+
+struct Type_Variant
+{
+ Array(struct Variant_Case) cases;
+};
+
+struct Type_Function
+{
+ struct Type_Ref return_type;
+ Array(struct Type_Ref) params;
+ bool variadic;
+};
+
+union Type_Value
+{
+ struct Type_Alias alias;
+ struct Type_Structure structure;
+ struct Type_Variant variant;
+ struct Type_Function function;
+};
+
+// every type the compiler ever talks about lives in the type table and has a
+// stable identifier which other types refer to.
+// unnamed, structural types get synthesized into full types.
+struct Type
+{
+ Type_Id id;
+ struct String name;
+
+ enum Type_Kind kind;
+ union Type_Value value;
+
+ // if a user names a type it is non-synthetic, if they just refer to a type
+ // without a name, like `var x (A, B) = ...`, it is synthetic.
+ bool synthetic;
+ // for synthethic types this is a hash of the contents of the type,
+ // used for de-duplication. otherwise we can't cast between the same type.
+ // 0 for all named types.
+ uint64 structural_hash;
+
+ // all types which this type has a hard dependency on
+ Array(Type_Id) depends_on;
+
+ struct Span span;
+};
+
+// one parameter of a function.
+struct Param
+{
+ struct String name;
+ struct Type_Ref type;
+};
+
+// minimal lowered statement set.
+enum Statement_Kind
+{
+ STATEMENT_NONE,
+ STATEMENT_DECLARATION,
+ STATEMENT_ASSIGN,
+ STATEMENT_EXPRESSION,
+ STATEMENT_CONDITIONAL,
+ STATEMENT_LOOP,
+ STATEMENT_RETURN,
+ STATEMENT_BREAK,
+ STATEMENT_CONTINUE,
+ STATEMENT_BLOCK,
+ STATEMENT_LABEL,
+ STATEMENT_GOTO,
+};
+
+struct Statement_Declaration
+{
+ struct String name;
+ struct Type_Ref type;
+ struct Expression* initializer; // nil if no initializer
+};
+
+// only pure `x = y` assignments, no compounds like `+=`.
+struct Statement_Assign
+{
+ struct Expression* lhs;
+ struct Expression* rhs;
+};
+
+struct Statement_Expression
+{
+ struct Expression* inner;
+};
+
+// one branch of an if/else-if/else chain.
+struct If_Branch
+{
+ struct Expression* condition; // nil if else branch
+ struct Block* body;
+};
+
+struct Statement_Conditional
+{
+ Array(struct If_Branch) branches;
+};
+
+struct Statement_Loop
+{
+ struct Expression* condition;
+ struct Block* body;
+};
+
+struct Statement_Return
+{
+ struct Expression* value; // nil if empty return
+};
+
+struct Statement_Block
+{
+ struct Block* inner;
+};
+
+struct Statement_Label
+{
+ struct String name;
+};
+
+struct Statement_Goto
+{
+ struct String target;
+};
+
+union Statement_Value
+{
+ struct Statement_Declaration declaration;
+ struct Statement_Assign assign;
+ struct Statement_Expression expression;
+ struct Statement_Conditional conditional;
+ struct Statement_Loop loop;
+ struct Statement_Return return_value;
+ struct Statement_Block block;
+ struct Statement_Label label;
+ struct Statement_Goto goto_target;
+};
+
+struct Statement
+{
+ enum Statement_Kind kind;
+ union Statement_Value value;
+ struct Span span;
+};
+
+// minimal lowered expression set.
+enum Expression_Kind
+{
+ EXPRESSION_NONE,
+ EXPRESSION_INTEGER_LITERAL,
+ EXPRESSION_FLOAT_LITERAL,
+ EXPRESSION_STRING_LITERAL,
+ EXPRESSION_BOOLEAN_LITERAL,
+ EXPRESSION_NAME,
+ EXPRESSION_UNARY_OPERATION,
+ EXPRESSION_BINARY_OPERATION,
+ EXPRESSION_SIZEOF_OPERATION,
+ EXPRESSION_CALL,
+ EXPRESSION_MEMBER,
+ EXPRESSION_SUBSCRIPT,
+ EXPRESSION_CAST,
+ EXPRESSION_CONSTRUCT,
+};
+
+struct Expression_Integer_Literal
+{
+ int64 value;
+};
+
+struct Expression_Float_Literal
+{
+ float64 value;
+};
+
+struct Expression_String_Literal
+{
+ struct String value;
+};
+
+struct Expression_Bool_Literal
+{
+ bool value;
+};
+
+struct Expression_Name
+{
+ struct String name;
+};
+
+struct Expression_Unary_Operator
+{
+ enum Unary_Operation operation;
+ struct Expression* operand;
+};
+
+struct Expression_Binary_Operator
+{
+ // assignment is excluded
+ enum Binary_Operation operation;
+ struct Expression* left_operand;
+ struct Expression* right_operand;
+};
+
+struct Expression_Sizeof_Operator
+{
+ struct Type_Ref target;
+};
+
+struct Expression_Call
+{
+ struct Expression* subject;
+ // call arguments are positional, named and out-of-order
+ // arguments get re-ordered until they are correct.
+ Array(struct Expression*) arguments;
+};
+
+struct Expression_Member
+{
+ struct Expression* subject;
+ struct String name;
+};
+
+struct Expression_Subscript
+{
+ struct Expression* subject;
+ struct Expression* index;
+};
+
+struct Expression_Cast
+{
+ struct Type_Ref target;
+ struct Expression* operand;
+};
+
+// one named field in a construction literal.
+struct Construct_Field
+{
+ // empty for positional initialization.
+ struct String name;
+ struct Expression* value;
+};
+
+struct Expression_Construct
+{
+ Type_Id type_id;
+ Array(struct Construct_Field) fields;
+};
+
+union Expression_Value
+{
+ struct Expression_Integer_Literal integer_literal;
+ struct Expression_Float_Literal float_literal;
+ struct Expression_String_Literal string_literal;
+ struct Expression_Bool_Literal bool_literal;
+ struct Expression_Name name;
+ struct Expression_Unary_Operator unary_operator;
+ struct Expression_Binary_Operator binary_operator;
+ struct Expression_Sizeof_Operator sizeof_operator;
+ struct Expression_Call call;
+ struct Expression_Member member;
+ struct Expression_Subscript subscript;
+ struct Expression_Cast cast;
+ struct Expression_Construct construct;
+};
+
+struct Expression
+{
+ enum Expression_Kind kind;
+ union Expression_Value value;
+
+ // TODO: fill this out with a basic type-checker
+ struct Type_Ref result_type;
+
+ struct Span span;
+};
+
+// a sequence of statements.
+struct Block
+{
+ Array(struct Statement*) statements;
+};
+
+// a function declaration.
+struct Function
+{
+ Function_Id id;
+ struct String name;
+
+ bool is_main;
+ bool main_takes_args;
+
+ // synthetic functions are unnamed closures.
+ bool synthetic;
+
+ struct Type_Ref return_type;
+ Array(struct Param) params;
+ bool variadic; // is last parameter variadic?
+
+ // first lowering pass only fills out the ast body and not the lowered body,
+ // to collect all top-declarations.
+ struct Tree_Block* ast_body;
+ struct Block* body;
+};
+
+struct Type_Hash_To_Id
+{
+ uint64 hash;
+ Type_Id id;
+};
+
+struct Type_Name_To_Id
+{
+ struct String name;
+ Type_Id id;
+};
+
+struct Type_Table
+{
+ // list of all types. index in this array is a type's unique identifier.
+ Array(struct Type*) entries;
+ // hash mapping of all synthetic types for de-duplication.
+ Array(struct Type_Hash_To_Id) by_hash;
+ // name mapping of all types.
+ Array(struct Type_Name_To_Id) by_name;
+
+ // seed table of all current primitive types, we fill it out
+ // one-by-one whenever we find one.
+ Type_Id primitive_int_id;
+ Type_Id primitive_uint_id;
+ Type_Id primitive_bool_id;
+ Type_Id primitive_string_id;
+ Type_Id primitive_float_id;
+ Type_Id primitive_byte_id;
+ Type_Id primitive_ascii_id;
+ Type_Id primitive_void_id;
+};
+
+struct Function_Name_To_Id
+{
+ struct String name;
+ Function_Id id;
+};
+
+struct Function_Table
+{
+ // list of all functions. index in this array is a functions's unique identifier.
+ Array(struct Function*) entries;
+ // name mapping of all functions
+ Array(struct Function_Name_To_Id) by_name;
+};
+
+// c import via pragma.
+struct Import
+{
+ struct String path;
+ struct Span span;
+};
+
+enum Diagnostic_Severity
+{
+ DIAGNOSTIC_NOTE,
+ DIAGNOSTIC_WARNING,
+ DIAGNOSTIC_ERROR,
+};
+
+// TODO: we might want to take this out to be used for other passes.
+struct Diagnostic
+{
+ enum Diagnostic_Severity severity;
+ struct Span span;
+ struct String message;
+};
+
+// a single translation unit.
+struct Unit
+{
+ struct Type_Table types;
+ struct Function_Table functions;
+ Array(struct Import) imports;
+
+ // types require a specific ordering taking into account
+ // their interconnected dependencies.
+ // this holds the final order necessary for correct compilation.
+ Array(Type_Id) type_emission_order;
+
+ bool had_error;
+ Array(struct Diagnostic) diagnostics;
+};
+
+REGION(struct Type, type)
+REGION(struct Function, function)
+REGION(struct Statement, statement)
+REGION(struct Expression, expression)
+REGION(struct Block, block)
+
+struct Type*
+type_new(Type_Id id, enum Type_Kind kind, struct String name, struct Span span)
+{
+ check(region_type_cursor < REGION_SIZE, "out of type memory");
+ struct Type* type = &region_type[region_type_cursor++];
+ *type = (struct Type){
+ .id = id,
+ .kind = kind,
+ .name = name,
+ .span = span,
+ };
+ return type;
+}
+
+struct Function*
+function_new(Function_Id id, struct String name)
+{
+ check(region_function_cursor < REGION_SIZE, "out of function memory");
+ struct Function* function = &region_function[region_function_cursor++];
+ *function = (struct Function){
+ .id = id,
+ .name = name,
+ };
+ return function;
+}
+
+struct Statement*
+statement_new(enum Statement_Kind kind, union Statement_Value value, struct Span span)
+{
+ check(region_statement_cursor < REGION_SIZE, "out of statement memory");
+ struct Statement* statement = &region_statement[region_statement_cursor++];
+ *statement = (struct Statement){
+ .kind = kind,
+ .value = value,
+ .span = span,
+ };
+ return statement;
+}
+
+struct Expression*
+expression_new(enum Expression_Kind kind, union Expression_Value value, struct Span span)
+{
+ check(region_expression_cursor < REGION_SIZE, "out of expression memory");
+ struct Expression* expression = &region_expression[region_expression_cursor++];
+ *expression = (struct Expression){
+ .kind = kind,
+ .value = value,
+ .span = span,
+ };
+ return expression;
+}
+
+struct Block*
+block_new(void)
+{
+ check(region_block_cursor < REGION_SIZE, "out of block memory");
+ struct Block* block = &region_block[region_block_cursor++];
+ *block = (struct Block){ 0 };
+ return block;
+}