/* * abstract syntax tree types for a catskill source. * * Copyright (c) 2025, Mel G. * * SPDX-License-Identifier: MPL-2.0 */ #pragma once #include "catboot.h" enum Unary_Operation { UNARY_NONE, UNARY_MINUS, UNARY_NOT, UNARY_BITWISE_NOT, UNARY_REFERENCE, UNARY_DEREFERENCE, }; enum Unary_Operation unary_operation_from_token(const struct Token* token) { switch (token->kind) { case TOKEN_MINUS: return UNARY_MINUS; case TOKEN_BANG: return UNARY_NOT; case TOKEN_TILDE: return UNARY_BITWISE_NOT; case TOKEN_AMPERSAND: return UNARY_REFERENCE; case TOKEN_STAR: return UNARY_DEREFERENCE; default: return UNARY_NONE; } } const ascii* unary_operation_to_string(enum Unary_Operation operation) { switch (operation) { case UNARY_MINUS: return "-"; case UNARY_NOT: return "!"; case UNARY_BITWISE_NOT: return "~"; case UNARY_REFERENCE: return "&"; case UNARY_DEREFERENCE: return "*"; default: failure("unexpected unary operation passed to `unary_operation_to_string`"); return nil; } } enum Binary_Operation { BINARY_NONE, BINARY_PLUS, BINARY_MINUS, BINARY_MULTIPLY, BINARY_DIVIDE, BINARY_MODULO, BINARY_POWER, BINARY_EQUAL, BINARY_NOT_EQUAL, BINARY_GREATER_THAN, BINARY_GREATER_THAN_EQUAL, BINARY_LESS_THAN, BINARY_LESS_THAN_EQUAL, BINARY_AND, BINARY_OR, BINARY_RANGE, BINARY_BITWISE_AND, BINARY_BITWISE_OR, BINARY_BITWISE_XOR, BINARY_BITWISE_LEFT_SHIFT, BINARY_BITWISE_RIGHT_SHIFT, BINARY_ASSIGN, BINARY_ASSIGN_PLUS, BINARY_ASSIGN_MINUS, BINARY_ASSIGN_MULTIPLY, BINARY_ASSIGN_DIVIDE, BINARY_ASSIGN_MODULO, BINARY_ASSIGN_AND, BINARY_ASSIGN_OR, BINARY_ASSIGN_BITWISE_AND, BINARY_ASSIGN_BITWISE_OR, BINARY_ASSIGN_BITWISE_XOR, BINARY_ASSIGN_BITWISE_LEFT_SHIFT, BINARY_ASSIGN_BITWISE_RIGHT_SHIFT, }; enum Binary_Operation binary_operation_from_token(const struct Token* token) { switch (token->kind) { case TOKEN_PLUS: return BINARY_PLUS; case TOKEN_MINUS: return BINARY_MINUS; case TOKEN_STAR: return BINARY_MULTIPLY; case TOKEN_SLASH: return BINARY_DIVIDE; case TOKEN_PERCENT: return BINARY_MODULO; case TOKEN_STAR_STAR: return BINARY_POWER; case TOKEN_EQUAL: return BINARY_EQUAL; case TOKEN_NOT_EQUAL: return BINARY_NOT_EQUAL; case TOKEN_GREATER: return BINARY_GREATER_THAN; case TOKEN_GREATER_EQUAL: return BINARY_GREATER_THAN_EQUAL; case TOKEN_LESS: return BINARY_LESS_THAN; case TOKEN_LESS_EQUAL: return BINARY_LESS_THAN_EQUAL; case TOKEN_AND: return BINARY_AND; case TOKEN_OR: return BINARY_OR; case TOKEN_DOT_DOT: return BINARY_RANGE; case TOKEN_AMPERSAND: return BINARY_BITWISE_AND; case TOKEN_PIPE: return BINARY_BITWISE_OR; case TOKEN_CARET: return BINARY_BITWISE_XOR; case TOKEN_LEFT_SHIFT: return BINARY_BITWISE_LEFT_SHIFT; case TOKEN_RIGHT_SHIFT: return BINARY_BITWISE_RIGHT_SHIFT; case TOKEN_ASSIGN: return BINARY_ASSIGN; case TOKEN_ASSIGN_PLUS: return BINARY_ASSIGN_PLUS; case TOKEN_ASSIGN_MINUS: return BINARY_ASSIGN_MINUS; case TOKEN_ASSIGN_STAR: return BINARY_ASSIGN_MULTIPLY; case TOKEN_ASSIGN_SLASH: return BINARY_ASSIGN_DIVIDE; case TOKEN_ASSIGN_PERCENT: return BINARY_ASSIGN_MODULO; case TOKEN_ASSIGN_AND: return BINARY_ASSIGN_AND; case TOKEN_ASSIGN_OR: return BINARY_ASSIGN_OR; case TOKEN_ASSIGN_AMPERSAND: return BINARY_ASSIGN_BITWISE_AND; case TOKEN_ASSIGN_PIPE: return BINARY_ASSIGN_BITWISE_OR; case TOKEN_ASSIGN_CARET: return BINARY_ASSIGN_BITWISE_XOR; case TOKEN_ASSIGN_LEFT_SHIFT: return BINARY_ASSIGN_BITWISE_LEFT_SHIFT; case TOKEN_ASSIGN_RIGHT_SHIFT: return BINARY_ASSIGN_BITWISE_RIGHT_SHIFT; default: return BINARY_NONE; } } // return the precedence of the given binary operation. // lower values are weaker, with 1 being the weakest. // the values are taken mostly the same as other C-family languages. uint binary_operation_precedence(enum Binary_Operation operation) { switch (operation) { // weakest case BINARY_ASSIGN: case BINARY_ASSIGN_PLUS: case BINARY_ASSIGN_MINUS: case BINARY_ASSIGN_MULTIPLY: case BINARY_ASSIGN_DIVIDE: case BINARY_ASSIGN_MODULO: case BINARY_ASSIGN_AND: case BINARY_ASSIGN_OR: case BINARY_ASSIGN_BITWISE_AND: case BINARY_ASSIGN_BITWISE_OR: case BINARY_ASSIGN_BITWISE_XOR: case BINARY_ASSIGN_BITWISE_LEFT_SHIFT: case BINARY_ASSIGN_BITWISE_RIGHT_SHIFT: return 1; case BINARY_RANGE: return 2; case BINARY_OR: return 3; case BINARY_AND: return 4; case BINARY_BITWISE_OR: return 5; case BINARY_BITWISE_XOR: return 6; case BINARY_BITWISE_AND: return 7; case BINARY_EQUAL: case BINARY_NOT_EQUAL: return 8; case BINARY_GREATER_THAN: case BINARY_GREATER_THAN_EQUAL: case BINARY_LESS_THAN: case BINARY_LESS_THAN_EQUAL: return 9; case BINARY_BITWISE_LEFT_SHIFT: case BINARY_BITWISE_RIGHT_SHIFT: return 10; case BINARY_PLUS: case BINARY_MINUS: return 11; case BINARY_MULTIPLY: case BINARY_DIVIDE: case BINARY_MODULO: return 12; case BINARY_POWER: return 13; // strongest default: failure("unexpected binary operation passed to `binary_operation_precedence`"); return 0; } } enum Binary_Operation_Associativity { BINARY_ASSOCIATIVITY_NONE, BINARY_ASSOCIATIVITY_LEFT, BINARY_ASSOCIATIVITY_RIGHT, }; enum Binary_Operation_Associativity binary_operation_associativity(enum Binary_Operation operation) { switch (operation) { case BINARY_ASSIGN: case BINARY_ASSIGN_PLUS: case BINARY_ASSIGN_MINUS: case BINARY_ASSIGN_MULTIPLY: case BINARY_ASSIGN_DIVIDE: case BINARY_ASSIGN_MODULO: case BINARY_ASSIGN_AND: case BINARY_ASSIGN_OR: case BINARY_ASSIGN_BITWISE_AND: case BINARY_ASSIGN_BITWISE_OR: case BINARY_ASSIGN_BITWISE_XOR: case BINARY_ASSIGN_BITWISE_LEFT_SHIFT: case BINARY_POWER: return BINARY_ASSOCIATIVITY_RIGHT; case BINARY_RANGE: return BINARY_ASSOCIATIVITY_NONE; default: return BINARY_ASSOCIATIVITY_LEFT; } } const ascii* binary_operation_to_string(enum Binary_Operation operation) { switch (operation) { case BINARY_PLUS: return "+"; case BINARY_MINUS: return "-"; case BINARY_MULTIPLY: return "*"; case BINARY_DIVIDE: return "/"; case BINARY_MODULO: return "%"; case BINARY_POWER: return "**"; case BINARY_EQUAL: return "=="; case BINARY_NOT_EQUAL: return "!="; case BINARY_GREATER_THAN: return ">"; case BINARY_GREATER_THAN_EQUAL: return ">="; case BINARY_LESS_THAN: return "<"; case BINARY_LESS_THAN_EQUAL: return "<="; case BINARY_AND: return "&&"; case BINARY_OR: return "||"; case BINARY_RANGE: return ".."; case BINARY_BITWISE_AND: return "&"; case BINARY_BITWISE_OR: return "|"; case BINARY_BITWISE_XOR: return "^"; case BINARY_BITWISE_LEFT_SHIFT: return "<<"; case BINARY_BITWISE_RIGHT_SHIFT: return ">>"; case BINARY_ASSIGN: return "="; case BINARY_ASSIGN_PLUS: return "+="; case BINARY_ASSIGN_MINUS: return "-="; case BINARY_ASSIGN_MULTIPLY: return "*="; case BINARY_ASSIGN_DIVIDE: return "/="; case BINARY_ASSIGN_MODULO: return "%="; case BINARY_ASSIGN_AND: return "&&="; case BINARY_ASSIGN_OR: return "||="; case BINARY_ASSIGN_BITWISE_AND: return "&="; case BINARY_ASSIGN_BITWISE_OR: return "|="; case BINARY_ASSIGN_BITWISE_XOR: return "^="; case BINARY_ASSIGN_BITWISE_LEFT_SHIFT: return "<<="; case BINARY_ASSIGN_BITWISE_RIGHT_SHIFT: return ">>="; default: failure("unexpected binary operation passed to `binary_operation_to_string`"); return nil; } } enum Increment_Decrement_Operation { INCREMENT_DECREMENT_NONE, INCREMENT_DECREMENT_INCREMENT, INCREMENT_DECREMENT_DECREMENT, }; enum Increment_Decrement_Operation increment_decrement_operation_from_token(const struct Token* token) { switch (token->kind) { case TOKEN_PLUS_PLUS: return INCREMENT_DECREMENT_INCREMENT; case TOKEN_MINUS_MINUS: return INCREMENT_DECREMENT_DECREMENT; default: return INCREMENT_DECREMENT_NONE; } } const ascii* increment_decrement_operation_to_string(enum Increment_Decrement_Operation operation) { switch (operation) { case INCREMENT_DECREMENT_INCREMENT: return "++"; case INCREMENT_DECREMENT_DECREMENT: return "--"; default: failure("unexpected increment/decrement operation passed to " "`increment_decrement_operation_to_string`"); return nil; } } // nodes are parts of the syntax tree that are reused often // and in different places. // a block of code, enclosed in curly braces. // represents a sequence of statements that are executed in order. struct Block_Node { struct Statement* statements; struct Span span; struct Cursor location; }; // a function header, describing the parameters and return type of function. // used both as a type and in full function definitions. struct Function_Header_Node { // linked list of parameters. // name, if given, is included in the type node. struct Type_Node* parameters_type_and_name; struct Type_Node* return_type; struct Span span; struct Cursor location; }; // a group of arguments, used in function calls and // constructions. // styled as either `1, 2, 3` or, when optional names // are given `a = 1, b = 2, c = 3`. struct Argument_Group_Node { // linked list of argument expressions. struct Expression* arguments; // names of the arguments, if given. // an unnamed argument is represented as an empty string. Array(struct String) argument_names; }; // a declaration of a variable, constant, or other binding, without a mutability // signifier, like `let` or `var`. // the mutability is determined by some outside context, where // a bare declaration in a for-loop, for example, is always mutable. struct Bare_Declaration_Node { Array(struct String) names; struct Expression* initializer; struct Type_Node* type; struct Span span; struct Cursor location; }; enum Type_Node_Type { TYPE_NODE_NONE, TYPE_NODE_NAME, TYPE_NODE_ARRAY, // an array of a type, `[int]`. TYPE_NODE_REFERENCE, // a reference to a type, `&int`. TYPE_NODE_MAYBE, // a type that may be null, `int?`. TYPE_NODE_TUPLE, // a tuple type, `(int string)`. TYPE_NODE_MAP, // a map type, `[string = int]`. TYPE_NODE_FUNCTION, // a function type, `fun (int) int`. TYPE_NODE_STRUCTURE, // a struct, invoked either with `type` or with `{}` when a type is inline. TYPE_NODE_VARIANT, // a tagged union. TYPE_NODE_CLASS, // a class of types, a.k.a. an interface. }; struct Type_Node_Name { struct String name; }; struct Type_Node_Array { struct Type_Node* element_type; }; struct Type_Node_Reference { struct Type_Node* referenced_type; }; struct Type_Node_Maybe { struct Type_Node* inner_type; }; struct Type_Node_Tuple { struct Type_Node* head; // the first type in the tuple, if any. }; struct Type_Node_Map { struct Type_Node* key_type; struct Type_Node* value_type; }; struct Type_Node_Function { struct Function_Header_Node header; }; struct Type_Node_Structure { // the fields of the structure, linked list of types and (required) names. struct Type_Node* fields; }; struct Type_Node_Variant { // the variants of the tagged union, linked list of (required) variant names and backing types. // if a variant has no backing type, it is TYPE_NODE_NONE. struct Type_Node* variants; }; struct Type_Node_Class { // linked list of the types of methods required to implement the class. // each node is required to have a name and be of TYPE_NODE_FUNCTION. struct Type_Node* methods; }; union Type_Node_Value { struct Type_Node_Name name; struct Type_Node_Array array; struct Type_Node_Reference reference; struct Type_Node_Maybe maybe; struct Type_Node_Tuple tuple; struct Type_Node_Map map; struct Type_Node_Function function; struct Type_Node_Structure structure; struct Type_Node_Variant variant; struct Type_Node_Class class; }; // a type node represents a type in the syntax tree. // currently, we only support types that are simple names. // or null types. // also includes the name of the field, member, parameter, etc., for which // the type is defined. struct Type_Node { enum Type_Node_Type type; union Type_Node_Value value; // note: we could also just include the token here i think? struct Span span; struct Cursor location; // usually a type is preceded by the name of the binding // it is assigned to, e.g. `x int`, `string y`, etc. // this is the name of that binding, for ease of listing. struct String value_name; // true if the type is used as a variadic parameter. // e.g. `fun (format string, ...args string)`. bool variadic; // if type is within a group of multiple types, // points to the next type within the group. struct Type_Node* next; }; REGION(struct Type_Node, type_node) // allocates a new type node in the global type node region. struct Type_Node* type_node_new( enum Type_Node_Type type, union Type_Node_Value value, struct Span span, struct Cursor location) { check(region_type_node_cursor < REGION_SIZE, "out of type node memory"); struct Type_Node* type_node = ®ion_type_node[region_type_node_cursor++]; *type_node = (struct Type_Node){ .type = type, .value = value, .span = span, .location = location, .value_name = string_empty(), .next = nil, }; return type_node; } // allocates a new type node with no value, used for `none` types. // this is used for types that are not specified, note that it is still // fully allocated and can be used in the syntax tree. struct Type_Node* type_node_none(struct Span span, struct Cursor location) { return type_node_new(TYPE_NODE_NONE, (union Type_Node_Value){ 0 }, span, location); } bool type_node_is_none(const struct Type_Node* type_node) { return type_node->type == TYPE_NODE_NONE; } enum Pragma_Type { PRAGMA_NONE, PRAGMA_UNKNOWN, PRAGMA_C_HEADER, // TODO: further pragma types. // NOTE: there would be plenty of use for user-defined pragmas, // acting similar to attributes in other languages or #[derive] macros // in Rust. for now we only support out hard-coded pragmas, // but it's something to definitely consider in the future. }; #define PRAGMA_ARGUMENT_MAX 3 struct Pragma_Argument { enum Pragma_Argument_Type { PRAGMA_ARGUMENT_NONE, PRAGMA_ARGUMENT_NAME_OR_STRING, PRAGMA_ARGUMENT_NUMBER, PRAGMA_ARGUMENT_DECIMAL, } type; union Pragma_Argument_Value { struct String name_or_string; int64 number; float64 decimal; } value; }; // a "pragma" is what we call compiler hints used for giving almost every piece of information // the compiler might require to compile your code. // you can recognize a pragma by the '|' token, like in '| c_header "stdio.h"'. // their use ranges from setting alignment/padding for structures, defining default copy or move // behaviour, to including different C compilation units and other catskill modules. // pragmas are parsed as lone statements in the source code at first, but are then // "attached" to the relevant nodes of the type the pragma is relevant to. struct Pragma_Node { enum Pragma_Type type; struct Pragma_Argument arguments[PRAGMA_ARGUMENT_MAX]; uint argument_count; struct Span span; struct Cursor location; struct Pragma_Node* next; // further pragmas on the same line. }; REGION(struct Pragma_Node, pragma_node) struct Pragma_Node* pragma_node_new(enum Pragma_Type type, struct Span span, struct Cursor location) { check(region_pragma_node_cursor < REGION_SIZE, "out of pragma node memory"); struct Pragma_Node* pragma = ®ion_pragma_node[region_pragma_node_cursor++]; *pragma = (struct Pragma_Node){ .type = type, .arguments = {}, .argument_count = 0, .span = span, .location = location, .next = nil, }; return pragma; } enum Pragma_Type pragma_type_from_string(struct String name) { // look up hash values with: // `echo -ne "string to hash" | cksum` uint32 hash = crc32_posix(name); switch (hash) { case 2852954401: // "c_header" return PRAGMA_C_HEADER; default: return PRAGMA_UNKNOWN; } } const ascii* pragma_type_to_string(enum Pragma_Type type) { switch (type) { case PRAGMA_C_HEADER: return "c_header"; case PRAGMA_UNKNOWN: return "unknown"; default: failure("unexpected pragma type passed to `pragma_type_to_string`"); return nil; } } 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_GROUP, EXPRESSION_CONSTRUCT, EXPRESSION_CALL, EXPRESSION_SUBSCRIPT, EXPRESSION_MEMBER, EXPRESSION_INCREMENT_DECREMENT, EXPRESSION_TRY, EXPRESSION_MUST, EXPRESSION_FUNCTION, EXPRESSION_TYPE, }; struct Expression_Integer_Literal { int64 value; // might not fit entire number given in source. }; 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 { enum Binary_Operation operation; struct Expression* left_operand; struct Expression* right_operand; }; struct Expression_Group { struct Expression* inner_expression; }; struct Expression_Call { struct Expression* subject; struct Argument_Group_Node argument_group; }; struct Expression_Construct { // this should be the type to construct, e.g. `int` or `string` or a generic like `Maybe(X)` // right now, we can't guarantee it fully. struct Expression* subject; struct Argument_Group_Node argument_group; }; struct Expression_Subscript { struct Expression* subject; struct Expression* index; }; struct Expression_Member { struct Expression* subject; struct String name; }; struct Expression_Increment_Decrement { // whether the increment/decrement is a prefix or postfix operation. bool prefix; struct Expression* subject; enum Increment_Decrement_Operation operation; }; struct Expression_Try { struct Expression* expression; }; struct Expression_Must { struct Expression* expression; }; struct Expression_Function { struct Function_Header_Node header; struct Block_Node body; }; struct Expression_Type { struct Type_Node* type; }; 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_Group group; struct Expression_Call call; struct Expression_Construct construct; struct Expression_Subscript subscript; struct Expression_Member member; struct Expression_Increment_Decrement increment_decrement; struct Expression_Try try; struct Expression_Must must; struct Expression_Function function; struct Expression_Type type; }; struct Expression { enum Expression_Kind kind; union Expression_Value value; struct Span span; struct Cursor location; // if expression is within a group of multiple expressions, // points to the next expression within it. struct Expression* next; }; REGION(struct Expression, expression) struct Expression* expression_new( enum Expression_Kind kind, union Expression_Value value, struct Span span, struct Cursor location) { check(region_expression_cursor < REGION_SIZE, "out of expression memory"); struct Expression* expression = ®ion_expression[region_expression_cursor++]; *expression = (struct Expression){ .kind = kind, .value = value, .span = span, .location = location, .next = nil, }; return expression; } enum Statement_Kind { STATEMENT_NONE, STATEMENT_EXPRESSION, STATEMENT_DECLARATION, // NOTE: a block could be an expression in the future. STATEMENT_BLOCK, STATEMENT_CONDITIONAL, STATEMENT_LOOP, STATEMENT_RETURN, STATEMENT_BREAK, STATEMENT_CONTINUE, STATEMENT_DEFER, STATEMENT_PRAGMA, }; struct Statement_Value_Expression { struct Expression* inner; }; enum Statement_Declaration_Kind { STATEMENT_DECLARATION_NONE, STATEMENT_DECLARATION_VARIABLE, STATEMENT_DECLARATION_CONSTANT, }; enum Statement_Declaration_Kind statement_declaration_kind_from_token(const struct Token* token) { switch (token->kind) { case TOKEN_WORD_VAR: return STATEMENT_DECLARATION_VARIABLE; case TOKEN_WORD_LET: return STATEMENT_DECLARATION_CONSTANT; default: return STATEMENT_DECLARATION_NONE; } } struct Statement_Value_Declaration { enum Statement_Declaration_Kind kind; struct Bare_Declaration_Node inner; }; struct Statement_Value_Block { struct Block_Node inner; // the block of statements. }; #define STATEMENT_VALUE_CONDITIONAL_MAX 8 struct Statement_Value_Conditional { struct Statement_Conditional_Branch { // if nil, the condition is always true. struct Expression* when; struct Block_Node then; } conditions[STATEMENT_VALUE_CONDITIONAL_MAX]; uint condition_count; }; enum Statement_Loop_Style { STATEMENT_LOOP_STYLE_NONE, STATEMENT_LOOP_STYLE_C, // for i int = 0; i < 10; ++i {} STATEMENT_LOOP_STYLE_FOR_EACH, // for x Obj = list {} STATEMENT_LOOP_STYLE_WHILE, // while true {} STATEMENT_LOOP_STYLE_ENDLESS, // while {} }; // stands for both `for` and `while` loops. struct Statement_Value_Loop { enum Statement_Loop_Style style; struct Bare_Declaration_Node declaration; struct Expression* condition; struct Expression* iteration; struct Block_Node body; }; struct Statement_Value_Return { // nil if there is no return value. struct Expression* value; }; struct Statement_Value_Defer { // either a simple expression, or, if expression is nil, // a block of code to execute. struct Expression* expression; struct Block_Node block; }; struct Statement_Value_Pragma { struct Pragma_Node* inner; }; union Statement_Value { struct Statement_Value_Expression expression; struct Statement_Value_Declaration declaration; struct Statement_Value_Block block; struct Statement_Value_Conditional conditional; struct Statement_Value_Loop loop; struct Statement_Value_Return return_value; struct Statement_Value_Defer defer; struct Statement_Value_Pragma pragma; }; struct Statement { enum Statement_Kind kind; union Statement_Value value; struct Span span; struct Cursor location; // if statement is within a group of multiple statements, // points to the next statement within it. struct Statement* next; }; REGION(struct Statement, statement) struct Statement* statement_new( enum Statement_Kind kind, union Statement_Value value, struct Span span, struct Cursor location) { check(region_statement_cursor < REGION_SIZE, "out of statement memory"); struct Statement* statement = ®ion_statement[region_statement_cursor++]; *statement = (struct Statement){ .kind = kind, .value = value, .span = span, .location = location, .next = nil, }; return statement; } // the top-level tree of a single catskill source file. struct Tree { struct Statement* top_level_statements; };