#! grammar: #! * type ..... -> identifier #! * assignment -> :: | := #! * literal .. -> int | float | string | 'true' | 'false' #! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' #! * expr ..... -> identifier | literal | '(' expr ')' | '-' expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' #! * decl ..... -> identifier [type] assignment expr #! * block .... -> '{' {stmt} '}' #! * arm ...... -> block | stmt (single statements become canonical blocks) #! * stmt ..... -> decl | expr | 'return' [expr] | 'if' expr arm ['else' arm] #! braceless guards require parentheses unless the condition is a call; else binds to the nearest if. import "@std/debug" import "@std/mem" import "@std/arraylist" import "@source/diagnostics" 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 invalid } DeclKind :: enum { statement, function, invalid } StmtKind :: enum { declaration, expression, return_stmt, if_stmt, invalid } Mutability :: enum { constant, variable } UnaryOperator :: enum { negate } BinaryOperator :: enum { add, subtract, multiply, divide equal, not_equal, less, less_equal, greater, greater_equal } @hide:file NodeKind :: enum { literal_int, literal_float, literal_string, literal_bool, expr_identifier, expr_unary, expr_binary, expr_call, stmt_expr, stmt_decl, stmt_return, stmt_if, func_decl, param_decl, block, package, invalid_expr, invalid_stmt, invalid_decl, } @hide:file NodeData :: union { node_id NodeId extra u32 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) } #! owns node storage, retains its allocator, and borrows source until finish or deinit_builder. Builder :: struct { storage Storage source []u8 } #! owns node storage and borrows source; source must remain valid until deinit. Tree :: struct { storage Storage source []u8 root NodeId } 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 } ReturnView :: struct { span Span value ?NodeId } IfView :: struct { span Span condition NodeId then_block NodeId else_block ?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), }, source = source, } } #! releases builder-owned node storage with its retained allocator, without freeing source. deinit_builder proc(builder @mut Builder) void { arraylist.deinit(&builder.storage.nodes) arraylist.deinit(&builder.storage.extra) arraylist.deinit(&builder.storage.spans) } #! transfers node storage and the source borrow to a caller-owned Tree; release with deinit. finish proc(builder @mut Builder, root NodeId) Tree { tree :: Tree{ storage = builder.storage, source = builder.source, root = root } allocator :: builder.storage.nodes.allocator builder.storage = Storage{ nodes = arraylist.init(allocator), extra = arraylist.init(allocator), spans = arraylist.init(allocator), } builder.source = &[] return tree } #! releases owned node storage with its retained allocator, without freeing source. deinit proc(tree @mut Tree) void { arraylist.deinit(&tree.storage.nodes) arraylist.deinit(&tree.storage.extra) arraylist.deinit(&tree.storage.spans) } 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) NodeId ! mem.AllocError { return try add_node(builder, Node{ kind = .expr_identifier }, 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_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_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_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_statement_decl proc( builder @mut Builder, span Span, name Span, 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 }, }, span) } add_param proc(builder @mut Builder, span, name Span, 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 }, }, span) } add_function_decl proc( builder @mut Builder, span Span, name Span, 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)) 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_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 } package_decl_count proc(tree @Tree) usize { root :: get_node(&tree.storage, tree.root) debug.assert(root.kind == .package) return usize(root.data1.extra - root.data0.extra) } package_decl proc(tree @Tree, index usize) NodeId { root :: get_node(&tree.storage, tree.root) 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]) } decl_kind proc(tree @Tree, id NodeId) DeclKind { return match get_node(&tree.storage, id).kind { .stmt_decl: .statement .func_decl: .function .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_if: .if_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)), } } 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), 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), return_type = NodeId(tree.storage.extra.items[extra + 2]), body = node.data0.node_id, } } 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 - 3) } 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 - 3)) return NodeId(tree.storage.extra.items[usize(node.main.extra) + 3 + 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)), 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 .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, } } 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]) } 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(span.start <= span.end and usize(span.end) <= tree.source.len) return tree.source[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) Span { return Span{ start = storage.extra.items[start], end = storage.extra.items[start + 1], } } @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) 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) }