#! grammar: #! * type ..... -> identifier {'.' member} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type #! * binding ... -> :: | := #! * literal .. -> int | float | string | 'true' | 'false' #! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or' #! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' #! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string #! * 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 | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm #! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if. #! precedence (tightest first): calls/indices/selections, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses. #! 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) LiteralKind :: enum { int, float, string, bool } ExprKind :: enum { literal_int literal_float literal_string literal_bool identifier unary binary call range array_type array_literal record_type record_literal enum_type union_type distinct_type alias_expr enum_literal match_expr index selection invalid } DeclKind :: enum { statement function import invalid } StmtKind :: enum { declaration expression assignment_stmt return_stmt break_stmt continue_stmt if_stmt while_stmt for_stmt invalid } Mutability :: enum { constant, variable } UnaryOperator :: enum { negate, logical_not } BinaryOperator :: enum { add, subtract multiply, divide 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_array_literal expr_record_type expr_record_literal record_field expr_enum_type expr_union_type expr_distinct_type expr_alias 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 } @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 Node :: struct { kind NodeKind 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) } #! owns node storage and source descriptors; 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; registered source paths and 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 } UnaryExprView :: struct { span Span operator UnaryOperator operand NodeId } BinaryExprView :: struct { span Span operator BinaryOperator left NodeId right NodeId } CallExprView :: struct { span Span callee NodeId } ArrayTypeView :: struct { span Span count ?NodeId element NodeId mutable bool } 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 } MatchArmView :: struct { capture ?NodeId body NodeId expression bool catch_all bool } 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 } AssignmentView :: struct { span Span target NodeId value NodeId } WhileView :: struct { span Span condition NodeId body NodeId } 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 } 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), }, 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 and source-descriptor storage, without freeing source paths or text. deinit_builder proc(builder @mut Builder) void { 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), } builder.source = &[] builder.source_id = diagnostics.SourceId(0) return tree } #! releases owned AST and source-descriptor storage, without freeing source paths or text. deinit proc(tree @mut Tree) void { 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 } 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) } 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) } 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_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_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) } 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_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) } 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) 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) } 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_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_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_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 return try add_node(builder, Node{ kind = .stmt_if, main = NodeData{ node_id = condition }, 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) } 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) } 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) } 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) 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) } 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)) 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) } 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_break: .break_stmt .stmt_continue: .continue_stmt .stmt_if: .if_stmt .stmt_assignment: .assignment_stmt .stmt_while: .while_stmt .stmt_for: .for_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 } 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)), } } 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 = node.main.node_id, then_block = node.data0.node_id, else_block = maybe_node_id(u32(node.data1.node_id)), } } 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, } } 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, } } 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] != 0, body = node.data0.node_id, } } 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, } } 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) return usize(node.data1.extra - node.main.extra - 4) } function_param proc(tree @Tree, id NodeId, index usize) NodeId { node :: get_node(&tree.storage, id) debug.assert(node.kind == .func_decl) debug.assert(index < usize(node.data1.extra - node.main.extra - 4)) return NodeId(tree.storage.extra.items[usize(node.main.extra) + 4 + 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, } } 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_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_enum_literal: .enum_literal .expr_match: .match_expr .expr_index: .index .expr_selection: .selection .invalid_expr: .invalid 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) return BinaryExprView{ span = get_span(&tree.storage, id), operator = node.main.binary_operator, left = node.data0.node_id, right = node.data1.node_id, } } 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) 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) 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, } } 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) 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 } } 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] != 0, catch_all = tree.storage.extra.items[extra + 2] != 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 - 3) } 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) + 3 + 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) }