Files
honey/source/ast/ast.hon
T

2267 lines
74 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 ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '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.
#! * 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 '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr ['|' identifier '|'] arm ['else' arm] | 'while' expr ['|' identifier '|'] arm | ['inline'] 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
#! braceless guards/iterables require parentheses unless they are calls or optional captures; else binds to the nearest if.
#! precedence (tightest first): calls/indices/selections/dereference/unwrap, unary -/!/&/try higher than multiplication/division, addition/subtraction, 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.
#! binary operators associate left; unary operators associate right.
import "@std/debug"
import "@std/mem"
import "@std/arraylist"
import "@source/diagnostics"
import "@source/strpool"
Span :: alias diagnostics.Span
DiagnosticId :: alias diagnostics.DiagnosticId
no_diagnostic :: alias diagnostics.no_diagnostic
NodeId :: distinct u32
@hide:file
no_node :: maxval!(NodeId)
#! void literals are generated constants only; there is no source void literal.
LiteralKind :: enum {
int
float
string
bool
null
void
undefined
}
ExprKind :: enum {
literal_int
literal_float
literal_string
literal_bool
identifier
unary
binary
call
range
array_type
pointer_type
function_type
array_literal
record_type
record_literal
enum_type
union_type
distinct_type
alias_expr
union_tag_type
enum_literal
match_expr
index
selection
invalid
slice_type
optional_type
slice
builtin_call
literal_null
fallible_type
sum_type
try_expr
catch_expr
typed_constant
comptime_expr
literal_void
literal_undefined
}
DeclKind :: enum {
statement
function
import
invalid
}
StmtKind :: enum {
declaration
expression
assignment_stmt
return_stmt
break_stmt
continue_stmt
if_stmt
while_stmt
for_stmt
defer_stmt
errdefer_stmt
block_stmt
invalid
}
Mutability :: enum {
constant, variable
}
UnaryOperator :: enum {
negate
logical_not
address_of
dereference
unwrap
}
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_pointer_type
expr_function_type
expr_array_literal
expr_record_type
expr_record_literal
record_field
expr_enum_type
expr_union_type
expr_distinct_type
expr_alias
expr_union_tag_type
expr_enum_literal
expr_match
match_arm
expr_index
expr_selection
stmt_expr
stmt_decl
stmt_return
stmt_break
stmt_continue
stmt_if
stmt_assignment
stmt_while
stmt_for
func_decl
param_decl
import_decl
block
package
invalid_expr
invalid_stmt
invalid_decl
expr_slice_type
expr_optional_type
expr_slice
expr_builtin_call
literal_null
expr_fallible_type
expr_sum_type
expr_try
expr_catch
stmt_defer
stmt_errdefer
stmt_block
expr_typed_constant
expr_comptime
literal_float_value
literal_void
literal_undefined
}
@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)
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 }
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
}
WhileView :: struct {
span Span
condition NodeId
body NodeId
capture ?NodeId = null
}
RangeView :: struct {
span Span
start NodeId
end NodeId
inclusive bool
}
ForView :: struct {
span Span
iterable NodeId
capture ?NodeId
index ?NodeId
pointer_capture bool
index_capture bool
body NodeId
is_inline bool = false
}
InvalidView :: struct {
span Span
diagnostic DiagnosticId
}
#! retains `allocator` for node storage and borrows `source`; release with deinit_builder.
init_builder proc(allocator mem.Allocator, source []u8) Builder {
return Builder{
storage = Storage{
nodes = arraylist.init(allocator),
extra = arraylist.init(allocator),
spans = arraylist.init(allocator),
sources = arraylist.init(allocator),
generated = arraylist.init(allocator),
},
source = source,
}
}
#! registers borrowed `path` and `text`; both must remain valid until deinit_builder or deinit.
set_source proc(
builder @mut Builder, path, text []u8,
) diagnostics.SourceId ! mem.AllocError {
id :: diagnostics.SourceId(builder.storage.sources.items.len)
try arraylist.append(&builder.storage.sources,
diagnostics.Source{
path = path,
text = text,
})
builder.source = text
builder.source_id = id
return id
}
#! removes the latest source after its failed parse has rolled back all nodes.
remove_source proc(builder @mut Builder) void {
debug.assert(builder.storage.sources.items.len > 0)
_ = arraylist.pop(&builder.storage.sources)
if (builder.storage.sources.items.len == 0) {
builder.source = &[]
builder.source_id = diagnostics.SourceId(0)
return
}
builder.source_id = diagnostics.SourceId(builder.storage.sources.items.len - 1)
builder.source = builder.storage.sources.items[usize(builder.source_id)].text
}
#! borrows `builder` storage; refresh after builder mutation and never pass this view to deinit.
view proc(builder @Builder, root NodeId) Tree {
return Tree{ storage = builder.storage, root = root }
}
source_files proc(tree @Tree) []diagnostics.Source {
return tree.storage.sources.items
}
#! releases owned AST, generated text, and source descriptors, without freeing borrowed sources.
deinit_builder proc(builder @mut Builder) void {
for (builder.storage.generated.items) |text| mem.free(builder.storage.generated.allocator, text)
arraylist.deinit(&builder.storage.generated)
arraylist.deinit(&builder.storage.nodes)
arraylist.deinit(&builder.storage.extra)
arraylist.deinit(&builder.storage.spans)
arraylist.deinit(&builder.storage.sources)
}
#! transfers storage and source borrows to a caller-owned Tree; release with deinit.
finish proc(builder @mut Builder, root NodeId) Tree {
tree :: view(builder, root)
allocator :: builder.storage.nodes.allocator
builder.storage = Storage{
nodes = arraylist.init(allocator),
extra = arraylist.init(allocator),
spans = arraylist.init(allocator),
sources = arraylist.init(allocator),
generated = arraylist.init(allocator),
}
builder.source = &[]
builder.source_id = diagnostics.SourceId(0)
return tree
}
#! releases owned AST, generated text, and source descriptors, without freeing borrowed sources.
deinit proc(tree @mut Tree) void {
for (tree.storage.generated.items) |text| mem.free(tree.storage.generated.allocator, text)
arraylist.deinit(&tree.storage.generated)
arraylist.deinit(&tree.storage.nodes)
arraylist.deinit(&tree.storage.extra)
arraylist.deinit(&tree.storage.spans)
arraylist.deinit(&tree.storage.sources)
}
add_literal proc(builder @mut Builder, kind LiteralKind, span Span) NodeId ! mem.AllocError {
node_kind :: match kind {
.int: .literal_int
.float: .literal_float
.string: .literal_string
.bool: .literal_bool
.null: .literal_null
.void: .literal_void
.undefined: .literal_undefined
}
return try add_node(builder, Node{ kind = node_kind }, span)
}
add_identifier proc(
builder @mut Builder,
span Span,
interned_name strpool.StringId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_identifier,
main = NodeData{ name_id = interned_name },
}, span)
}
#! copies selector spelling into AST-owned storage, independently of its diagnostic span.
add_generated_identifier proc(
builder @mut Builder,
at Span,
interned_name strpool.StringId,
text []u8,
) NodeId ! mem.AllocError {
id :: try add_generated_literal(builder, at, .string, text)
node :: &builder.storage.nodes.items[usize(id)]
node.kind = .expr_identifier
node.data1 = node.main
node.main = NodeData{ name_id = interned_name }
return id
}
add_unary proc(
builder @mut Builder,
span Span,
operator UnaryOperator,
operand NodeId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_unary,
main = NodeData{ unary_operator = operator },
data0 = NodeData{ node_id = operand },
}, span)
}
add_binary proc(
builder @mut Builder,
span Span,
operator BinaryOperator,
left NodeId,
right NodeId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_binary,
main = NodeData{ binary_operator = operator },
data0 = NodeData{ node_id = left },
data1 = NodeData{ node_id = right },
}, span)
}
#! preserves a generated constant's declared type without introducing a runtime binding.
add_typed_constant proc(
builder @mut Builder, at Span, type, value NodeId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_typed_constant,
data0 = NodeData{ node_id = type },
data1 = NodeData{ node_id = value },
}, at)
}
add_comptime_expr proc(builder @mut Builder, at Span, operand NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_comptime,
main = NodeData{ node_id = operand },
}, at)
}
#! retains the exact IEEE representation, including signed zero and NaN payloads.
add_float proc(builder @mut Builder, at Span, value f64) NodeId ! mem.AllocError {
bits :: ptrcast!(@u64, &value)^
return try add_node(builder, Node{
kind = .literal_float_value,
data0 = NodeData{ extra = u32(bits & 4294967295) },
data1 = NodeData{ extra = u32(bits >> 32) },
}, at)
}
add_binary_type proc(
builder @mut Builder, span Span, left, right NodeId, fallible bool,
) NodeId ! mem.AllocError {
kind NodeKind :: if (fallible) .expr_fallible_type else .expr_sum_type
return try add_node(builder, Node{
kind = kind,
main = NodeData{ binary_operator = .add },
data0 = NodeData{ node_id = left },
data1 = NodeData{ node_id = right },
}, span)
}
add_try_expr proc(builder @mut Builder, span Span, operand NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_try,
main = NodeData{ node_id = operand },
}, span)
}
add_catch_expr proc(
builder @mut Builder, span Span, operand NodeId, capture ?NodeId,
handler NodeId, handler_is_block bool,
) NodeId ! mem.AllocError {
capture_id :: if (capture) |id| u32(id) else u32(no_node)
block_flag :: if (handler_is_block) u32(1) else u32(0)
extra :: try add_extra(builder, capture_id)
_ = try add_extra(builder, block_flag)
return try add_node(builder, Node{
kind = .expr_catch,
main = NodeData{ extra = extra },
data0 = NodeData{ node_id = operand },
data1 = NodeData{ node_id = handler },
}, span)
}
add_range proc(
builder @mut Builder, span Span, start NodeId, end NodeId, inclusive bool,
) NodeId ! mem.AllocError {
flag :: if (inclusive) u32(1) else u32(0)
return try add_node(builder, Node{
kind = .expr_range,
main = NodeData{ extra = flag },
data0 = NodeData{ node_id = start },
data1 = NodeData{ node_id = end },
}, span)
}
add_call proc(
builder @mut Builder,
span Span,
callee NodeId,
arguments []NodeId,
) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (arguments) |argument| _ = try add_extra(builder, u32(argument))
return try add_node(builder, Node{
kind = .expr_call,
main = NodeData{ node_id = callee },
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
add_builtin_call proc(builder @mut Builder, span Span, name NodeId, arguments []NodeId) NodeId ! mem.AllocError {
id :: try add_call(builder, span, name, arguments)
builder.storage.nodes.items[usize(id)].kind = .expr_builtin_call
return id
}
add_optional_type proc(builder @mut Builder, span Span, element NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{ kind = .expr_optional_type, main = NodeData{ node_id = element } }, span)
}
add_slice_type proc(builder @mut Builder, span Span, element NodeId, mutable bool, sentinel ?NodeId) NodeId ! mem.AllocError {
flag u32 :: if (mutable) 1 else 0
raw_sentinel :: if (sentinel) |id| id else no_node
return try add_node(builder, Node{
kind = .expr_slice_type,
main = NodeData{ extra = flag },
data0 = NodeData{ node_id = raw_sentinel },
data1 = NodeData{ node_id = element },
}, span)
}
add_slice proc(builder @mut Builder, span Span, base NodeId, start, end, sentinel ?NodeId) NodeId ! mem.AllocError {
raw_start :: if (start) |id| u32(id) else u32(no_node)
raw_end :: if (end) |id| u32(id) else u32(no_node)
raw_sentinel :: if (sentinel) |id| u32(id) else u32(no_node)
extra :: try add_extra(builder, raw_start)
_ = try add_extra(builder, raw_end)
_ = try add_extra(builder, raw_sentinel)
return try add_node(builder, Node{
kind = .expr_slice, main = NodeData{ node_id = base }, data0 = NodeData{ extra = extra },
}, span)
}
add_array_type proc(
builder @mut Builder, span Span, count ?NodeId, element NodeId, mutable bool,
) NodeId ! mem.AllocError {
raw_count :: if (count) |id| id else no_node
flag :: if (mutable) u32(1) else u32(0)
return try add_node(builder, Node{
kind = .expr_array_type,
main = NodeData{ extra = flag },
data0 = NodeData{ node_id = raw_count },
data1 = NodeData{ node_id = element },
}, span)
}
add_pointer_type proc(
builder @mut Builder,
span Span,
element NodeId,
mutable, many bool,
) NodeId ! mem.AllocError {
mutable_flag :: if (mutable) u32(1) else u32(0)
many_flag :: if (many) u32(1) else u32(0)
return try add_node(builder, Node{
kind = .expr_pointer_type,
main = NodeData{ extra = mutable_flag },
data0 = NodeData{ extra = many_flag },
data1 = NodeData{ node_id = element },
}, span)
}
add_function_type proc(
builder @mut Builder, span Span, result NodeId, params []NodeId,
) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (params) |parameter| _ = try add_extra(builder, u32(parameter))
return try add_node(builder, Node{
kind = .expr_function_type,
main = NodeData{ node_id = result },
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (elements) |element| _ = try add_extra(builder, u32(element))
return try add_node(builder, Node{
kind = .expr_array_literal,
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
add_record_type proc(builder @mut Builder, span Span, fields []NodeId) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
return try add_node(builder, Node{
kind = .expr_record_type,
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
set_record_c_layout proc(builder @mut Builder, id NodeId) void {
node :: &builder.storage.nodes.items[usize(id)]
debug.assert(node.kind == .expr_record_type)
node.main = NodeData{ extra = 1 }
}
record_c_layout proc(tree @Tree, id NodeId) bool {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_record_type)
return node.main.extra == 1
}
add_enum_type proc(builder @mut Builder, span Span, backing ?NodeId, members []NodeId) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (members) |member| _ = try add_extra(builder, u32(member))
raw_backing :: if (backing) |id| id else no_node
return try add_node(builder, Node{
kind = .expr_enum_type,
main = NodeData{ node_id = raw_backing },
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
add_union_type proc(builder @mut Builder, span Span, tag ?NodeId, auto_tag bool, fields []NodeId) NodeId ! mem.AllocError {
raw_tag :: if (tag) |id| u32(id) else u32(no_node)
tag_flag :: if (auto_tag) u32(1) else u32(0)
metadata :: try add_extra(builder, raw_tag)
_ = try add_extra(builder, tag_flag)
start :: builder.storage.extra.items.len
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
return try add_node(builder, Node{
kind = .expr_union_type,
main = NodeData{ extra = metadata },
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
add_type_operand proc(builder @mut Builder, span Span, operand NodeId, transparent bool) NodeId ! mem.AllocError {
kind :: if (transparent) NodeKind.expr_alias else NodeKind.expr_distinct_type
return try add_node(builder, Node{
kind = kind,
main = NodeData{ node_id = operand },
}, span)
}
add_union_tag_type proc(builder @mut Builder, span Span, operand NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_union_tag_type,
main = NodeData{ node_id = operand },
}, span)
}
add_enum_literal proc(builder @mut Builder, span Span, member NodeId) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_enum_literal,
main = NodeData{ node_id = member },
}, span)
}
add_match proc(builder @mut Builder, span Span, subject NodeId, arms []NodeId) NodeId ! mem.AllocError {
start :: builder.storage.extra.items.len
for (arms) |arm| _ = try add_extra(builder, u32(arm))
return try add_node(builder, Node{
kind = .expr_match,
main = NodeData{ node_id = subject },
data0 = NodeData{ extra = u32(start) },
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
}, span)
}
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_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
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)
}
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
}
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_break: .break_stmt
.stmt_continue: .continue_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
}
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 = 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,
}
}
while_stmt proc(tree @Tree, id NodeId) WhileView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .stmt_while)
return WhileView{
span = get_span(&tree.storage, id),
condition = node.main.node_id,
body = node.data0.node_id,
capture = maybe_node_id(u32(node.data1.node_id)),
}
}
for_stmt proc(tree @Tree, id NodeId) ForView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .stmt_for)
extra :: usize(node.data1.extra)
return ForView{
span = get_span(&tree.storage, id),
iterable = node.main.node_id,
capture = maybe_node_id(tree.storage.extra.items[extra]),
index = maybe_node_id(tree.storage.extra.items[extra + 1]),
pointer_capture = tree.storage.extra.items[extra + 2] != 0,
index_capture = (tree.storage.extra.items[extra + 3] & 1) != 0,
body = node.data0.node_id,
is_inline = (tree.storage.extra.items[extra + 3] & 2) != 0,
}
}
statement_decl proc(tree @Tree, id NodeId) StatementDeclView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .stmt_decl)
extra :: usize(node.main.extra)
raw_type :: tree.storage.extra.items[extra + 2]
return StatementDeclView{
span = get_span(&tree.storage, id),
name = span_from_extra(&tree.storage, extra, get_span(&tree.storage, id).source),
type = maybe_node_id(raw_type),
initializer = NodeId(tree.storage.extra.items[extra + 3]),
mutability = node.data0.mutability,
}
}
function_decl proc(tree @Tree, id NodeId) FunctionDeclView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl)
extra :: usize(node.main.extra)
return FunctionDeclView{
span = get_span(&tree.storage, id),
name = span_from_extra(&tree.storage, extra, get_span(&tree.storage, id).source),
return_type = NodeId(tree.storage.extra.items[extra + 2]),
body = node.data0.node_id,
}
}
callable_result proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id)
if (node.kind == .func_decl) return function_decl(tree, id).return_type
debug.assert(node.kind == .expr_function_type)
return node.main.node_id
}
import_decl proc(tree @Tree, id NodeId) ImportView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .import_decl)
span :: get_span(&tree.storage, id)
namespace ?strpool.StringId :: if (node.data0.name_id == strpool.no_str) null else node.data0.name_id
return ImportView{
span = span,
path = span_from_extra(&tree.storage, usize(node.main.extra), span.source),
namespace = namespace,
test_only = node.data1.extra != 0,
}
}
function_param_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
suffix :: if (node.kind == .func_decl) u32(1) else u32(0)
return usize(node.data1.extra - start - suffix)
}
function_param proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .func_decl or node.kind == .expr_function_type)
start :: if (node.kind == .func_decl) node.main.extra + 4 else node.data0.extra
debug.assert(index < function_param_count(tree, id))
return NodeId(tree.storage.extra.items[usize(start) + index])
}
param proc(tree @Tree, id NodeId) ParamView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .param_decl)
return ParamView{
span = get_span(&tree.storage, id),
name = span_from_extra(&tree.storage, usize(node.main.extra), get_span(&tree.storage, id).source),
type = node.data0.node_id,
comptime = tree.storage.extra.items[usize(node.main.extra) + 2] != 0,
}
}
block_statement_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .block)
return usize(node.data1.extra - node.data0.extra)
}
block_statement proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .block)
debug.assert(index < usize(node.data1.extra - node.data0.extra))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
expr_kind proc(tree @Tree, id NodeId) ExprKind {
return expression_kind(get_node(&tree.storage, id).kind)
}
builder_expr_kind proc(builder @Builder, id NodeId) ExprKind {
return expression_kind(get_node(&builder.storage, id).kind)
}
@hide:file
expression_kind proc(kind NodeKind) ExprKind {
return match kind {
.literal_int: .literal_int
.literal_float: .literal_float
.literal_string: .literal_string
.literal_bool: .literal_bool
.expr_identifier: .identifier
.expr_unary: .unary
.expr_binary: .binary
.expr_call: .call
.expr_range: .range
.expr_array_type: .array_type
.expr_pointer_type: .pointer_type
.expr_function_type: .function_type
.expr_array_literal: .array_literal
.expr_record_type: .record_type
.expr_record_literal: .record_literal
.expr_enum_type: .enum_type
.expr_union_type: .union_type
.expr_distinct_type: .distinct_type
.expr_alias: .alias_expr
.expr_union_tag_type: .union_tag_type
.expr_enum_literal: .enum_literal
.expr_match: .match_expr
.expr_index: .index
.expr_selection: .selection
.invalid_expr: .invalid
.expr_slice_type: .slice_type
.expr_optional_type: .optional_type
.expr_slice: .slice
.expr_builtin_call: .builtin_call
.literal_null: .literal_null
.expr_fallible_type: .fallible_type
.expr_sum_type: .sum_type
.expr_try: .try_expr
.expr_catch: .catch_expr
.expr_typed_constant: .typed_constant
.expr_comptime: .comptime_expr
.literal_float_value: .literal_float
.literal_void: .literal_void
.literal_undefined: .literal_undefined
else: unreachable
}
}
unary_expr proc(tree @Tree, id NodeId) UnaryExprView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_unary)
return UnaryExprView{
span = get_span(&tree.storage, id),
operator = node.main.unary_operator,
operand = node.data0.node_id,
}
}
binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_binary or node.kind == .expr_fallible_type or node.kind == .expr_sum_type)
return BinaryExprView{
span = get_span(&tree.storage, id),
operator = node.main.binary_operator,
left = node.data0.node_id,
right = node.data1.node_id,
}
}
comptime_operand proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_comptime)
return node.main.node_id
}
#! generated IEEE literals have no textual spelling; parsed float literals return null.
float_value proc(tree @Tree, id NodeId) ?f64 {
node :: get_node(&tree.storage, id)
if (node.kind != .literal_float_value) return null
bits :: u64(node.data0.extra) | (u64(node.data1.extra) << 32)
return ptrcast!(@f64, &bits)^
}
typed_constant proc(tree @Tree, id NodeId) TypedConstantView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_typed_constant)
return TypedConstantView{
type = node.data0.node_id,
value = node.data1.node_id,
}
}
catch_expr proc(tree @Tree, id NodeId) CatchExprView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_catch)
extra :: usize(node.main.extra)
return CatchExprView{
span = get_span(&tree.storage, id),
operand = node.data0.node_id,
capture = maybe_node_id(tree.storage.extra.items[extra]),
handler = node.data1.node_id,
handler_is_block = tree.storage.extra.items[extra + 1] != 0,
}
}
range_expr proc(tree @Tree, id NodeId) RangeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_range)
return RangeView{
span = get_span(&tree.storage, id),
start = node.data0.node_id,
end = node.data1.node_id,
inclusive = node.main.extra != 0,
}
}
call_expr proc(tree @Tree, id NodeId) CallExprView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_call)
return CallExprView{
span = get_span(&tree.storage, id),
callee = node.main.node_id,
}
}
call_arg_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_call or node.kind == .expr_builtin_call)
return usize(node.data1.extra - node.data0.extra)
}
call_arg proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_call or node.kind == .expr_builtin_call)
debug.assert(index < usize(node.data1.extra - node.data0.extra))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
array_type proc(tree @Tree, id NodeId) ArrayTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_type)
return ArrayTypeView{
span = get_span(&tree.storage, id),
count = maybe_node_id(u32(node.data0.node_id)),
element = node.data1.node_id,
mutable = node.main.extra != 0,
}
}
pointer_type proc(tree @Tree, id NodeId) PointerTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_pointer_type)
return PointerTypeView{
element = node.data1.node_id,
mutable = node.main.extra != 0,
many = node.data0.extra != 0,
}
}
slice_type proc(tree @Tree, id NodeId) SliceTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_slice_type)
return SliceTypeView{
element = node.data1.node_id, mutable = node.main.extra != 0,
sentinel = maybe_node_id(u32(node.data0.node_id)),
}
}
slice_expr proc(tree @Tree, id NodeId) SliceView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_slice)
extra :: usize(node.data0.extra)
return SliceView{
base = node.main.node_id,
start = maybe_node_id(tree.storage.extra.items[extra]),
end = maybe_node_id(tree.storage.extra.items[extra + 1]),
sentinel = maybe_node_id(tree.storage.extra.items[extra + 2]),
}
}
builtin_call proc(tree @Tree, id NodeId) BuiltinCallView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_builtin_call)
return BuiltinCallView{ name = node.main.node_id }
}
array_element_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_literal)
return usize(node.data1.extra - node.data0.extra)
}
array_element proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_array_literal)
debug.assert(index < usize(node.data1.extra - node.data0.extra))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
record_literal proc(tree @Tree, id NodeId) RecordLiteralView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_record_literal)
return RecordLiteralView{
span = get_span(&tree.storage, id),
type = maybe_node_id(u32(node.main.node_id)),
}
}
field proc(tree @Tree, id NodeId) FieldView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .record_field)
return FieldView{
span = get_span(&tree.storage, id),
name = maybe_node_id(u32(node.main.node_id)),
type = maybe_node_id(u32(node.data0.node_id)),
value = maybe_node_id(u32(node.data1.node_id)),
}
}
enum_type proc(tree @Tree, id NodeId) EnumTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_enum_type)
return EnumTypeView{ backing = maybe_node_id(u32(node.main.node_id)) }
}
enum_member_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_enum_type)
return usize(node.data1.extra - node.data0.extra)
}
enum_member proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(index < enum_member_count(tree, id))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
union_type proc(tree @Tree, id NodeId) UnionTypeView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_union_type)
extra :: usize(node.main.extra)
return UnionTypeView{
tag = maybe_node_id(tree.storage.extra.items[extra]),
auto_tag = tree.storage.extra.items[extra + 1] != 0,
}
}
type_operand proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias or node.kind == .expr_optional_type or node.kind == .expr_try or node.kind == .expr_union_tag_type)
return node.main.node_id
}
enum_literal proc(tree @Tree, id NodeId) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_enum_literal)
return node.main.node_id
}
match_expr proc(tree @Tree, id NodeId) MatchView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_match)
return MatchView{ subject = node.main.node_id }
}
match_arm_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_match)
return usize(node.data1.extra - node.data0.extra)
}
match_arm proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(index < match_arm_count(tree, id))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
match_arm_view proc(tree @Tree, id NodeId) MatchArmView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .match_arm)
extra :: usize(node.data0.extra)
return MatchArmView{
capture = maybe_node_id(tree.storage.extra.items[extra]),
body = node.main.node_id,
expression = (tree.storage.extra.items[extra + 1] & 1) != 0,
catch_all = tree.storage.extra.items[extra + 2] != 0,
is_inline = (tree.storage.extra.items[extra + 1] & 2) != 0,
tag_capture = maybe_node_id(tree.storage.extra.items[extra + 3]),
tag_capture_present = (tree.storage.extra.items[extra + 1] & 4) != 0,
}
}
match_pattern_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .match_arm)
return usize(node.data1.extra - node.data0.extra - 4)
}
match_pattern proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(index < match_pattern_count(tree, id))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + 4 + index])
}
record_field_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal or node.kind == .expr_union_type)
return usize(node.data1.extra - node.data0.extra)
}
record_field proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal or node.kind == .expr_union_type)
debug.assert(index < usize(node.data1.extra - node.data0.extra))
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
}
index_expr proc(tree @Tree, id NodeId) IndexView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_index)
return IndexView{
span = get_span(&tree.storage, id),
base = node.data0.node_id,
index = node.data1.node_id,
}
}
selection_expr proc(tree @Tree, id NodeId) SelectionView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_selection)
return SelectionView{
span = get_span(&tree.storage, id),
base = node.data0.node_id,
member = node.data1.node_id,
}
}
node_span proc(tree @Tree, id NodeId) Span {
return get_span(&tree.storage, id)
}
invalid proc(tree @Tree, id NodeId) InvalidView {
node :: get_node(&tree.storage, id)
debug.assert(
node.kind == .invalid_expr
or node.kind == .invalid_stmt
or node.kind == .invalid_decl
)
return InvalidView{
span = get_span(&tree.storage, id),
diagnostic = node.main.diagnostic,
}
}
#! returns text borrowed from the source buffer; the caller must not free it.
source_text proc(tree @Tree, span Span) []u8 {
debug.assert(usize(span.source) < tree.storage.sources.items.len)
text :: tree.storage.sources.items[usize(span.source)].text
debug.assert(span.start <= span.end and usize(span.end) <= text.len)
return text[usize(span.start)..usize(span.end)]
}
builder_span proc(builder @Builder, id NodeId) Span {
return get_span(&builder.storage, id)
}
@hide:file
span_from_extra proc(storage @Storage, start usize, source diagnostics.SourceId) Span {
return Span{
start = storage.extra.items[start],
end = storage.extra.items[start + 1],
source = source,
}
}
@hide:file
add_node proc(builder @mut Builder, node Node, span Span) NodeId ! mem.AllocError {
debug.assert(builder.storage.nodes.items.len == builder.storage.spans.items.len)
if (builder.storage.sources.items.len == 0)
_ = try set_source(builder, "", builder.source)
id :: node_id(builder.storage.nodes.items.len)
try arraylist.reserve(&builder.storage.nodes, builder.storage.nodes.items.len + 1)
try arraylist.reserve(&builder.storage.spans, builder.storage.spans.items.len + 1)
try arraylist.append(&builder.storage.nodes, node)
try arraylist.append(&builder.storage.spans, span)
return id
}
@hide:file
add_extra proc(builder @mut Builder, value u32) u32 ! mem.AllocError {
debug.assert(u64(builder.storage.extra.items.len) < u64(maxval!(u32)))
id :: u32(builder.storage.extra.items.len)
try arraylist.append(&builder.storage.extra, value)
return id
}
@hide:file
get_node proc(storage @Storage, id NodeId) Node {
debug.assert(usize(id) < storage.nodes.items.len)
return storage.nodes.items[usize(id)]
}
@hide:file
get_span proc(storage @Storage, id NodeId) Span {
debug.assert(usize(id) < storage.spans.items.len)
return storage.spans.items[usize(id)]
}
@hide:file
node_id proc(index usize) NodeId {
debug.assert(u64(index) < u64(no_node))
return NodeId(index)
}
@hide:file
maybe_node_id proc(value u32) ?NodeId {
if (value == u32(no_node)) return null
return NodeId(value)
}
NodeClass :: enum {
expression, statement, function, parameter, block, field, match_arm, package, import, invalid
}
@hide:file
NodeLayout :: struct {
class NodeClass = .expression
direct_children u32 = 0
extra_pointers u32 = 0
extra_start usize = 0
extra_count usize = 0
extra_children u32 = 0
list_start usize = 0
list_end usize = 0
}
#! describes owned extra storage and child slots, excluding names, spans, and flags.
@hide:file
node_layout proc(node Node) NodeLayout {
layout := NodeLayout{}
match node.kind {
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
.literal_void, .literal_undefined, .expr_identifier, .invalid_expr: {}
.expr_unary: layout.direct_children = 2
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
layout.direct_children = 6
.expr_pointer_type: layout.direct_children = 4
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_union_tag_type, .expr_enum_literal,
.expr_try, .expr_comptime: 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_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, .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
}