#! grammar: #! * type ..... -> type_sum ['!' type]; type_sum -> type_atom {'|' type_atom}; type_atom includes '(' type ')', identifier {'.' member | '(' [expr {',' expr} [',']] ')' | '[' expr ']'}, '?' type_atom, ('@' | '*') ['mut'] type_atom, ['@'] 'proc' '(' parameters ')' type, '[' (expr | '_') ']' ['mut'] type_atom, '[' [';' expr] ']' ['mut'] type_atom, record/enum/union declarations, and ('distinct' | 'alias') type. #! * binding ... -> :: | := #! * literal .. -> int | float | string | 'true' | 'false' | 'null' #! * op ....... -> '+' | '-' | '*' | '/' | '&' | 'xor' | '|' | '<<' | '>>' | '<<|' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or' #! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '~' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block) #! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string #! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently. #! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence. #! value expressions also accept 'if' guard ['|' identifier '|'] expr 'else' expr, canonicalized to matches; else is required. #! bare braces containing statement syntax form a value block; contextual record literals keep their initializer grammar. #! 'yield' requires an expression on the same line and targets the nearest enclosing value construct, not a statement block. #! * block .... -> '{' {stmt} '}' #! * arm ...... -> block | stmt (single statements become canonical blocks) #! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated. #! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes. #! * stmt ..... -> decl | expr | block | ('defer' | 'errdefer' ['|' identifier '|']) arm | expr assignment_op expr | 'return' [expr] | 'yield' expr | 'break' | 'continue' | 'unreachable' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm #! assignment_op is '=' or a contiguous compound: '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|='. #! braceless guards/iterables require parentheses unless they are calls, explicit comptime expressions or optional captures; else binds to the nearest if. bitwise-OR headers require parentheses before captures. #! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -, !, ~, &, try higher than multiplication/division, addition/subtraction, shifts << / >> / <<|, bitwise &/xor/|, relational comparisons, equality, and, or, ranges/catch. type composition | binds tighter than fallible !; pointer/optional/array prefixes bind to type atoms. compound range endpoints require their own parentheses. #! compound assignments share their target with the canonical binary value's left operand; lowering evaluates the place and loads its old value once before the RHS. #! binary operators associate left; unary operators associate right. import "@std/debug" import "@std/mem" import "@std/arraylist" import "@source/diagnostics" import "@source/strpool" Span :: alias diagnostics.Span DiagnosticId :: alias diagnostics.DiagnosticId no_diagnostic :: alias diagnostics.no_diagnostic NodeId :: distinct u32 @hide:file no_node :: maxval!(NodeId) #! void literals are generated constants only; there is no source void literal. LiteralKind :: enum { int float string bool null void undefined } ExprKind :: enum { literal_int literal_float literal_string literal_bool identifier unary binary call range array_type pointer_type function_type array_literal record_type record_literal enum_type union_type distinct_type alias_expr union_tag_type enum_literal match_expr index selection invalid slice_type optional_type slice builtin_call literal_null fallible_type sum_type try_expr catch_expr typed_constant comptime_expr literal_void literal_undefined block_expr } DeclKind :: enum { statement function import invalid } StmtKind :: enum { declaration expression assignment_stmt return_stmt yield_stmt break_stmt continue_stmt unreachable_stmt if_stmt while_stmt for_stmt defer_stmt errdefer_stmt block_stmt invalid } Mutability :: enum { constant, variable } UnaryOperator :: enum { negate logical_not bit_not address_of dereference unwrap } BinaryOperator :: enum { add, subtract multiply, divide bit_and, bit_or, bit_xor shift_left, shift_right, shift_left_saturating equal, not_equal less, less_equal greater, greater_equal logical_and, logical_or } @hide:file NodeKind :: enum { literal_int literal_float literal_string literal_bool expr_identifier expr_unary expr_binary expr_call expr_range expr_array_type expr_pointer_type expr_function_type expr_array_literal expr_record_type expr_record_literal record_field expr_enum_type expr_union_type expr_distinct_type expr_alias expr_union_tag_type expr_enum_literal expr_match match_arm expr_index expr_selection stmt_expr stmt_decl stmt_return stmt_break stmt_continue stmt_if stmt_assignment stmt_while stmt_for func_decl param_decl import_decl block package invalid_expr invalid_stmt invalid_decl expr_slice_type expr_optional_type expr_slice expr_builtin_call literal_null expr_fallible_type expr_sum_type expr_try expr_catch stmt_defer stmt_errdefer stmt_block expr_typed_constant expr_comptime literal_float_value literal_void literal_undefined expr_block stmt_yield stmt_unreachable } @hide:file NodeData :: union { node_id NodeId extra u32 name_id strpool.StringId diagnostic DiagnosticId mutability Mutability unary_operator UnaryOperator binary_operator BinaryOperator } @hide:file file_local_flag u8 :: 0b0001 @hide:file foreign_type_flag u8 :: 0b0010 @hide:file variadic_flag u8 :: 0b0100 @hide:file opaque_flag u8 :: 0b1000 @hide:file optional_match_flag u8 :: 0b00010000 @hide:file compound_assignment_flag u8 :: 0b00100000 @hide:file Node :: struct { kind NodeKind # use the padding before the payload words rather than enlarging every node. flags u8 = 0 main NodeData = NodeData{ node_id = no_node } data0 NodeData = NodeData{ node_id = no_node } data1 NodeData = NodeData{ node_id = no_node } } @hide:file Storage :: struct { nodes arraylist.ArrayList(Node) extra arraylist.ArrayList(u32) spans arraylist.ArrayList(Span) sources arraylist.ArrayList(diagnostics.Source) generated arraylist.ArrayList([]mut u8) } #! owns node storage, source descriptors, and generated text; borrows registered source paths and text. Builder :: struct { storage Storage source []u8 source_id diagnostics.SourceId = diagnostics.SourceId(0) } #! owns storage after finish, or borrows it after view; source paths and non-generated text remain borrowed. Tree :: struct { storage Storage root NodeId } #! transient projection, not stored per import; test_only decodes an existing Node payload word. ImportView :: struct { span Span path Span namespace ?strpool.StringId test_only bool } StatementDeclView :: struct { span Span name Span type ?NodeId mutability Mutability initializer NodeId } FunctionDeclView :: struct { span Span name Span return_type NodeId body NodeId } ParamView :: struct { span Span name Span type NodeId comptime bool = false } UnaryExprView :: struct { span Span operator UnaryOperator operand NodeId } BinaryExprView :: struct { span Span operator BinaryOperator left NodeId right NodeId } TypedConstantView :: struct { type NodeId value NodeId } CatchExprView :: struct { span Span operand NodeId capture ?NodeId handler NodeId handler_is_block bool } CleanupView :: struct { span Span body NodeId capture ?NodeId } CallExprView :: struct { span Span callee NodeId } ArrayTypeView :: struct { span Span count ?NodeId element NodeId mutable bool } PointerTypeView :: struct { element NodeId mutable bool many bool } SliceTypeView :: struct { element NodeId mutable bool sentinel ?NodeId } SliceView :: struct { base NodeId start ?NodeId end ?NodeId sentinel ?NodeId } BuiltinCallView :: struct { name NodeId } RecordLiteralView :: struct { span Span type ?NodeId } FieldView :: struct { span Span name ?NodeId type ?NodeId value ?NodeId } EnumTypeView :: struct { backing ?NodeId } UnionTypeView :: struct { tag ?NodeId, auto_tag bool, } MatchView :: struct { subject NodeId, optional_capture bool = false, } MatchArmView :: struct { capture ?NodeId body NodeId expression bool catch_all bool is_inline bool = false tag_capture ?NodeId = null tag_capture_present bool = false } IndexView :: struct { span Span base NodeId index NodeId } SelectionView :: struct { span Span base NodeId member NodeId } ReturnView :: struct { span Span value ?NodeId } IfView :: struct { span Span condition NodeId then_block NodeId else_block ?NodeId capture ?NodeId = null } AssignmentView :: struct { span Span target NodeId value NodeId compound bool = false } WhileView :: struct { span Span condition NodeId body NodeId capture ?NodeId = null } RangeView :: struct { span Span start NodeId end NodeId inclusive bool } ForView :: struct { span Span iterable NodeId capture ?NodeId index ?NodeId pointer_capture bool index_capture bool body NodeId is_inline bool = false } InvalidView :: struct { span Span diagnostic DiagnosticId } #! retains `allocator` for node storage and borrows `source`; release with deinit_builder. init_builder proc(allocator mem.Allocator, source []u8) Builder { return Builder{ storage = Storage{ nodes = arraylist.init(allocator), extra = arraylist.init(allocator), spans = arraylist.init(allocator), sources = arraylist.init(allocator), generated = arraylist.init(allocator), }, source = source, } } #! registers borrowed `path` and `text`; both must remain valid until deinit_builder or deinit. set_source proc( builder @mut Builder, path, text []u8, ) diagnostics.SourceId ! mem.AllocError { id :: diagnostics.SourceId(builder.storage.sources.items.len) try arraylist.append(&builder.storage.sources, diagnostics.Source{ path = path, text = text, }) builder.source = text builder.source_id = id return id } #! removes the latest source after its failed parse has rolled back all nodes. remove_source proc(builder @mut Builder) void { debug.assert(builder.storage.sources.items.len > 0) _ = arraylist.pop(&builder.storage.sources) if (builder.storage.sources.items.len == 0) { builder.source = &[] builder.source_id = diagnostics.SourceId(0) return } builder.source_id = diagnostics.SourceId(builder.storage.sources.items.len - 1) builder.source = builder.storage.sources.items[usize(builder.source_id)].text } #! borrows `builder` storage; refresh after builder mutation and never pass this view to deinit. view proc(builder @Builder, root NodeId) Tree { return Tree{ storage = builder.storage, root = root } } source_files proc(tree @Tree) []diagnostics.Source { return tree.storage.sources.items } #! releases owned AST, generated text, and source descriptors, without freeing borrowed sources. deinit_builder proc(builder @mut Builder) void { for (builder.storage.generated.items) |text| mem.free(builder.storage.generated.allocator, text) arraylist.deinit(&builder.storage.generated) arraylist.deinit(&builder.storage.nodes) arraylist.deinit(&builder.storage.extra) arraylist.deinit(&builder.storage.spans) arraylist.deinit(&builder.storage.sources) } #! transfers storage and source borrows to a caller-owned Tree; release with deinit. finish proc(builder @mut Builder, root NodeId) Tree { tree :: view(builder, root) allocator :: builder.storage.nodes.allocator builder.storage = Storage{ nodes = arraylist.init(allocator), extra = arraylist.init(allocator), spans = arraylist.init(allocator), sources = arraylist.init(allocator), generated = arraylist.init(allocator), } builder.source = &[] builder.source_id = diagnostics.SourceId(0) return tree } #! releases owned AST, generated text, and source descriptors, without freeing borrowed sources. deinit proc(tree @mut Tree) void { for (tree.storage.generated.items) |text| mem.free(tree.storage.generated.allocator, text) arraylist.deinit(&tree.storage.generated) arraylist.deinit(&tree.storage.nodes) arraylist.deinit(&tree.storage.extra) arraylist.deinit(&tree.storage.spans) arraylist.deinit(&tree.storage.sources) } add_literal proc(builder @mut Builder, kind LiteralKind, span Span) NodeId ! mem.AllocError { node_kind :: match kind { .int: .literal_int .float: .literal_float .string: .literal_string .bool: .literal_bool .null: .literal_null .void: .literal_void .undefined: .literal_undefined } return try add_node(builder, Node{ kind = node_kind }, span) } add_identifier proc( builder @mut Builder, span Span, interned_name strpool.StringId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_identifier, main = NodeData{ name_id = interned_name }, }, span) } #! copies selector spelling into AST-owned storage, independently of its diagnostic span. add_generated_identifier proc( builder @mut Builder, at Span, interned_name strpool.StringId, text []u8, ) NodeId ! mem.AllocError { id :: try add_generated_literal(builder, at, .string, text) node :: &builder.storage.nodes.items[usize(id)] node.kind = .expr_identifier node.data1 = node.main node.main = NodeData{ name_id = interned_name } return id } add_unary proc( builder @mut Builder, span Span, operator UnaryOperator, operand NodeId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_unary, main = NodeData{ unary_operator = operator }, data0 = NodeData{ node_id = operand }, }, span) } add_binary proc( builder @mut Builder, span Span, operator BinaryOperator, left NodeId, right NodeId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_binary, main = NodeData{ binary_operator = operator }, data0 = NodeData{ node_id = left }, data1 = NodeData{ node_id = right }, }, span) } #! preserves a generated constant's declared type without introducing a runtime binding. add_typed_constant proc( builder @mut Builder, at Span, type, value NodeId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_typed_constant, data0 = NodeData{ node_id = type }, data1 = NodeData{ node_id = value }, }, at) } add_comptime_expr proc(builder @mut Builder, at Span, operand NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_comptime, main = NodeData{ node_id = operand }, }, at) } #! retains the exact IEEE representation, including signed zero and NaN payloads. add_float proc(builder @mut Builder, at Span, value f64) NodeId ! mem.AllocError { bits :: ptrcast!(@u64, &value)^ return try add_node(builder, Node{ kind = .literal_float_value, data0 = NodeData{ extra = u32(bits & 4294967295) }, data1 = NodeData{ extra = u32(bits >> 32) }, }, at) } add_binary_type proc( builder @mut Builder, span Span, left, right NodeId, fallible bool, ) NodeId ! mem.AllocError { kind NodeKind :: if (fallible) .expr_fallible_type else .expr_sum_type return try add_node(builder, Node{ kind = kind, main = NodeData{ binary_operator = .add }, data0 = NodeData{ node_id = left }, data1 = NodeData{ node_id = right }, }, span) } add_try_expr proc(builder @mut Builder, span Span, operand NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_try, main = NodeData{ node_id = operand }, }, span) } add_catch_expr proc( builder @mut Builder, span Span, operand NodeId, capture ?NodeId, handler NodeId, handler_is_block bool, ) NodeId ! mem.AllocError { capture_id :: if (capture) |id| u32(id) else u32(no_node) block_flag :: if (handler_is_block) u32(1) else u32(0) extra :: try add_extra(builder, capture_id) _ = try add_extra(builder, block_flag) return try add_node(builder, Node{ kind = .expr_catch, main = NodeData{ extra = extra }, data0 = NodeData{ node_id = operand }, data1 = NodeData{ node_id = handler }, }, span) } add_range proc( builder @mut Builder, span Span, start NodeId, end NodeId, inclusive bool, ) NodeId ! mem.AllocError { flag :: if (inclusive) u32(1) else u32(0) return try add_node(builder, Node{ kind = .expr_range, main = NodeData{ extra = flag }, data0 = NodeData{ node_id = start }, data1 = NodeData{ node_id = end }, }, span) } add_call proc( builder @mut Builder, span Span, callee NodeId, arguments []NodeId, ) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (arguments) |argument| _ = try add_extra(builder, u32(argument)) return try add_node(builder, Node{ kind = .expr_call, main = NodeData{ node_id = callee }, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_builtin_call proc(builder @mut Builder, span Span, name NodeId, arguments []NodeId) NodeId ! mem.AllocError { id :: try add_call(builder, span, name, arguments) builder.storage.nodes.items[usize(id)].kind = .expr_builtin_call return id } add_optional_type proc(builder @mut Builder, span Span, element NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_optional_type, main = NodeData{ node_id = element } }, span) } add_slice_type proc(builder @mut Builder, span Span, element NodeId, mutable bool, sentinel ?NodeId) NodeId ! mem.AllocError { flag u32 :: if (mutable) 1 else 0 raw_sentinel :: if (sentinel) |id| id else no_node return try add_node(builder, Node{ kind = .expr_slice_type, main = NodeData{ extra = flag }, data0 = NodeData{ node_id = raw_sentinel }, data1 = NodeData{ node_id = element }, }, span) } add_slice proc(builder @mut Builder, span Span, base NodeId, start, end, sentinel ?NodeId) NodeId ! mem.AllocError { raw_start :: if (start) |id| u32(id) else u32(no_node) raw_end :: if (end) |id| u32(id) else u32(no_node) raw_sentinel :: if (sentinel) |id| u32(id) else u32(no_node) extra :: try add_extra(builder, raw_start) _ = try add_extra(builder, raw_end) _ = try add_extra(builder, raw_sentinel) return try add_node(builder, Node{ kind = .expr_slice, main = NodeData{ node_id = base }, data0 = NodeData{ extra = extra }, }, span) } add_array_type proc( builder @mut Builder, span Span, count ?NodeId, element NodeId, mutable bool, ) NodeId ! mem.AllocError { raw_count :: if (count) |id| id else no_node flag :: if (mutable) u32(1) else u32(0) return try add_node(builder, Node{ kind = .expr_array_type, main = NodeData{ extra = flag }, data0 = NodeData{ node_id = raw_count }, data1 = NodeData{ node_id = element }, }, span) } add_pointer_type proc( builder @mut Builder, span Span, element NodeId, mutable, many bool, ) NodeId ! mem.AllocError { mutable_flag :: if (mutable) u32(1) else u32(0) many_flag :: if (many) u32(1) else u32(0) return try add_node(builder, Node{ kind = .expr_pointer_type, main = NodeData{ extra = mutable_flag }, data0 = NodeData{ extra = many_flag }, data1 = NodeData{ node_id = element }, }, span) } add_function_type proc( builder @mut Builder, span Span, result NodeId, params []NodeId, ) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (params) |parameter| _ = try add_extra(builder, u32(parameter)) return try add_node(builder, Node{ kind = .expr_function_type, main = NodeData{ node_id = result }, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (elements) |element| _ = try add_extra(builder, u32(element)) return try add_node(builder, Node{ kind = .expr_array_literal, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_record_type proc(builder @mut Builder, span Span, fields []NodeId) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (fields) |field_id| _ = try add_extra(builder, u32(field_id)) return try add_node(builder, Node{ kind = .expr_record_type, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } set_record_c_layout proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] debug.assert(node.kind == .expr_record_type) node.main = NodeData{ extra = 1 } } record_c_layout proc(tree @Tree, id NodeId) bool { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_record_type) return node.main.extra == 1 } add_enum_type proc(builder @mut Builder, span Span, backing ?NodeId, members []NodeId) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (members) |member| _ = try add_extra(builder, u32(member)) raw_backing :: if (backing) |id| id else no_node return try add_node(builder, Node{ kind = .expr_enum_type, main = NodeData{ node_id = raw_backing }, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_union_type proc(builder @mut Builder, span Span, tag ?NodeId, auto_tag bool, fields []NodeId) NodeId ! mem.AllocError { raw_tag :: if (tag) |id| u32(id) else u32(no_node) tag_flag :: if (auto_tag) u32(1) else u32(0) metadata :: try add_extra(builder, raw_tag) _ = try add_extra(builder, tag_flag) start :: builder.storage.extra.items.len for (fields) |field_id| _ = try add_extra(builder, u32(field_id)) return try add_node(builder, Node{ kind = .expr_union_type, main = NodeData{ extra = metadata }, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_type_operand proc(builder @mut Builder, span Span, operand NodeId, transparent bool) NodeId ! mem.AllocError { kind :: if (transparent) NodeKind.expr_alias else NodeKind.expr_distinct_type return try add_node(builder, Node{ kind = kind, main = NodeData{ node_id = operand }, }, span) } add_union_tag_type proc(builder @mut Builder, span Span, operand NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_union_tag_type, main = NodeData{ node_id = operand }, }, span) } add_enum_literal proc(builder @mut Builder, span Span, member NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_enum_literal, main = NodeData{ node_id = member }, }, span) } add_match proc(builder @mut Builder, span Span, subject NodeId, arms []NodeId) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (arms) |arm| _ = try add_extra(builder, u32(arm)) return try add_node(builder, Node{ kind = .expr_match, main = NodeData{ node_id = subject }, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } #! marks the canonical presence match emitted for an optional-capture conditional. set_match_optional proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] debug.assert(node.kind == .expr_match) node.flags = node.flags | optional_match_flag } add_match_arm proc( builder @mut Builder, span Span, patterns []NodeId, capture ?NodeId, body NodeId, expression, catch_all bool, ) NodeId ! mem.AllocError { raw_capture :: if (capture) |id| u32(id) else u32(no_node) expression_flag :: if (expression) u32(1) else u32(0) catch_all_flag :: if (catch_all) u32(1) else u32(0) start :: try add_extra(builder, raw_capture) _ = try add_extra(builder, expression_flag) _ = try add_extra(builder, catch_all_flag) _ = try add_extra(builder, u32(no_node)) for (patterns) |pattern| _ = try add_extra(builder, u32(pattern)) return try add_node(builder, Node{ kind = .match_arm, main = NodeData{ node_id = body }, data0 = NodeData{ extra = start }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } set_match_arm_inline proc( builder @mut Builder, id NodeId, tag_capture ?NodeId, tag_capture_present bool, ) void { node :: get_node(&builder.storage, id) debug.assert(node.kind == .match_arm) extra :: usize(node.data0.extra) builder.storage.extra.items[extra + 1] = builder.storage.extra.items[extra + 1] | 2 if (tag_capture_present) builder.storage.extra.items[extra + 1] = builder.storage.extra.items[extra + 1] | 4 builder.storage.extra.items[extra + 3] = if (tag_capture) |capture| u32(capture) else u32(no_node) } add_record_literal proc( builder @mut Builder, span Span, type ?NodeId, fields []NodeId, ) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (fields) |field_id| _ = try add_extra(builder, u32(field_id)) raw_type :: if (type) |id| id else no_node return try add_node(builder, Node{ kind = .expr_record_literal, main = NodeData{ node_id = raw_type }, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_field proc( builder @mut Builder, span Span, name, type, value ?NodeId, ) NodeId ! mem.AllocError { raw_name :: if (name) |id| id else no_node raw_type :: if (type) |id| id else no_node raw_value :: if (value) |id| id else no_node return try add_node(builder, Node{ kind = .record_field, main = NodeData{ node_id = raw_name }, data0 = NodeData{ node_id = raw_type }, data1 = NodeData{ node_id = raw_value }, }, span) } add_index proc(builder @mut Builder, span Span, base, index NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_index, data0 = NodeData{ node_id = base }, data1 = NodeData{ node_id = index }, }, span) } add_selection proc( builder @mut Builder, span Span, base, member NodeId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_selection, data0 = NodeData{ node_id = base }, data1 = NodeData{ node_id = member }, }, span) } add_expression_stmt proc(builder @mut Builder, expression NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_expr, main = NodeData{ node_id = expression }, }, builder_span(builder, expression)) } add_cleanup_stmt proc( builder @mut Builder, span Span, body NodeId, capture ?NodeId, error_only bool, ) NodeId ! mem.AllocError { kind NodeKind :: if (error_only) .stmt_errdefer else .stmt_defer capture_id :: if (capture) |id| id else no_node return try add_node(builder, Node{ kind = kind, main = NodeData{ node_id = body }, data0 = NodeData{ node_id = capture_id }, }, span) } add_block_stmt proc(builder @mut Builder, body NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_block, main = NodeData{ node_id = body }, }, builder_span(builder, body)) } add_block_expr proc(builder @mut Builder, span Span, body NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_block, main = NodeData{ node_id = body }, }, span) } add_return_stmt proc(builder @mut Builder, span Span, value ?NodeId) NodeId ! mem.AllocError { raw_value :: if (value) |id| id else no_node return try add_node(builder, Node{ kind = .stmt_return, main = NodeData{ node_id = raw_value }, }, span) } add_yield_stmt proc(builder @mut Builder, span Span, value NodeId) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_yield, main = NodeData{ node_id = value }, }, span) } add_break_stmt proc(builder @mut Builder, span Span) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_break }, span) } add_continue_stmt proc(builder @mut Builder, span Span) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_continue }, span) } add_unreachable_stmt proc(builder @mut Builder, span Span) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_unreachable }, span) } add_if_stmt proc( builder @mut Builder, span Span, condition NodeId, then_block NodeId, else_block ?NodeId, ) NodeId ! mem.AllocError { alternative :: if (else_block) |id| id else no_node extra :: try add_extra(builder, u32(condition)) _ = try add_extra(builder, u32(no_node)) return try add_node(builder, Node{ kind = .stmt_if, main = NodeData{ extra = extra }, data0 = NodeData{ node_id = then_block }, data1 = NodeData{ node_id = alternative }, }, span) } add_assignment_stmt proc( builder @mut Builder, span Span, target NodeId, value NodeId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_assignment, main = NodeData{ node_id = target }, data0 = NodeData{ node_id = value }, }, span) } set_assignment_compound proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] debug.assert(node.kind == .stmt_assignment) node.flags = node.flags | compound_assignment_flag } add_while_stmt proc( builder @mut Builder, span Span, condition NodeId, body NodeId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .stmt_while, main = NodeData{ node_id = condition }, data0 = NodeData{ node_id = body }, }, span) } set_capture proc(builder @mut Builder, id NodeId, capture NodeId) void { node :: &builder.storage.nodes.items[usize(id)] if (node.kind == .stmt_if) builder.storage.extra.items[usize(node.main.extra) + 1] = u32(capture) else { debug.assert(node.kind == .stmt_while) node.data1.node_id = capture } } add_for_stmt proc( builder @mut Builder, span Span, iterable NodeId, capture, index ?NodeId, pointer_capture, index_capture bool, body NodeId, ) NodeId ! mem.AllocError { raw_capture :: if (capture) |id| u32(id) else u32(no_node) raw_index :: if (index) |id| u32(id) else u32(no_node) pointer_flag :: if (pointer_capture) u32(1) else u32(0) index_flag :: if (index_capture) u32(1) else u32(0) extra :: try add_extra(builder, raw_capture) _ = try add_extra(builder, raw_index) _ = try add_extra(builder, pointer_flag) _ = try add_extra(builder, index_flag) return try add_node(builder, Node{ kind = .stmt_for, main = NodeData{ node_id = iterable }, data0 = NodeData{ node_id = body }, data1 = NodeData{ extra = extra }, }, span) } set_for_inline proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] debug.assert(node.kind == .stmt_for) flags :: &builder.storage.extra.items[usize(node.data1.extra) + 3] flags^ = flags^ | 2 } add_statement_decl proc( builder @mut Builder, span Span, name Span, interned_name strpool.StringId, type ?NodeId, mutability Mutability, initializer NodeId, ) NodeId ! mem.AllocError { raw_type :: if (type) |id| u32(id) else u32(no_node) extra :: try add_extra(builder, name.start) _ = try add_extra(builder, name.end) _ = try add_extra(builder, raw_type) _ = try add_extra(builder, u32(initializer)) return try add_node(builder, Node{ kind = .stmt_decl, main = NodeData{ extra = extra }, data0 = NodeData{ mutability = mutability }, data1 = NodeData{ name_id = interned_name }, }, span) } add_param proc( builder @mut Builder, span, name Span, interned_name strpool.StringId, type NodeId, ) NodeId ! mem.AllocError { extra :: try add_extra(builder, name.start) _ = try add_extra(builder, name.end) _ = try add_extra(builder, 0) return try add_node(builder, Node{ kind = .param_decl, main = NodeData{ extra = extra }, data0 = NodeData{ node_id = type }, data1 = NodeData{ name_id = interned_name }, }, span) } set_param_comptime proc(builder @mut Builder, id NodeId) void { node :: builder.storage.nodes.items[usize(id)] debug.assert(node.kind == .param_decl) builder.storage.extra.items[usize(node.main.extra) + 2] = 1 } add_function_decl proc( builder @mut Builder, span Span, name Span, interned_name strpool.StringId, return_type NodeId, params []NodeId, body NodeId, ) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len _ = try add_extra(builder, name.start) _ = try add_extra(builder, name.end) _ = try add_extra(builder, u32(return_type)) _ = try add_extra(builder, u32(interned_name)) for (params) |parameter| _ = try add_extra(builder, u32(parameter)) _ = try add_extra(builder, 0) return try add_node(builder, Node{ kind = .func_decl, main = NodeData{ extra = u32(start) }, data0 = NodeData{ node_id = body }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } set_function_external proc(builder @mut Builder, id NodeId) void { node :: builder.storage.nodes.items[usize(id)] debug.assert(node.kind == .func_decl) builder.storage.extra.items[usize(node.data1.extra) - 1] = 1 } function_external proc(tree @Tree, id NodeId) bool { node :: get_node(&tree.storage, id) if (node.kind != .func_decl) return false return tree.storage.extra.items[usize(node.data1.extra) - 1] != 0 } set_file_local proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] node.flags = node.flags | file_local_flag } file_local proc(tree @Tree, id NodeId) bool { return (tree.storage.nodes.items[usize(id)].flags & file_local_flag) != 0 } set_callable_foreign proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] node.flags = node.flags | foreign_type_flag } callable_foreign proc(tree @Tree, id NodeId) bool { return function_external(tree, id) or (tree.storage.nodes.items[usize(id)].flags & foreign_type_flag) != 0 } set_callable_variadic proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] node.flags = node.flags | variadic_flag } callable_variadic proc(tree @Tree, id NodeId) bool { return (tree.storage.nodes.items[usize(id)].flags & variadic_flag) != 0 } set_record_opaque proc(builder @mut Builder, id NodeId) void { node :: &builder.storage.nodes.items[usize(id)] node.flags = node.flags | opaque_flag } record_opaque proc(tree @Tree, id NodeId) bool { return (tree.storage.nodes.items[usize(id)].flags & opaque_flag) != 0 } function_generic proc(tree @Tree, id NodeId) bool { node :: get_node(&tree.storage, id) if (node.kind != .func_decl and node.kind != .expr_function_type) return false for 0..function_param_count(tree, id) |index| { if (param(tree, function_param(tree, id, index)).comptime) return true } return false } add_import_decl proc( builder @mut Builder, span, path Span, namespace ?strpool.StringId, test_only bool, ) NodeId ! mem.AllocError { extra :: try add_extra(builder, path.start) _ = try add_extra(builder, path.end) alias_id :: if (namespace) |id| id else strpool.no_str flag :: if (test_only) u32(1) else u32(0) return try add_node(builder, Node{ kind = .import_decl, main = NodeData{ extra = extra }, data0 = NodeData{ name_id = alias_id }, data1 = NodeData{ extra = flag }, }, span) } add_block proc( builder @mut Builder, span Span, statements []NodeId, ) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (statements) |statement| _ = try add_extra(builder, u32(statement)) return try add_node(builder, Node{ kind = .block, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_package proc(builder @mut Builder, span Span, declarations []NodeId) NodeId ! mem.AllocError { start :: builder.storage.extra.items.len for (declarations) |declaration| _ = try add_extra(builder, u32(declaration)) return try add_node(builder, Node{ kind = .package, data0 = NodeData{ extra = u32(start) }, data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }, }, span) } add_invalid_expr proc( builder @mut Builder, span Span, diagnostic DiagnosticId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .invalid_expr, main = NodeData{ diagnostic = diagnostic }, }, span) } add_invalid_stmt proc( builder @mut Builder, span Span, diagnostic DiagnosticId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .invalid_stmt, main = NodeData{ diagnostic = diagnostic }, }, span) } add_invalid_decl proc( builder @mut Builder, span Span, diagnostic DiagnosticId, ) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .invalid_decl, main = NodeData{ diagnostic = diagnostic }, }, span) } node_count proc(tree @Tree) usize { return tree.storage.nodes.items.len } #! returns the identifier's identity in the compilation-owned string pool. #! accepts identifiers, declarations, and parameters; source spans remain available in typed views. name_id proc(tree @Tree, id NodeId) strpool.StringId { node :: get_node(&tree.storage, id) return match node.kind { .expr_identifier: node.main.name_id .stmt_decl, .param_decl: node.data1.name_id .func_decl: strpool.StringId(tree.storage.extra.items[usize(node.main.extra) + 3]) else: unreachable } } package_decl_count proc(tree @Tree) usize { return package_decl_count_at(tree, tree.root) } package_decl proc(tree @Tree, index usize) NodeId { return package_decl_at(tree, tree.root, index) } package_decl_count_at proc(tree @Tree, package NodeId) usize { root :: get_node(&tree.storage, package) debug.assert(root.kind == .package) return usize(root.data1.extra - root.data0.extra) } package_decl_at proc(tree @Tree, package NodeId, index usize) NodeId { root :: get_node(&tree.storage, package) debug.assert(root.kind == .package) debug.assert(index < usize(root.data1.extra - root.data0.extra)) return NodeId(tree.storage.extra.items[usize(root.data0.extra) + index]) } builder_package_decl_count proc(builder @Builder, package NodeId) usize { tree :: view(builder, package) return package_decl_count(&tree) } builder_package_decl proc(builder @Builder, package NodeId, index usize) NodeId { tree :: view(builder, package) return package_decl(&tree, index) } decl_kind proc(tree @Tree, id NodeId) DeclKind { return match get_node(&tree.storage, id).kind { .stmt_decl: .statement .func_decl: .function .import_decl: .import .invalid_decl: .invalid else: unreachable } } stmt_kind proc(tree @Tree, id NodeId) StmtKind { return match get_node(&tree.storage, id).kind { .stmt_decl: .declaration .stmt_expr: .expression .stmt_return: .return_stmt .stmt_yield: .yield_stmt .stmt_break: .break_stmt .stmt_continue: .continue_stmt .stmt_unreachable: .unreachable_stmt .stmt_if: .if_stmt .stmt_assignment: .assignment_stmt .stmt_while: .while_stmt .stmt_for: .for_stmt .stmt_defer: .defer_stmt .stmt_errdefer: .errdefer_stmt .stmt_block: .block_stmt .invalid_stmt: .invalid else: unreachable } } statement_expr proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_expr) return node.main.node_id } cleanup_stmt proc(tree @Tree, id NodeId) CleanupView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_defer or node.kind == .stmt_errdefer) return CleanupView{ span = get_span(&tree.storage, id), body = node.main.node_id, capture = maybe_node_id(u32(node.data0.node_id)), } } statement_block proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_block) return node.main.node_id } block_expr proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_block) return node.main.node_id } return_stmt proc(tree @Tree, id NodeId) ReturnView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_return) return ReturnView{ span = get_span(&tree.storage, id), value = maybe_node_id(u32(node.main.node_id)), } } yield_stmt proc(tree @Tree, id NodeId) ReturnView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_yield) return ReturnView{ span = get_span(&tree.storage, id), value = node.main.node_id, } } if_stmt proc(tree @Tree, id NodeId) IfView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_if) return IfView{ span = get_span(&tree.storage, id), condition = NodeId(tree.storage.extra.items[usize(node.main.extra)]), then_block = node.data0.node_id, else_block = maybe_node_id(u32(node.data1.node_id)), capture = maybe_node_id(tree.storage.extra.items[usize(node.main.extra) + 1]), } } assignment_stmt proc(tree @Tree, id NodeId) AssignmentView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_assignment) return AssignmentView{ span = get_span(&tree.storage, id), target = node.main.node_id, value = node.data0.node_id, compound = (node.flags & compound_assignment_flag) != 0, } } while_stmt proc(tree @Tree, id NodeId) WhileView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_while) return WhileView{ span = get_span(&tree.storage, id), condition = node.main.node_id, body = node.data0.node_id, capture = maybe_node_id(u32(node.data1.node_id)), } } for_stmt proc(tree @Tree, id NodeId) ForView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_for) extra :: usize(node.data1.extra) return ForView{ span = get_span(&tree.storage, id), iterable = node.main.node_id, capture = maybe_node_id(tree.storage.extra.items[extra]), index = maybe_node_id(tree.storage.extra.items[extra + 1]), pointer_capture = tree.storage.extra.items[extra + 2] != 0, index_capture = (tree.storage.extra.items[extra + 3] & 1) != 0, body = node.data0.node_id, is_inline = (tree.storage.extra.items[extra + 3] & 2) != 0, } } statement_decl proc(tree @Tree, id NodeId) StatementDeclView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .stmt_decl) extra :: usize(node.main.extra) raw_type :: tree.storage.extra.items[extra + 2] return StatementDeclView{ span = get_span(&tree.storage, id), name = span_from_extra(&tree.storage, extra, get_span(&tree.storage, id).source), type = maybe_node_id(raw_type), initializer = NodeId(tree.storage.extra.items[extra + 3]), mutability = node.data0.mutability, } } function_decl proc(tree @Tree, id NodeId) FunctionDeclView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .func_decl) extra :: usize(node.main.extra) return FunctionDeclView{ span = get_span(&tree.storage, id), name = span_from_extra(&tree.storage, extra, get_span(&tree.storage, id).source), return_type = NodeId(tree.storage.extra.items[extra + 2]), body = node.data0.node_id, } } callable_result proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) if (node.kind == .func_decl) return function_decl(tree, id).return_type debug.assert(node.kind == .expr_function_type) return node.main.node_id } import_decl proc(tree @Tree, id NodeId) ImportView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .import_decl) span :: get_span(&tree.storage, id) namespace ?strpool.StringId :: if (node.data0.name_id == strpool.no_str) null else node.data0.name_id return ImportView{ span = span, path = span_from_extra(&tree.storage, usize(node.main.extra), span.source), namespace = namespace, test_only = node.data1.extra != 0, } } function_param_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .func_decl or node.kind == .expr_function_type) start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra suffix :: if (node.kind == .func_decl) u32(1) else u32(0) return usize(node.data1.extra - start - suffix) } function_param proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .func_decl or node.kind == .expr_function_type) start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra debug.assert(index < function_param_count(tree, id)) return NodeId(tree.storage.extra.items[usize(start) + index]) } param proc(tree @Tree, id NodeId) ParamView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .param_decl) return ParamView{ span = get_span(&tree.storage, id), name = span_from_extra(&tree.storage, usize(node.main.extra), get_span(&tree.storage, id).source), type = node.data0.node_id, comptime = tree.storage.extra.items[usize(node.main.extra) + 2] != 0, } } block_statement_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .block) return usize(node.data1.extra - node.data0.extra) } block_statement proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .block) debug.assert(index < usize(node.data1.extra - node.data0.extra)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) } expr_kind proc(tree @Tree, id NodeId) ExprKind { return expression_kind(get_node(&tree.storage, id).kind) } builder_expr_kind proc(builder @Builder, id NodeId) ExprKind { return expression_kind(get_node(&builder.storage, id).kind) } @hide:file expression_kind proc(kind NodeKind) ExprKind { return match kind { .literal_int: .literal_int .literal_float: .literal_float .literal_string: .literal_string .literal_bool: .literal_bool .expr_identifier: .identifier .expr_unary: .unary .expr_binary: .binary .expr_call: .call .expr_range: .range .expr_array_type: .array_type .expr_pointer_type: .pointer_type .expr_function_type: .function_type .expr_array_literal: .array_literal .expr_record_type: .record_type .expr_record_literal: .record_literal .expr_enum_type: .enum_type .expr_union_type: .union_type .expr_distinct_type: .distinct_type .expr_alias: .alias_expr .expr_union_tag_type: .union_tag_type .expr_enum_literal: .enum_literal .expr_match: .match_expr .expr_index: .index .expr_selection: .selection .invalid_expr: .invalid .expr_slice_type: .slice_type .expr_optional_type: .optional_type .expr_slice: .slice .expr_builtin_call: .builtin_call .literal_null: .literal_null .expr_fallible_type: .fallible_type .expr_sum_type: .sum_type .expr_try: .try_expr .expr_catch: .catch_expr .expr_typed_constant: .typed_constant .expr_comptime: .comptime_expr .expr_block: .block_expr .literal_float_value: .literal_float .literal_void: .literal_void .literal_undefined: .literal_undefined else: unreachable } } unary_expr proc(tree @Tree, id NodeId) UnaryExprView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_unary) return UnaryExprView{ span = get_span(&tree.storage, id), operator = node.main.unary_operator, operand = node.data0.node_id, } } binary_expr proc(tree @Tree, id NodeId) BinaryExprView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_binary or node.kind == .expr_fallible_type or node.kind == .expr_sum_type) return BinaryExprView{ span = get_span(&tree.storage, id), operator = node.main.binary_operator, left = node.data0.node_id, right = node.data1.node_id, } } comptime_operand proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_comptime) return node.main.node_id } #! generated IEEE literals have no textual spelling; parsed float literals return null. float_value proc(tree @Tree, id NodeId) ?f64 { node :: get_node(&tree.storage, id) if (node.kind != .literal_float_value) return null bits :: u64(node.data0.extra) | (u64(node.data1.extra) << 32) return ptrcast!(@f64, &bits)^ } typed_constant proc(tree @Tree, id NodeId) TypedConstantView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_typed_constant) return TypedConstantView{ type = node.data0.node_id, value = node.data1.node_id, } } catch_expr proc(tree @Tree, id NodeId) CatchExprView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_catch) extra :: usize(node.main.extra) return CatchExprView{ span = get_span(&tree.storage, id), operand = node.data0.node_id, capture = maybe_node_id(tree.storage.extra.items[extra]), handler = node.data1.node_id, handler_is_block = tree.storage.extra.items[extra + 1] != 0, } } range_expr proc(tree @Tree, id NodeId) RangeView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_range) return RangeView{ span = get_span(&tree.storage, id), start = node.data0.node_id, end = node.data1.node_id, inclusive = node.main.extra != 0, } } call_expr proc(tree @Tree, id NodeId) CallExprView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_call) return CallExprView{ span = get_span(&tree.storage, id), callee = node.main.node_id, } } call_arg_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_call or node.kind == .expr_builtin_call) return usize(node.data1.extra - node.data0.extra) } call_arg proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_call or node.kind == .expr_builtin_call) debug.assert(index < usize(node.data1.extra - node.data0.extra)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) } array_type proc(tree @Tree, id NodeId) ArrayTypeView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_array_type) return ArrayTypeView{ span = get_span(&tree.storage, id), count = maybe_node_id(u32(node.data0.node_id)), element = node.data1.node_id, mutable = node.main.extra != 0, } } pointer_type proc(tree @Tree, id NodeId) PointerTypeView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_pointer_type) return PointerTypeView{ element = node.data1.node_id, mutable = node.main.extra != 0, many = node.data0.extra != 0, } } slice_type proc(tree @Tree, id NodeId) SliceTypeView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_slice_type) return SliceTypeView{ element = node.data1.node_id, mutable = node.main.extra != 0, sentinel = maybe_node_id(u32(node.data0.node_id)), } } slice_expr proc(tree @Tree, id NodeId) SliceView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_slice) extra :: usize(node.data0.extra) return SliceView{ base = node.main.node_id, start = maybe_node_id(tree.storage.extra.items[extra]), end = maybe_node_id(tree.storage.extra.items[extra + 1]), sentinel = maybe_node_id(tree.storage.extra.items[extra + 2]), } } builtin_call proc(tree @Tree, id NodeId) BuiltinCallView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_builtin_call) return BuiltinCallView{ name = node.main.node_id } } array_element_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_array_literal) return usize(node.data1.extra - node.data0.extra) } array_element proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_array_literal) debug.assert(index < usize(node.data1.extra - node.data0.extra)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) } record_literal proc(tree @Tree, id NodeId) RecordLiteralView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_record_literal) return RecordLiteralView{ span = get_span(&tree.storage, id), type = maybe_node_id(u32(node.main.node_id)), } } field proc(tree @Tree, id NodeId) FieldView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .record_field) return FieldView{ span = get_span(&tree.storage, id), name = maybe_node_id(u32(node.main.node_id)), type = maybe_node_id(u32(node.data0.node_id)), value = maybe_node_id(u32(node.data1.node_id)), } } enum_type proc(tree @Tree, id NodeId) EnumTypeView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_enum_type) return EnumTypeView{ backing = maybe_node_id(u32(node.main.node_id)) } } enum_member_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_enum_type) return usize(node.data1.extra - node.data0.extra) } enum_member proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(index < enum_member_count(tree, id)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) } union_type proc(tree @Tree, id NodeId) UnionTypeView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_union_type) extra :: usize(node.main.extra) return UnionTypeView{ tag = maybe_node_id(tree.storage.extra.items[extra]), auto_tag = tree.storage.extra.items[extra + 1] != 0, } } type_operand proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type or node.kind == .expr_try or node.kind == .expr_union_tag_type) return node.main.node_id } enum_literal proc(tree @Tree, id NodeId) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_enum_literal) return node.main.node_id } match_expr proc(tree @Tree, id NodeId) MatchView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_match) return MatchView{ subject = node.main.node_id, optional_capture = (node.flags & optional_match_flag) != 0, } } match_arm_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_match) return usize(node.data1.extra - node.data0.extra) } match_arm proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(index < match_arm_count(tree, id)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) } match_arm_view proc(tree @Tree, id NodeId) MatchArmView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .match_arm) extra :: usize(node.data0.extra) return MatchArmView{ capture = maybe_node_id(tree.storage.extra.items[extra]), body = node.main.node_id, expression = (tree.storage.extra.items[extra + 1] & 1) != 0, catch_all = tree.storage.extra.items[extra + 2] != 0, is_inline = (tree.storage.extra.items[extra + 1] & 2) != 0, tag_capture = maybe_node_id(tree.storage.extra.items[extra + 3]), tag_capture_present = (tree.storage.extra.items[extra + 1] & 4) != 0, } } match_pattern_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .match_arm) return usize(node.data1.extra - node.data0.extra - 4) } match_pattern proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(index < match_pattern_count(tree, id)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + 4 + index]) } record_field_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal or node.kind == .expr_union_type) return usize(node.data1.extra - node.data0.extra) } record_field proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal or node.kind == .expr_union_type) debug.assert(index < usize(node.data1.extra - node.data0.extra)) return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index]) } index_expr proc(tree @Tree, id NodeId) IndexView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_index) return IndexView{ span = get_span(&tree.storage, id), base = node.data0.node_id, index = node.data1.node_id, } } selection_expr proc(tree @Tree, id NodeId) SelectionView { node :: get_node(&tree.storage, id) debug.assert(node.kind == .expr_selection) return SelectionView{ span = get_span(&tree.storage, id), base = node.data0.node_id, member = node.data1.node_id, } } node_span proc(tree @Tree, id NodeId) Span { return get_span(&tree.storage, id) } invalid proc(tree @Tree, id NodeId) InvalidView { node :: get_node(&tree.storage, id) debug.assert( node.kind == .invalid_expr or node.kind == .invalid_stmt or node.kind == .invalid_decl ) return InvalidView{ span = get_span(&tree.storage, id), diagnostic = node.main.diagnostic, } } #! returns text borrowed from the source buffer; the caller must not free it. source_text proc(tree @Tree, span Span) []u8 { debug.assert(usize(span.source) < tree.storage.sources.items.len) text :: tree.storage.sources.items[usize(span.source)].text debug.assert(span.start <= span.end and usize(span.end) <= text.len) return text[usize(span.start)..usize(span.end)] } builder_span proc(builder @Builder, id NodeId) Span { return get_span(&builder.storage, id) } @hide:file span_from_extra proc(storage @Storage, start usize, source diagnostics.SourceId) Span { return Span{ start = storage.extra.items[start], end = storage.extra.items[start + 1], source = source, } } @hide:file add_node proc(builder @mut Builder, node Node, span Span) NodeId ! mem.AllocError { debug.assert(builder.storage.nodes.items.len == builder.storage.spans.items.len) if (builder.storage.sources.items.len == 0) _ = try set_source(builder, "", builder.source) id :: node_id(builder.storage.nodes.items.len) try arraylist.reserve(&builder.storage.nodes, builder.storage.nodes.items.len + 1) try arraylist.reserve(&builder.storage.spans, builder.storage.spans.items.len + 1) try arraylist.append(&builder.storage.nodes, node) try arraylist.append(&builder.storage.spans, span) return id } @hide:file add_extra proc(builder @mut Builder, value u32) u32 ! mem.AllocError { debug.assert(u64(builder.storage.extra.items.len) < u64(maxval!(u32))) id :: u32(builder.storage.extra.items.len) try arraylist.append(&builder.storage.extra, value) return id } @hide:file get_node proc(storage @Storage, id NodeId) Node { debug.assert(usize(id) < storage.nodes.items.len) return storage.nodes.items[usize(id)] } @hide:file get_span proc(storage @Storage, id NodeId) Span { debug.assert(usize(id) < storage.spans.items.len) return storage.spans.items[usize(id)] } @hide:file node_id proc(index usize) NodeId { debug.assert(u64(index) < u64(no_node)) return NodeId(index) } @hide:file maybe_node_id proc(value u32) ?NodeId { if (value == u32(no_node)) return null return NodeId(value) } NodeClass :: enum { expression, statement, function, parameter, block, field, match_arm, package, import, invalid } @hide:file NodeLayout :: struct { class NodeClass = .expression direct_children u32 = 0 extra_pointers u32 = 0 extra_start usize = 0 extra_count usize = 0 extra_children u32 = 0 list_start usize = 0 list_end usize = 0 } #! describes owned extra storage and child slots, excluding names, spans, and flags. @hide:file node_layout proc(node Node) NodeLayout { layout := NodeLayout{} match node.kind { .literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null, .literal_void, .literal_undefined, .expr_identifier, .invalid_expr: {} .expr_unary: layout.direct_children = 2 .expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant, .expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type: layout.direct_children = 6 .expr_pointer_type: layout.direct_children = 4 .expr_optional_type, .expr_distinct_type, .expr_alias, .expr_union_tag_type, .expr_enum_literal, .expr_try, .expr_comptime, .expr_block: layout.direct_children = 1 .expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal, .expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match, .block, .package: { layout.extra_pointers = 6 layout.extra_start = usize(node.data0.extra) layout.extra_count = usize(node.data1.extra - node.data0.extra) layout.list_end = layout.extra_count if (node.kind == .block) layout.class = .block else if (node.kind == .package) layout.class = .package else if (node.kind != .expr_array_literal and node.kind != .expr_record_type) layout.direct_children = 1 } .expr_union_type: { layout.extra_pointers = 7 layout.extra_start = usize(node.main.extra) layout.extra_count = usize(node.data1.extra - node.main.extra) layout.extra_children = 1 layout.list_start = 2 layout.list_end = layout.extra_count } .record_field: { layout.class = .field layout.direct_children = 7 } .match_arm: { layout.class = .match_arm layout.direct_children = 1 layout.extra_pointers = 6 layout.extra_start = usize(node.data0.extra) layout.extra_count = usize(node.data1.extra - node.data0.extra) layout.extra_children = 9 layout.list_start = 4 layout.list_end = layout.extra_count } .expr_catch: { layout.direct_children = 6 layout.extra_pointers = 1 layout.extra_start = usize(node.main.extra) layout.extra_count = 2 layout.extra_children = 1 } .expr_slice: { layout.direct_children = 1 layout.extra_pointers = 2 layout.extra_start = usize(node.data0.extra) layout.extra_count = 3 layout.extra_children = 7 } .stmt_expr, .stmt_return, .stmt_yield, .stmt_block, .stmt_defer, .stmt_errdefer: { layout.class = .statement layout.direct_children = 1 if (node.kind == .stmt_defer or node.kind == .stmt_errdefer) layout.direct_children = 3 } .stmt_break, .stmt_continue, .stmt_unreachable, .invalid_stmt: layout.class = .statement .stmt_assignment: { layout.class = .statement layout.direct_children = 3 } .stmt_while: { layout.class = .statement layout.direct_children = 7 } .stmt_if: { layout.class = .statement layout.direct_children = 6 layout.extra_pointers = 1 layout.extra_start = usize(node.main.extra) layout.extra_count = 2 layout.extra_children = 3 } .stmt_for: { layout.class = .statement layout.direct_children = 3 layout.extra_pointers = 4 layout.extra_start = usize(node.data1.extra) layout.extra_count = 4 layout.extra_children = 3 } .stmt_decl: { layout.class = .statement layout.extra_pointers = 1 layout.extra_start = usize(node.main.extra) layout.extra_count = 4 layout.extra_children = 12 } .func_decl: { layout.class = .function layout.direct_children = 2 layout.extra_pointers = 5 layout.extra_start = usize(node.main.extra) layout.extra_count = usize(node.data1.extra - node.main.extra) layout.extra_children = 4 layout.list_start = 4 layout.list_end = layout.extra_count - 1 } .param_decl: { layout.class = .parameter layout.direct_children = 2 layout.extra_pointers = 1 layout.extra_start = usize(node.main.extra) layout.extra_count = 3 } .import_decl: { layout.class = .import layout.extra_pointers = 1 layout.extra_start = usize(node.main.extra) layout.extra_count = 2 } .invalid_decl: layout.class = .invalid } return layout } @hide:file has_slot proc(mask u32, index usize) bool { return (mask & (u32(1) << u32(index))) != 0 } @hide:file node_word proc(node Node, index usize) NodeData { return match index { 0: node.main 1: node.data0 2: node.data1 else: unreachable } } @hide:file set_node_word proc(node @mut Node, index usize, word NodeData) void { match index { 0: node.main = word 1: node.data0 = word 2: node.data1 = word else: unreachable } } node_class proc(tree @Tree, id NodeId) NodeClass { return node_layout(get_node(&tree.storage, id)).class } #! counts actual AST children, excluding absent optional nodes and non-node metadata. child_count proc(tree @Tree, id NodeId) usize { node :: get_node(&tree.storage, id) layout :: node_layout(node) count := layout.list_end - layout.list_start for (0..usize(3)) |i| { if (has_slot(layout.direct_children, i) and node_word(node, i).node_id != no_node) count += 1 } for (0..usize(4)) |i| { if (!has_slot(layout.extra_children, i)) continue if (NodeId(tree.storage.extra.items[layout.extra_start + i]) != no_node) count += 1 } return count } #! returns a child in stable payload order; `index` must be less than child_count. child proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) layout :: node_layout(node) remaining := index for (0..usize(3)) |i| { if (!has_slot(layout.direct_children, i)) continue value :: node_word(node, i).node_id if (value == no_node) continue if (remaining == 0) return value remaining -= 1 } for (0..usize(4)) |i| { if (!has_slot(layout.extra_children, i)) continue value :: NodeId(tree.storage.extra.items[layout.extra_start + i]) if (value == no_node) continue if (remaining == 0) return value remaining -= 1 } debug.assert(remaining < layout.list_end - layout.list_start) return NodeId(tree.storage.extra.items[layout.extra_start + layout.list_start + remaining]) } #! clones owned payload storage and preserves spans and interned names. #! `mapping` covers original nodes, initially maxval!(NodeId), with substitutions prefilled. #! nodes outside `mapping` are already concrete and returned unchanged. clone_subtree proc( builder @mut Builder, root NodeId, mapping []mut NodeId, ) NodeId ! mem.AllocError { if (root == no_node or usize(root) >= mapping.len) return root if (mapping[usize(root)] != no_node) return mapping[usize(root)] original :: get_node(&builder.storage, root) span :: get_span(&builder.storage, root) layout :: node_layout(original) cloned := original start :: builder.storage.extra.items.len for (0..layout.extra_count) |i| { value :: builder.storage.extra.items[layout.extra_start + i] _ = try add_extra(builder, value) } for (0..usize(3)) |i| { if (has_slot(layout.extra_pointers, i)) { offset :: usize(node_word(original, i).extra) - layout.extra_start set_node_word(&cloned, i, NodeData{ extra = u32(start + offset) }) } if (has_slot(layout.direct_children, i)) { value :: try clone_subtree(builder, node_word(original, i).node_id, mapping) set_node_word(&cloned, i, NodeData{ node_id = value }) } } for (0..layout.extra_count) |i| { is_child :: ( (i < 4 and has_slot(layout.extra_children, i)) or (i >= layout.list_start and i < layout.list_end) ) if (!is_child) continue original_child :: NodeId(builder.storage.extra.items[layout.extra_start + i]) value :: try clone_subtree(builder, original_child, mapping) builder.storage.extra.items[start + i] = u32(value) } result :: try add_node(builder, cloned, span) mapping[usize(root)] = result return result } #! shallow-copies the source kind and payload, retaining the target's diagnostic span. replace_node proc(builder @mut Builder, target, source NodeId) void { builder.storage.nodes.items[usize(target)] = get_node(&builder.storage, source) } #! selects a source node, retaining its literal spelling and diagnostic provenance. replace_node_from_source proc(builder @mut Builder, target, source NodeId) void { replace_node(builder, target, source) builder.storage.spans.items[usize(target)] = get_span(&builder.storage, source) } #! shallow-copies kind, payload, and span; extra storage remains shared. copy_node proc(builder @mut Builder, id NodeId) NodeId ! mem.AllocError { node :: get_node(&builder.storage, id) span :: get_span(&builder.storage, id) return try add_node(builder, node, span) } #! replaces package children without changing its node identity or source span. set_package_declarations proc( builder @mut Builder, root NodeId, declarations []NodeId, ) void ! mem.AllocError { debug.assert(get_node(&builder.storage, root).kind == .package) start :: builder.storage.extra.items.len for (declarations) |declaration| _ = try add_extra(builder, u32(declaration)) builder.storage.nodes.items[usize(root)].data0 = NodeData{ extra = u32(start) } builder.storage.nodes.items[usize(root)].data1 = NodeData{ extra = u32(builder.storage.extra.items.len) } } #! retains generated decimal text until deinit_builder or ownership transfer through finish. #! generated literals retain `at` for diagnostics; read their spelling through literal_text. add_integer proc(builder @mut Builder, at Span, magnitude u64) NodeId ! mem.AllocError { digits [20]mut u8 := undefined start := digits.len remaining := magnitude while true { start -= 1 digits[start] = '0' + u8(rem!(remaining, 10)) remaining = divtrunc!(remaining, 10) if (remaining == 0) break } return try add_generated_literal(builder, at, .int, digits[start..]) } #! retains generated boolean text with the same ownership and provenance rules as add_integer. add_boolean proc(builder @mut Builder, at Span, value bool) NodeId ! mem.AllocError { text :: if (value) "true" else "false" return try add_generated_literal(builder, at, .bool, text) } #! copies arbitrary bytes into owned quoted spelling, retaining `at` for diagnostics. add_string proc(builder @mut Builder, at Span, bytes []u8) NodeId ! mem.AllocError { if (bytes.len > divtrunc!(maxval!(usize) - 2, usize(4))) return .out_of_memory allocator :: builder.storage.generated.allocator text :: try mem.alloc(u8, allocator, bytes.len * 4 + 2) errdefer mem.free(allocator, text) text[0] = '"' digits :: "0123456789abcdef" for (bytes) |byte, index| { start :: index * 4 + 1 text[start] = '\\' text[start + 1] = 'x' text[start + 2] = digits[usize(divtrunc!(byte, u8(16)))] text[start + 3] = digits[usize(rem!(byte, u8(16)))] } text[text.len - 1] = '"' return try add_owned_literal(builder, at, .string, text) } @hide:file add_generated_literal proc( builder @mut Builder, at Span, kind LiteralKind, bytes []u8, ) NodeId ! mem.AllocError { debug.assert(usize(at.source) < builder.storage.sources.items.len) allocator :: builder.storage.generated.allocator text :: try mem.alloc(u8, allocator, bytes.len) errdefer mem.free(allocator, text) memcopy!(text, bytes) return try add_owned_literal(builder, at, kind, text) } @hide:file add_owned_literal proc( builder @mut Builder, at Span, kind LiteralKind, text []mut u8, ) NodeId ! mem.AllocError { debug.assert(usize(at.source) < builder.storage.sources.items.len) index :: builder.storage.generated.items.len debug.assert(u64(index) < u64(maxval!(u32))) try arraylist.append(&builder.storage.generated, text) errdefer _ = arraylist.pop(&builder.storage.generated) id :: try add_literal(builder, kind, at) builder.storage.nodes.items[usize(id)].main = NodeData{ extra = u32(index) } builder.storage.nodes.items[usize(id)].data0 = NodeData{ extra = 1 } return id } #! borrows literal or selector spelling, independently of its diagnostic source span. #! query float_value first for generated IEEE literals. literal_text proc(tree @Tree, id NodeId) []u8 { node :: get_node(&tree.storage, id) if (node.kind == .expr_identifier and node.data0.extra == 1) return tree.storage.generated.items[usize(node.data1.extra)] if (node.data0.extra == 1) return tree.storage.generated.items[usize(node.main.extra)] return source_text(tree, get_span(&tree.storage, id)) } StringError :: enum { invalid_escape } #! decodes one byte from quoted literal text without allocation. #! start `cursor` at one and call only while it is less than `text.len - 1`. string_next proc(text []u8, cursor @mut usize) u8 ! StringError { debug.assert(text.len >= 2 and cursor^ >= 1 and cursor^ < text.len - 1) end :: text.len - 1 byte := text[cursor^] cursor^ += 1 if (byte != '\\') return byte if (cursor^ >= end) return .invalid_escape escape :: text[cursor^] cursor^ += 1 match escape { '\\': byte = '\\' '"': byte = '"' 'n': byte = '\n' 'r': byte = '\r' 't': byte = '\t' '0': byte = 0 'x': { if (end - cursor^ < 2) return .invalid_escape high := u8(0) low := u8(0) if (hex_digit(text[cursor^])) |digit| high = digit else return .invalid_escape if (hex_digit(text[cursor^ + 1])) |digit| low = digit else return .invalid_escape byte = high * 16 + low cursor^ += 2 } else: return .invalid_escape } return byte } @hide:file hex_digit proc(byte u8) ?u8 { if (byte >= '0' and byte <= '9') return byte - '0' if (byte >= 'a' and byte <= 'f') return byte - 'a' + 10 if (byte >= 'A' and byte <= 'F') return byte - 'A' + 10 return null }