Files
honey/source/ast/ast.hon
T
hl-valdemar cc39c96fc0 feat: materialize address-free compile-time untagged unions
Snapshot full union storage rather than choosing an overlapping field. Preserve structural union source syntax independently of nominal identity so direct literal arguments materialize and share normalized specialization keys. Reject uninitialized bytes and address-bearing member layouts.
2026-10-30 16:35:11 +01:00

2412 lines
79 KiB
Plaintext

#! 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
union_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_union_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)
}
#! retains an untagged union's complete native-layout bytes as a string literal.
add_union_constant proc(
builder @mut Builder, at Span, type, bytes NodeId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_union_constant,
data0 = NodeData{ node_id = type },
data1 = NodeData{ node_id = bytes },
}, 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_union_constant: .union_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,
}
}
union_constant proc(tree @Tree, id NodeId) TypedConstantView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_union_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_union_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
}