feat: support enums, distinct types, unions and matching
This commit is contained in:
+189
-4
@@ -1,12 +1,14 @@
|
||||
#! grammar:
|
||||
#! * type ..... -> identifier {'.' identifier} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' [field {(',' | newline) field}] '}'
|
||||
#! * type ..... -> identifier {'.' member} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
|
||||
#! * binding ... -> :: | :=
|
||||
#! * literal .. -> int | float | string | 'true' | 'false'
|
||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||
#! * expr ..... -> identifier | literal | type | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (identifier | int) | expr ('..' | '..=') expr
|
||||
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
|
||||
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
|
||||
#! * block .... -> '{' {stmt} '}'
|
||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
||||
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
|
||||
#! * stmt ..... -> decl | expr | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||
#! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if.
|
||||
#! precedence (tightest first): calls/indices/selections, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
|
||||
@@ -45,6 +47,12 @@ ExprKind :: enum {
|
||||
array_literal
|
||||
record_type
|
||||
record_literal
|
||||
enum_type
|
||||
union_type
|
||||
distinct_type
|
||||
alias_expr
|
||||
enum_literal
|
||||
match_expr
|
||||
index
|
||||
selection
|
||||
invalid
|
||||
@@ -101,6 +109,13 @@ NodeKind :: enum {
|
||||
expr_record_type
|
||||
expr_record_literal
|
||||
record_field
|
||||
expr_enum_type
|
||||
expr_union_type
|
||||
expr_distinct_type
|
||||
expr_alias
|
||||
expr_enum_literal
|
||||
expr_match
|
||||
match_arm
|
||||
expr_index
|
||||
expr_selection
|
||||
stmt_expr
|
||||
@@ -228,6 +243,16 @@ FieldView :: struct {
|
||||
value ?NodeId
|
||||
}
|
||||
|
||||
EnumTypeView :: struct { backing ?NodeId }
|
||||
UnionTypeView :: struct { tag ?NodeId, auto_tag bool }
|
||||
MatchView :: struct { subject NodeId }
|
||||
MatchArmView :: struct {
|
||||
capture ?NodeId
|
||||
body NodeId
|
||||
expression bool
|
||||
catch_all bool
|
||||
}
|
||||
|
||||
IndexView :: struct {
|
||||
span Span
|
||||
base NodeId
|
||||
@@ -477,6 +502,78 @@ add_record_type proc(builder @mut Builder, span Span, fields []NodeId) NodeId !
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_enum_type proc(builder @mut Builder, span Span, backing ?NodeId, members []NodeId) NodeId ! mem.AllocError {
|
||||
start :: builder.storage.extra.items.len
|
||||
for (members) |member| _ = try add_extra(builder, u32(member))
|
||||
raw_backing :: if (backing) |id| id else no_node
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_enum_type,
|
||||
main = NodeData{ node_id = raw_backing },
|
||||
data0 = NodeData{ extra = u32(start) },
|
||||
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_union_type proc(builder @mut Builder, span Span, tag ?NodeId, auto_tag bool, fields []NodeId) NodeId ! mem.AllocError {
|
||||
raw_tag :: if (tag) |id| u32(id) else u32(no_node)
|
||||
tag_flag :: if (auto_tag) u32(1) else u32(0)
|
||||
metadata :: try add_extra(builder, raw_tag)
|
||||
_ = try add_extra(builder, tag_flag)
|
||||
start :: builder.storage.extra.items.len
|
||||
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_union_type,
|
||||
main = NodeData{ extra = metadata },
|
||||
data0 = NodeData{ extra = u32(start) },
|
||||
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_type_operand proc(builder @mut Builder, span Span, operand NodeId, transparent bool) NodeId ! mem.AllocError {
|
||||
kind :: if (transparent) NodeKind.expr_alias else NodeKind.expr_distinct_type
|
||||
return try add_node(builder, Node{
|
||||
kind = kind,
|
||||
main = NodeData{ node_id = operand },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_enum_literal proc(builder @mut Builder, span Span, member NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_enum_literal,
|
||||
main = NodeData{ node_id = member },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_match proc(builder @mut Builder, span Span, subject NodeId, arms []NodeId) NodeId ! mem.AllocError {
|
||||
start :: builder.storage.extra.items.len
|
||||
for (arms) |arm| _ = try add_extra(builder, u32(arm))
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_match,
|
||||
main = NodeData{ node_id = subject },
|
||||
data0 = NodeData{ extra = u32(start) },
|
||||
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_match_arm proc(
|
||||
builder @mut Builder, span Span, patterns []NodeId, capture ?NodeId,
|
||||
body NodeId, expression, catch_all bool,
|
||||
) NodeId ! mem.AllocError {
|
||||
raw_capture :: if (capture) |id| u32(id) else u32(no_node)
|
||||
expression_flag :: if (expression) u32(1) else u32(0)
|
||||
catch_all_flag :: if (catch_all) u32(1) else u32(0)
|
||||
start :: try add_extra(builder, raw_capture)
|
||||
_ = try add_extra(builder, expression_flag)
|
||||
_ = try add_extra(builder, catch_all_flag)
|
||||
for (patterns) |pattern| _ = try add_extra(builder, u32(pattern))
|
||||
return try add_node(builder, Node{
|
||||
kind = .match_arm,
|
||||
main = NodeData{ node_id = body },
|
||||
data0 = NodeData{ extra = start },
|
||||
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_record_literal proc(
|
||||
builder @mut Builder, span Span, type ?NodeId, fields []NodeId,
|
||||
) NodeId ! mem.AllocError {
|
||||
@@ -979,6 +1076,12 @@ expression_kind proc(kind NodeKind) ExprKind {
|
||||
.expr_array_literal: .array_literal
|
||||
.expr_record_type: .record_type
|
||||
.expr_record_literal: .record_literal
|
||||
.expr_enum_type: .enum_type
|
||||
.expr_union_type: .union_type
|
||||
.expr_distinct_type: .distinct_type
|
||||
.expr_alias: .alias_expr
|
||||
.expr_enum_literal: .enum_literal
|
||||
.expr_match: .match_expr
|
||||
.expr_index: .index
|
||||
.expr_selection: .selection
|
||||
.invalid_expr: .invalid
|
||||
@@ -1084,15 +1187,97 @@ field proc(tree @Tree, id NodeId) FieldView {
|
||||
}
|
||||
}
|
||||
|
||||
enum_type proc(tree @Tree, id NodeId) EnumTypeView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_enum_type)
|
||||
return EnumTypeView{ backing = maybe_node_id(u32(node.main.node_id)) }
|
||||
}
|
||||
|
||||
enum_member_count proc(tree @Tree, id NodeId) usize {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_enum_type)
|
||||
return usize(node.data1.extra - node.data0.extra)
|
||||
}
|
||||
|
||||
enum_member proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(index < enum_member_count(tree, id))
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||
}
|
||||
|
||||
union_type proc(tree @Tree, id NodeId) UnionTypeView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_union_type)
|
||||
extra :: usize(node.main.extra)
|
||||
return UnionTypeView{
|
||||
tag = maybe_node_id(tree.storage.extra.items[extra]),
|
||||
auto_tag = tree.storage.extra.items[extra + 1] != 0,
|
||||
}
|
||||
}
|
||||
|
||||
type_operand proc(tree @Tree, id NodeId) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias)
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
enum_literal proc(tree @Tree, id NodeId) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_enum_literal)
|
||||
return node.main.node_id
|
||||
}
|
||||
|
||||
match_expr proc(tree @Tree, id NodeId) MatchView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_match)
|
||||
return MatchView{ subject = node.main.node_id }
|
||||
}
|
||||
|
||||
match_arm_count proc(tree @Tree, id NodeId) usize {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_match)
|
||||
return usize(node.data1.extra - node.data0.extra)
|
||||
}
|
||||
|
||||
match_arm proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(index < match_arm_count(tree, id))
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||
}
|
||||
|
||||
match_arm_view proc(tree @Tree, id NodeId) MatchArmView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .match_arm)
|
||||
extra :: usize(node.data0.extra)
|
||||
return MatchArmView{
|
||||
capture = maybe_node_id(tree.storage.extra.items[extra]),
|
||||
body = node.main.node_id,
|
||||
expression = tree.storage.extra.items[extra + 1] != 0,
|
||||
catch_all = tree.storage.extra.items[extra + 2] != 0,
|
||||
}
|
||||
}
|
||||
|
||||
match_pattern_count proc(tree @Tree, id NodeId) usize {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .match_arm)
|
||||
return usize(node.data1.extra - node.data0.extra - 3)
|
||||
}
|
||||
|
||||
match_pattern proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(index < match_pattern_count(tree, id))
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + 3 + index])
|
||||
}
|
||||
|
||||
record_field_count proc(tree @Tree, id NodeId) usize {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal)
|
||||
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)
|
||||
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])
|
||||
}
|
||||
|
||||
@@ -76,6 +76,19 @@ Code :: enum {
|
||||
namespace_not_value
|
||||
not_namespace
|
||||
invalid_import_alias
|
||||
invalid_enum_backing
|
||||
invalid_enum_value
|
||||
enum_context_required
|
||||
invalid_distinct_backing
|
||||
invalid_union_tag
|
||||
union_initializer_count
|
||||
invalid_match_subject
|
||||
invalid_match_pattern
|
||||
duplicate_match_pattern
|
||||
non_exhaustive_match
|
||||
unreachable_match_arm
|
||||
invalid_match_capture
|
||||
inactive_union_field
|
||||
}
|
||||
|
||||
Diagnostic :: struct {
|
||||
@@ -173,5 +186,18 @@ messages :: $enummap.init(Code, []u8, {
|
||||
namespace_not_value = "package namespace cannot be used as a value",
|
||||
not_namespace = "member selection requires a package namespace",
|
||||
invalid_import_alias = "import requires an explicit valid identifier alias",
|
||||
invalid_enum_backing = "enum backing must be a concrete integer type",
|
||||
invalid_enum_value = "enum values require explicit backing and increasing representable constant integers",
|
||||
enum_context_required = "enum literal requires an enum or tagged union context",
|
||||
invalid_distinct_backing = "distinct requires a concrete runtime backing type",
|
||||
invalid_union_tag = "union tag must be an enum containing every union variant",
|
||||
union_initializer_count = "union construction requires exactly one member",
|
||||
invalid_match_subject = "match subject must be an enum, tagged union, or scalar",
|
||||
invalid_match_pattern = "invalid match pattern",
|
||||
duplicate_match_pattern = "duplicate or overlapping match pattern",
|
||||
non_exhaustive_match = "match is not exhaustive",
|
||||
unreachable_match_arm = "match arm is unreachable",
|
||||
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
|
||||
inactive_union_field = "tagged union field is not active",
|
||||
})
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ Address :: struct {
|
||||
cells []mut ir.Value
|
||||
scalar ?@mut ir.Value = null
|
||||
initialized ?@mut bool = null
|
||||
raw bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -104,6 +105,7 @@ run proc(
|
||||
if instruction_index == block.instructions.items.len {
|
||||
terminator :: block.terminator
|
||||
if (terminator.kind == .trap) return Outcome{ trap = terminator.diagnostic }
|
||||
if (terminator.kind == .unreachable) unreachable
|
||||
if (terminator.kind == .jump or terminator.kind == .branch) {
|
||||
target := terminator.target
|
||||
if (terminator.kind == .branch) {
|
||||
@@ -121,7 +123,7 @@ run proc(
|
||||
completed :: frames.items[frame_index]
|
||||
# transfer leaves while the completed frame still owns its backing.
|
||||
if completed.global |global_index| {
|
||||
copy_value(&globals[global_index].value, value)
|
||||
copy_value(module, &globals[global_index].value, value)
|
||||
globals[global_index].status = .ready
|
||||
}
|
||||
if (frames.items.len == 1) {
|
||||
@@ -134,7 +136,7 @@ run proc(
|
||||
return Outcome{ value = value }
|
||||
}
|
||||
if completed.destination != ir.no_value {
|
||||
copy_value(&frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
||||
copy_value(module, &frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
||||
}
|
||||
_ = arraylist.pop(&frames)
|
||||
free_frame(allocator, completed)
|
||||
@@ -155,7 +157,7 @@ run proc(
|
||||
}
|
||||
.store_local: {
|
||||
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
copy_value(module, &local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
local.initialized = true
|
||||
continue
|
||||
}
|
||||
@@ -164,7 +166,7 @@ run proc(
|
||||
if (!local.initialized) {
|
||||
return try runtime_trap(diagnostic_store, .write_before_initialization, instruction.span)
|
||||
}
|
||||
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
copy_value(module, &local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
continue
|
||||
}
|
||||
.reset_local: {
|
||||
@@ -199,7 +201,7 @@ run proc(
|
||||
for (0..instruction.arguments_count) |argument_index| {
|
||||
argument :: block.arguments.items[instruction.arguments_start + argument_index]
|
||||
local :: &frames.items[frame_index + 1].locals[argument_index]
|
||||
copy_value(&local.value, frames.items[frame_index].values[usize(argument)])
|
||||
copy_value(module, &local.value, frames.items[frame_index].values[usize(argument)])
|
||||
local.initialized = true
|
||||
}
|
||||
continue
|
||||
@@ -212,7 +214,8 @@ run proc(
|
||||
element :: frames.items[frame_index].values[usize(operand)]
|
||||
if (ir.aggregate(element.type)) {
|
||||
count :: element.data.elements.len
|
||||
memcopy!(destination.data.elements[offset..offset + count], element.data.elements)
|
||||
target :: aggregate_view(element.type, element.array_type, destination.data.elements[offset..offset + count])
|
||||
ir.copy_aggregate(module, target, element)
|
||||
offset += count
|
||||
} else {
|
||||
destination.data.elements[offset] = element
|
||||
@@ -221,6 +224,18 @@ run proc(
|
||||
}
|
||||
continue
|
||||
}
|
||||
.union_literal: {
|
||||
destination :: frames.items[frame_index].values[usize(instruction.destination)]
|
||||
shape :: module.arrays.items[usize(instruction.array_type)]
|
||||
mem.set(destination.data.elements, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||
if (shape.tag_type != .void) ir.write_storage(module, destination.data.elements, instruction.constant)
|
||||
if (instruction.left != ir.no_value) {
|
||||
payload :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
ir.write_storage(module, destination.data.elements[shape.payload_offset..], payload)
|
||||
}
|
||||
continue
|
||||
}
|
||||
.union_tag: value = ir.union_tag(module, frames.items[frame_index].values[usize(instruction.left)])
|
||||
.index: {
|
||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
@@ -232,6 +247,10 @@ run proc(
|
||||
}
|
||||
.field: {
|
||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
shape :: module.arrays.items[usize(base.array_type)]
|
||||
if (shape.is_union and !ir.union_active(module, base, usize(instruction.slot))) {
|
||||
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
|
||||
}
|
||||
value = ir.aggregate_item(module, base, usize(instruction.slot))
|
||||
}
|
||||
.address_local: {
|
||||
@@ -284,26 +303,37 @@ run proc(
|
||||
if (index >= u64(shape.len)) {
|
||||
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||
}
|
||||
width :: element_cells(module, shape)
|
||||
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
|
||||
start :: usize(index) * width
|
||||
address := Address{
|
||||
type = instruction.pointee, array_type = instruction.array_type,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized, raw = base.raw,
|
||||
}
|
||||
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||
}
|
||||
.field_address: {
|
||||
base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||
base :: frames.items[frame_index].addresses[usize(base_id)]
|
||||
field :: module.arrays.items[usize(base.array_type)].fields[usize(instruction.slot)]
|
||||
width :: if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
shape :: module.arrays.items[usize(base.array_type)]
|
||||
field :: shape.fields[usize(instruction.slot)]
|
||||
if (shape.is_union and shape.tag_type != .void) {
|
||||
if base.initialized |initialized| {
|
||||
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
|
||||
}
|
||||
if (!ir.union_active(module, aggregate_view(.record, base.array_type, base.cells), usize(instruction.slot))) {
|
||||
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
|
||||
}
|
||||
}
|
||||
raw :: base.raw or shape.is_union
|
||||
width :: if (raw) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
start :: if (raw) field.byte_offset else field.offset
|
||||
address := Address{
|
||||
type = instruction.pointee, array_type = instruction.array_type,
|
||||
cells = base.cells[field.offset..field.offset + width], initialized = base.initialized,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized, raw = raw,
|
||||
}
|
||||
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||
}
|
||||
@@ -317,10 +347,12 @@ run proc(
|
||||
}
|
||||
}
|
||||
if (instruction.op == .load_address) {
|
||||
value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
|
||||
if (address.raw) value = ir.read_storage(module, address.type, address.array_type, address.cells, true)
|
||||
else value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
|
||||
} else {
|
||||
source :: frames.items[frame_index].values[usize(instruction.right)]
|
||||
if (ir.aggregate(address.type)) memcopy!(address.cells, source.data.elements)
|
||||
if (address.raw) ir.write_storage(module, address.cells, source)
|
||||
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source)
|
||||
else address.scalar?^ = source
|
||||
continue
|
||||
}
|
||||
@@ -421,7 +453,7 @@ run proc(
|
||||
}
|
||||
}
|
||||
if instruction.destination != ir.no_value {
|
||||
copy_value(&frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||
copy_value(module, &frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||
}
|
||||
}
|
||||
unreachable
|
||||
@@ -518,10 +550,10 @@ aggregate_view proc(type ir.Type, shape ir.ArrayId, cells []mut ir.Value) ir.Val
|
||||
}
|
||||
|
||||
@hide:file
|
||||
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
||||
copy_value proc(module @ir.Module, destination @mut ir.Value, source ir.Value) void {
|
||||
if (ir.aggregate(source.type)) {
|
||||
# aggregate destinations already own disjoint fixed segments; never install an alias.
|
||||
memcopy!(destination.data.elements, source.data.elements)
|
||||
ir.copy_aggregate(module, destination^, source)
|
||||
} else {
|
||||
destination^ = source
|
||||
}
|
||||
|
||||
@@ -1463,3 +1463,126 @@ inclusive_unsigned_range_reaches_the_maximum_without_incrementing_past_it test {
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(u64(2), outcome.value.data.unsigned)
|
||||
}
|
||||
|
||||
union_storage_overlays_nested_record_and_array_payloads_and_copies_by_value test {
|
||||
module := ir.init(mem.c_allocator)
|
||||
defer ir.deinit(&module)
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
record_fields [2]ir.RecordField :: [ir.RecordField{ type = .u8 }, ir.RecordField{ type = .u32 }]
|
||||
record :: try ir.add_record(&module, record_fields[..])
|
||||
bytes :: try add_array(&module, .u8, ir.no_array, 8)
|
||||
variants [2]ir.RecordField :: [
|
||||
ir.RecordField{ type = .record, array_type = record },
|
||||
ir.RecordField{ type = .array, array_type = bytes },
|
||||
]
|
||||
shape :: try ir.add_union(&module, variants[..], false)
|
||||
identity :: try add_function(&module, &[], 0, .record)
|
||||
_ = try ir.add_parameter(&module, identity, .record, shape)
|
||||
module.functions.items[usize(identity)].result_array_type = shape
|
||||
try emit(&module, identity, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(0) })
|
||||
return_value(&module, identity, ir.ValueId(0))
|
||||
main :: try add_function(&module, &[], 0, .u32)
|
||||
_ = try ir.add_local(&module, main, .record, shape)
|
||||
module.entry = main
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(0), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 1 } } })
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .u32, destination = ir.ValueId(1), constant = ir.Value{ type = .u32, data = ir.ValueData{ unsigned = 287454020 } } })
|
||||
block :: &module.functions.items[usize(main)].blocks.items[0]
|
||||
try arraylist.append(&block.arguments, ir.ValueId(0))
|
||||
try arraylist.append(&block.arguments, ir.ValueId(1))
|
||||
try emit(&module, main, ir.Instruction{ op = .record_literal, type = .record, array_type = record, destination = ir.ValueId(2), arguments_count = 2 })
|
||||
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(3), left = ir.ValueId(2) })
|
||||
try emit(&module, main, ir.Instruction{ op = .store_local, left = ir.ValueId(3) })
|
||||
try emit(&module, main, function_value(4, identity))
|
||||
try arraylist.append(&block.arguments, ir.ValueId(3))
|
||||
try emit(&module, main, ir.Instruction{ op = .call, type = .record, array_type = shape, destination = ir.ValueId(5), left = ir.ValueId(4), arguments_start = 2, arguments_count = 1 })
|
||||
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(6) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field_address, type = .address, pointee = .array, array_type = bytes, destination = ir.ValueId(7), left = ir.ValueId(6), slot = 1 })
|
||||
try emit(&module, main, unsigned(8, 4))
|
||||
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .u8, destination = ir.ValueId(9), left = ir.ValueId(7), right = ir.ValueId(8) })
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(10), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 153 } } })
|
||||
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(9), right = ir.ValueId(10) })
|
||||
try emit(&module, main, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(11) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(12), left = ir.ValueId(11) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(13), left = ir.ValueId(12), slot = 1 })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(14), left = ir.ValueId(5) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(15), left = ir.ValueId(14), slot = 1 })
|
||||
try emit(&module, main, ir.Instruction{ op = .subtract, type = .u32, destination = ir.ValueId(16), left = ir.ValueId(13), right = ir.ValueId(15) })
|
||||
return_value(&module, main, ir.ValueId(16))
|
||||
outcome := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(u64(85), outcome.value.data.unsigned)
|
||||
}
|
||||
|
||||
tagged_unions_preserve_actual_tags_and_trap_inactive_value_and_address_projections test {
|
||||
operations [2]ir.Op :: [.field, .field_address]
|
||||
for (operations) |operation| {
|
||||
module := ir.init(mem.c_allocator)
|
||||
defer ir.deinit(&module)
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
tag :: ir.Value{ type = .i8, data = ir.ValueData{ integer = -1 } }
|
||||
variants [2]ir.RecordField :: [
|
||||
ir.RecordField{ type = .int, tag = tag },
|
||||
ir.RecordField{ type = .int, tag = ir.Value{ type = .i8, data = ir.ValueData{ integer = 37 } } },
|
||||
]
|
||||
shape :: try ir.add_union(&module, variants[..], true)
|
||||
main :: try add_function(&module, &[], 0, .i8)
|
||||
_ = try ir.add_local(&module, main, .record, shape)
|
||||
module.entry = main
|
||||
try emit(&module, main, integer(0, 42))
|
||||
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0), constant = tag })
|
||||
try emit(&module, main, ir.Instruction{ op = .union_tag, type = .i8, destination = ir.ValueId(2), left = ir.ValueId(1) })
|
||||
return_value(&module, main, ir.ValueId(2))
|
||||
outcome := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(-1), outcome.value.data.integer)
|
||||
base := ir.ValueId(1)
|
||||
if (operation == .field_address) {
|
||||
try emit(&module, main, ir.Instruction{ op = .store_local, left = base })
|
||||
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(3) })
|
||||
base = ir.ValueId(3)
|
||||
}
|
||||
span :: diagnostics.Span{ start = 11, end = 15 }
|
||||
type :: if (operation == .field) ir.Type.int else ir.Type.address
|
||||
try emit(&module, main, ir.Instruction{ op = operation, type = type, pointee = .int, destination = ir.ValueId(4), left = base, slot = 1, span = span })
|
||||
trapped := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&trapped)
|
||||
try expect_trap(trapped, &store, .inactive_union_field, span)
|
||||
}
|
||||
}
|
||||
|
||||
union_scalar_reinterpretation_and_zero_leaf_payloads_keep_their_shapes test {
|
||||
module := ir.init(mem.c_allocator)
|
||||
defer ir.deinit(&module)
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
empty :: try ir.add_record(&module, &[])
|
||||
variants [3]ir.RecordField :: [
|
||||
ir.RecordField{ type = .float },
|
||||
ir.RecordField{ type = .u64 },
|
||||
ir.RecordField{ type = .record, array_type = empty },
|
||||
]
|
||||
shape :: try ir.add_union(&module, variants[..], false)
|
||||
main :: try add_function(&module, &[], 0, .u64)
|
||||
module.entry = main
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(0), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 1.0 } } })
|
||||
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .u64, destination = ir.ValueId(2), left = ir.ValueId(1), slot = 1 })
|
||||
return_value(&module, main, ir.ValueId(2))
|
||||
bits := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&bits)
|
||||
try testing.expect(bits.trap == null)
|
||||
try testing.expect_equal(u64(4607182418800017408), bits.value.data.unsigned)
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = empty, destination = ir.ValueId(3), left = ir.ValueId(1), slot = 2 })
|
||||
module.functions.items[usize(main)].result = .record
|
||||
module.functions.items[usize(main)].result_array_type = empty
|
||||
return_value(&module, main, ir.ValueId(3))
|
||||
zero := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&zero)
|
||||
try testing.expect(zero.trap == null)
|
||||
try testing.expect_equal(empty, zero.value.array_type)
|
||||
try testing.expect_equal(usize(0), zero.value.data.elements.len)
|
||||
}
|
||||
|
||||
+189
-9
@@ -85,6 +85,7 @@ ValueData :: union {
|
||||
Value :: struct {
|
||||
type Type = .void
|
||||
array_type ArrayId = no_array
|
||||
raw bool = false
|
||||
data ValueData = ValueData{ integer = 0 }
|
||||
}
|
||||
|
||||
@@ -92,10 +93,12 @@ RecordField :: struct {
|
||||
type Type
|
||||
array_type ArrayId = no_array
|
||||
offset usize = 0
|
||||
byte_offset usize = 0
|
||||
tag Value = Value{}
|
||||
}
|
||||
|
||||
#! fixed recursive aggregate shape; constructors derive the flattened scalar-leaf count.
|
||||
#! record fields are owned by the module; array metadata retains its existing representation.
|
||||
#! fixed recursive aggregate shape; cells count interpreter slots, byte_size describes native layout.
|
||||
#! unions retain overlapping byte cells; ordinary aggregates retain flattened scalar leaves.
|
||||
ArrayType :: struct {
|
||||
kind Type = .array
|
||||
element Type = .invalid
|
||||
@@ -104,6 +107,11 @@ ArrayType :: struct {
|
||||
cells usize = 0
|
||||
mutable bool = false
|
||||
fields []mut RecordField = &[]
|
||||
is_union bool = false
|
||||
tag_type Type = .void
|
||||
payload_offset usize = 0
|
||||
byte_size usize = 0
|
||||
alignment usize = 1
|
||||
}
|
||||
|
||||
#! maps a local slot or temporary to its preallocated, by-value frame segment.
|
||||
@@ -123,6 +131,8 @@ Op :: enum {
|
||||
load_global
|
||||
array_literal
|
||||
record_literal
|
||||
union_literal
|
||||
union_tag
|
||||
field
|
||||
index
|
||||
address_local
|
||||
@@ -167,6 +177,7 @@ TerminatorKind :: enum {
|
||||
trap,
|
||||
jump,
|
||||
branch,
|
||||
unreachable,
|
||||
}
|
||||
|
||||
Terminator :: struct {
|
||||
@@ -266,6 +277,8 @@ add_array proc(
|
||||
cells :: len * width
|
||||
if (cells > divtrunc!(maxval!(usize), sizeof!(Value))) return .storage_limit_reached
|
||||
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
||||
stride :: storage_size(module, element, element_array)
|
||||
if (stride != 0 and len > divtrunc!(maxval!(usize), stride)) return .storage_limit_reached
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
try arraylist.append(&module.arrays, ArrayType{
|
||||
element = element,
|
||||
@@ -273,6 +286,8 @@ add_array proc(
|
||||
len = len,
|
||||
cells = cells,
|
||||
mutable = mutable,
|
||||
byte_size = len * stride,
|
||||
alignment = storage_alignment(module, element, element_array),
|
||||
})
|
||||
return id
|
||||
}
|
||||
@@ -286,19 +301,35 @@ add_record proc(
|
||||
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
||||
errdefer mem.free(module.allocator, owned)
|
||||
cells usize := 0
|
||||
bytes usize := 0
|
||||
alignment usize := 1
|
||||
for (fields) |field, index| {
|
||||
debug.assert(aggregate(field.type) == (field.array_type != no_array))
|
||||
width :: if (aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
if (width > divtrunc!(maxval!(usize), sizeof!(Value)) - cells) return .storage_limit_reached
|
||||
owned[index] = RecordField{ type = field.type, array_type = field.array_type, offset = cells }
|
||||
field_alignment :: storage_alignment(module, field.type, field.array_type)
|
||||
if (field_alignment > alignment) alignment = field_alignment
|
||||
if (bytes > maxval!(usize) - (field_alignment - 1)) return .storage_limit_reached
|
||||
bytes = aligned_size(bytes, field_alignment)
|
||||
size :: storage_size(module, field.type, field.array_type)
|
||||
if (size > maxval!(usize) - bytes) return .storage_limit_reached
|
||||
owned[index] = RecordField{
|
||||
type = field.type, array_type = field.array_type, offset = cells,
|
||||
byte_offset = bytes, tag = field.tag,
|
||||
}
|
||||
bytes += size
|
||||
cells += width
|
||||
}
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
try arraylist.append(&module.arrays, ArrayType{ kind = .record, fields = owned, cells = cells })
|
||||
if (bytes > maxval!(usize) - (alignment - 1)) return .storage_limit_reached
|
||||
try arraylist.append(&module.arrays, ArrayType{
|
||||
kind = .record, fields = owned, cells = cells,
|
||||
byte_size = aligned_size(bytes, alignment), alignment = alignment,
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
||||
#! returns a borrowed child value from flattened aggregate storage, preserving zero-cell shapes.
|
||||
#! returns a borrowed child, including a raw view when projecting overlapping union storage.
|
||||
aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
type := shape.element
|
||||
@@ -310,13 +341,162 @@ aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||
type = field.type
|
||||
child = field.array_type
|
||||
start = field.offset
|
||||
if (value.raw) start = field.byte_offset
|
||||
if (shape.is_union) start = shape.payload_offset
|
||||
} else {
|
||||
if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
||||
if (value.raw) width = storage_size(module, type, child)
|
||||
else if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
||||
start = index * width
|
||||
}
|
||||
if (!aggregate(type)) return value.data.elements[start]
|
||||
width = module.arrays.items[usize(child)].cells
|
||||
return Value{ type = type, array_type = child, data = ValueData{ elements = value.data.elements[start..start + width] } }
|
||||
raw :: value.raw or shape.is_union
|
||||
if (!aggregate(type)) {
|
||||
if (raw) return read_storage(module, type, child, value.data.elements[start..], true)
|
||||
return value.data.elements[start]
|
||||
}
|
||||
width = if (raw) storage_size(module, type, child) else module.arrays.items[usize(child)].cells
|
||||
return Value{
|
||||
type = type, array_type = child, raw = raw,
|
||||
data = ValueData{ elements = value.data.elements[start..start + width] },
|
||||
}
|
||||
}
|
||||
|
||||
#! copies variant descriptors; all payloads share one eight-byte-aligned storage region.
|
||||
add_union proc(module @mut Module, fields []RecordField, tagged bool) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
||||
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
||||
errdefer mem.free(module.allocator, owned)
|
||||
size usize := 0
|
||||
for (fields) |field, index| {
|
||||
width :: storage_size(module, field.type, field.array_type)
|
||||
if (width > size) size = width
|
||||
owned[index] = field
|
||||
owned[index].offset = if (tagged) 8 else 0
|
||||
owned[index].byte_offset = owned[index].offset
|
||||
}
|
||||
prefix usize :: if (tagged) 8 else 0
|
||||
if (size > divtrunc!(maxval!(usize), sizeof!(Value)) - 15) return .storage_limit_reached
|
||||
size = aligned_size(size, 8) + prefix
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
tag_type :: if (tagged and fields.len != 0) fields[0].tag.type else Type.void
|
||||
try arraylist.append(&module.arrays, ArrayType{
|
||||
kind = .record, fields = owned, is_union = true,
|
||||
tag_type = tag_type,
|
||||
payload_offset = prefix, cells = size, byte_size = size, alignment = 8,
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
||||
@hide:file
|
||||
aligned_size proc(size, alignment usize) usize {
|
||||
return divtrunc!(size + alignment - 1, alignment) * alignment
|
||||
}
|
||||
|
||||
storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
|
||||
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
|
||||
bits :: integer_bits(type)
|
||||
if (bits != 0) return divtrunc!(bits, 8)
|
||||
return if (type == .float or type == .string or type == .function) 8 else 1
|
||||
}
|
||||
|
||||
storage_size proc(module @Module, type Type, shape ArrayId) usize {
|
||||
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
|
||||
bits :: integer_bits(type)
|
||||
if (bits != 0) return divtrunc!(bits, 8)
|
||||
return match type {
|
||||
.float, .function: 8
|
||||
.string: 16
|
||||
.bool: 1
|
||||
.void: 0
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
#! loads a scalar or borrows an aggregate from byte cells without allocating.
|
||||
read_storage proc(module @Module, type Type, shape ArrayId, cells []mut Value, raw bool) Value {
|
||||
if (aggregate(type)) {
|
||||
return Value{ type = type, array_type = shape, raw = raw, data = ValueData{ elements = cells[..storage_size(module, type, shape)] } }
|
||||
}
|
||||
bits u64 := 0
|
||||
size :: storage_size(module, type, shape)
|
||||
count :: if (size > 8) usize(8) else size
|
||||
for (0..count) |i| bits += cells[i].data.unsigned << u64(i * 8)
|
||||
data := ValueData{ unsigned = bits }
|
||||
match type {
|
||||
.i8: data = ValueData{ integer = i64(ptrcast!(@i8, &bits)^) }
|
||||
.i16: data = ValueData{ integer = i64(ptrcast!(@i16, &bits)^) }
|
||||
.i32: data = ValueData{ integer = i64(ptrcast!(@i32, &bits)^) }
|
||||
.int, .isize: data = ValueData{ integer = ptrcast!(@i64, &bits)^ }
|
||||
.float: data = ValueData{ real = ptrcast!(@f64, &bits)^ }
|
||||
.bool: data = ValueData{ boolean = (bits & 1) != 0 }
|
||||
.string: data = ValueData{ string = StringId(bits) }
|
||||
.function: data = ValueData{ function = FunctionId(bits) }
|
||||
.void: return Value{}
|
||||
else: {}
|
||||
}
|
||||
return Value{ type = type, data = data }
|
||||
}
|
||||
|
||||
#! stores a value in shared byte cells; recursive aggregates preserve their native field padding.
|
||||
write_storage proc(module @Module, cells []mut Value, value Value) void {
|
||||
if (aggregate(value.type)) {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
if (value.raw or shape.is_union) {
|
||||
memcopy!(cells[..shape.byte_size], value.data.elements)
|
||||
return
|
||||
}
|
||||
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||
for (0..count) |i| {
|
||||
offset :: if (shape.kind == .record) shape.fields[i].byte_offset else i * storage_size(module, shape.element, shape.element_array)
|
||||
write_storage(module, cells[offset..], aggregate_item(module, value, i))
|
||||
}
|
||||
return
|
||||
}
|
||||
bits u64 := 0
|
||||
match value.type {
|
||||
.float: bits = ptrcast!(@u64, &value.data.real)^
|
||||
.bool: bits = if (value.data.boolean) 1 else 0
|
||||
.string: bits = u64(value.data.string)
|
||||
.function: bits = u64(value.data.function)
|
||||
.void: return
|
||||
else: bits = ptrcast!(@u64, &value.data.integer)^
|
||||
}
|
||||
size :: storage_size(module, value.type, value.array_type)
|
||||
for (0..size) |i| {
|
||||
byte u64 := 0
|
||||
if (i < 8) byte = (bits >> u64(i * 8)) & 255
|
||||
else byte = (u64(string_bytes(module, value.data.string).len) >> u64((i - 8) * 8)) & 255
|
||||
cells[i] = Value{ type = .u8, data = ValueData{ unsigned = byte } }
|
||||
}
|
||||
}
|
||||
|
||||
#! converts raw aggregate projections into the caller's preallocated flattened segment.
|
||||
copy_aggregate proc(module @Module, destination Value, source Value) void {
|
||||
shape :: module.arrays.items[usize(source.array_type)]
|
||||
if (!source.raw or shape.is_union) {
|
||||
memcopy!(destination.data.elements, source.data.elements)
|
||||
return
|
||||
}
|
||||
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||
for (0..count) |i| {
|
||||
child :: aggregate_item(module, source, i)
|
||||
if (aggregate(child.type)) copy_aggregate(module, aggregate_item(module, destination, i), child)
|
||||
else {
|
||||
offset :: if (shape.kind == .record) shape.fields[i].offset else i
|
||||
destination.data.elements[offset] = child
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
union_tag proc(module @Module, value Value) Value {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
return read_storage(module, shape.tag_type, no_array, value.data.elements, true)
|
||||
}
|
||||
|
||||
union_active proc(module @Module, value Value, field usize) bool {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
if (shape.tag_type == .void) return true
|
||||
tag :: union_tag(module, value)
|
||||
return tag.data.unsigned == shape.fields[field].tag.data.unsigned
|
||||
}
|
||||
|
||||
@hide:file
|
||||
|
||||
@@ -34,6 +34,11 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "or", .or },
|
||||
{ "mut", .mut },
|
||||
{ "struct", .struct },
|
||||
{ "enum", .enum },
|
||||
{ "union", .union },
|
||||
{ "distinct", .distinct },
|
||||
{ "alias", .alias },
|
||||
{ "match", .match },
|
||||
{ "import", .import },
|
||||
{ "test", .test },
|
||||
])
|
||||
@@ -85,7 +90,6 @@ scan proc(
|
||||
|
||||
if char == '#' {
|
||||
while (cursor < program.len and program[cursor] != '\n') cursor += 1
|
||||
if (cursor < program.len) cursor += 1
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -352,12 +356,9 @@ scan_ident proc(
|
||||
cursor += 1
|
||||
}
|
||||
|
||||
# don't intern keywords (already O(1) lookup via token kind)
|
||||
kind :: strmap.get(&keywords, program[start..cursor]) orelse .ident
|
||||
str_id :: if (kind == .ident)
|
||||
try strpool.intern(pool, program[start..cursor])
|
||||
else
|
||||
strpool.no_str
|
||||
# member contexts accept keywords and therefore need their interned spelling too.
|
||||
str_id :: try strpool.intern(pool, program[start..cursor])
|
||||
return ScanIdentResult{ end = cursor, kind = kind, str_id = str_id }
|
||||
}
|
||||
|
||||
|
||||
@@ -13,15 +13,16 @@ reports_invalid_input_and_keeps_scanning test {
|
||||
|
||||
try scan(&state, "if name # comment\n$1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
|
||||
|
||||
try testing.expect_equal(8, state.tokens.items.len)
|
||||
try testing.expect_equal(9, state.tokens.items.len)
|
||||
try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind)
|
||||
try testing.expect_equal(TokenKind.ident, state.tokens.items[1].kind)
|
||||
try testing.expect_equal(TokenKind.invalid, state.tokens.items[2].kind)
|
||||
try testing.expect_equal(TokenKind.newline, state.tokens.items[2].kind)
|
||||
try testing.expect_equal(TokenKind.invalid, state.tokens.items[3].kind)
|
||||
try testing.expect_equal(TokenKind.int, state.tokens.items[4].kind)
|
||||
try testing.expect_equal(TokenKind.float, state.tokens.items[5].kind)
|
||||
try testing.expect_equal(TokenKind.string, state.tokens.items[6].kind)
|
||||
try testing.expect_equal(TokenKind.eof, state.tokens.items[7].kind)
|
||||
try testing.expect_equal(TokenKind.invalid, state.tokens.items[4].kind)
|
||||
try testing.expect_equal(TokenKind.int, state.tokens.items[5].kind)
|
||||
try testing.expect_equal(TokenKind.float, state.tokens.items[6].kind)
|
||||
try testing.expect_equal(TokenKind.string, state.tokens.items[7].kind)
|
||||
try testing.expect_equal(TokenKind.eof, state.tokens.items[8].kind)
|
||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(
|
||||
"name",
|
||||
@@ -142,7 +143,7 @@ scans_array_delimiters_and_mut_keyword test {
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
reuses_identifiers_without_interning_keywords test {
|
||||
reuses_identifier_spellings test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
@@ -155,8 +156,6 @@ reuses_identifiers_without_interning_keywords test {
|
||||
try testing.expect(first != second)
|
||||
try testing.expect_equal(first, state.tokens.items[2].data.str_id)
|
||||
try testing.expect_equal(second, state.tokens.items[4].data.str_id)
|
||||
try testing.expect_equal(null, strpool.get_id(&pool, "if"))
|
||||
try testing.expect_equal(usize(2), pool.strings.items.len)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -230,8 +229,6 @@ scans_import_keywords_and_preserves_source_in_tokens_and_diagnostics test {
|
||||
try scan(&state, "import test $\n1.\n\"unfinished", &pool, &diagnostic_store)
|
||||
try testing.expect_equal(TokenKind.import, state.tokens.items[0].kind)
|
||||
try testing.expect_equal(TokenKind.test, state.tokens.items[1].kind)
|
||||
try testing.expect_equal(null, strpool.get_id(&pool, "import"))
|
||||
try testing.expect_equal(null, strpool.get_id(&pool, "test"))
|
||||
for (state.tokens.items) |token| try testing.expect_equal(state.source_id, token.span.source)
|
||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||
for (diagnostic_store.list.items) |diagnostic| {
|
||||
|
||||
@@ -20,13 +20,15 @@ TokenKind :: enum {
|
||||
# keywords
|
||||
if, else
|
||||
for, while
|
||||
match
|
||||
proc, return
|
||||
break, continue
|
||||
true, false
|
||||
and, or
|
||||
mut
|
||||
struct
|
||||
struct, enum, union
|
||||
distinct, alias
|
||||
import, test
|
||||
mut
|
||||
|
||||
# literals
|
||||
ident, string
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "@std/io"
|
||||
import "@source/ir"
|
||||
import "@source/diagnostics"
|
||||
|
||||
@hide
|
||||
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
|
||||
@@ -57,11 +58,49 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
}
|
||||
return
|
||||
}
|
||||
.union_literal: {
|
||||
shape :: state.module.arrays.items[usize(instruction.array_type)]
|
||||
try io.write_all(output, " store ")
|
||||
try write_type(output, state.module, type)
|
||||
try io.print(" zeroinitializer, ptr %v{}\n", output, {u32(instruction.destination)})
|
||||
if (shape.tag_type != .void) {
|
||||
try io.write_all(output, " store ")
|
||||
try write_type(output, state.module, Type{ kind = shape.tag_type })
|
||||
try io.write_all(output, " ")
|
||||
try write_constant(output, state.module, instruction.constant)
|
||||
try io.print(", ptr %v{}\n", output, {u32(instruction.destination)})
|
||||
}
|
||||
if (instruction.left != ir.no_value) {
|
||||
payload :: try load(state, instruction.left)
|
||||
pointer :: fresh(state)
|
||||
try io.print(" %r{} = getelementptr i8, ptr %v{}, i64 {}\n", output, {pointer, u32(instruction.destination), shape.payload_offset})
|
||||
try store_payload(state, state.values[usize(instruction.left)], payload, pointer)
|
||||
}
|
||||
return
|
||||
}
|
||||
.union_tag: {
|
||||
try io.print(" %r{} = load ", output, {result})
|
||||
try write_type(output, state.module, type)
|
||||
try io.print(", ptr %v{}\n", output, {u32(instruction.left)})
|
||||
}
|
||||
.field: {
|
||||
base :: try load(state, instruction.left)
|
||||
try io.print(" %r{} = extractvalue ", output, {result})
|
||||
try write_type(output, state.module, state.values[usize(instruction.left)])
|
||||
try io.print(" %r{}, {}\n", output, {base, instruction.slot})
|
||||
base_type :: state.values[usize(instruction.left)]
|
||||
shape :: state.module.arrays.items[usize(base_type.shape)]
|
||||
if (shape.is_union) {
|
||||
pointer :: fresh(state)
|
||||
try io.print(" %r{} = getelementptr i8, ptr %v{}, i64 0\n", output, {pointer, u32(instruction.left)})
|
||||
try check_union(state, pointer, shape, usize(instruction.slot), instruction.span)
|
||||
if (instruction.type == .void) return
|
||||
payload :: fresh(state)
|
||||
try io.print(" %r{} = getelementptr i8, ptr %r{}, i64 {}\n %r{} = load ", output, {payload, pointer, shape.payload_offset, result})
|
||||
try write_type(output, state.module, type)
|
||||
try io.print(", ptr %r{}\n", output, {payload})
|
||||
} else {
|
||||
base :: try load(state, instruction.left)
|
||||
try io.print(" %r{} = extractvalue ", output, {result})
|
||||
try write_type(output, state.module, base_type)
|
||||
try io.print(" %r{}, {}\n", output, {base, instruction.slot})
|
||||
}
|
||||
}
|
||||
.field_address: {
|
||||
base_type :: state.values[usize(instruction.left)]
|
||||
@@ -72,9 +111,19 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
address :: fresh(state)
|
||||
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {data, base})
|
||||
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, base})
|
||||
try io.print(" %r{} = getelementptr ", output, {pointer})
|
||||
try write_type(output, state.module, Type{ kind = .record, shape = base_type.shape })
|
||||
try io.print(", ptr %r{}, i64 0, i32 {}\n", output, {data, instruction.slot})
|
||||
shape :: state.module.arrays.items[usize(base_type.shape)]
|
||||
if (shape.is_union) {
|
||||
if (shape.tag_type != .void) {
|
||||
position :: location(state.line_starts, instruction.span)
|
||||
try io.print(" call void @check_init(ptr %r{}, i1 true, i64 {}, i64 {}, i64 {})\n", output, {flag, usize(instruction.span.source), position.line, position.column})
|
||||
try check_union(state, data, shape, usize(instruction.slot), instruction.span)
|
||||
}
|
||||
try io.print(" %r{} = getelementptr i8, ptr %r{}, i64 {}\n", output, {pointer, data, shape.payload_offset})
|
||||
} else {
|
||||
try io.print(" %r{} = getelementptr ", output, {pointer})
|
||||
try write_type(output, state.module, Type{ kind = .record, shape = base_type.shape })
|
||||
try io.print(", ptr %r{}, i64 0, i32 {}\n", output, {data, instruction.slot})
|
||||
}
|
||||
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
||||
try io.print(" %r{} = insertvalue %address %r{}, ptr %r{}, 1\n", output, {result, address, flag})
|
||||
}
|
||||
@@ -129,9 +178,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
try io.print(" call void @check_init(ptr %r{}, i1 {}, i64 {}, i64 {}, i64 {})\n", output, {flag, instruction.op == .load_address, usize(instruction.span.source), position.line, position.column})
|
||||
if (instruction.op == .store_address) {
|
||||
value :: try load(state, instruction.right)
|
||||
try io.write_all(output, " store ")
|
||||
try write_type(output, state.module, state.values[usize(instruction.right)])
|
||||
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
|
||||
try store_payload(state, state.values[usize(instruction.right)], value, pointer)
|
||||
return
|
||||
}
|
||||
try io.print(" %r{} = load ", output, {result})
|
||||
@@ -246,6 +293,46 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
try store(state, instruction.destination, result)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
check_union proc(state @mut State, pointer usize, shape ir.ArrayType, field usize, span diagnostics.Span) void ! io.WriteError {
|
||||
if (shape.tag_type == .void) return
|
||||
tag :: fresh(state)
|
||||
failed :: fresh(state)
|
||||
try io.print(" %r{} = load ", state.output, {tag})
|
||||
try write_type(state.output, state.module, Type{ kind = shape.tag_type })
|
||||
try io.print(", ptr %r{}\n %r{} = icmp ne ", state.output, {pointer, failed})
|
||||
try write_type(state.output, state.module, Type{ kind = shape.tag_type })
|
||||
try io.print(" %r{}, ", state.output, {tag})
|
||||
try write_constant(state.output, state.module, shape.fields[field].tag)
|
||||
try io.write_all(state.output, "\n")
|
||||
try check_failure(state, failed, .inactive_union_field, span)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
store_payload proc(state @mut State, type Type, value, pointer usize) void ! io.WriteError {
|
||||
if (type.kind == .void) return
|
||||
if (ir.aggregate(type.kind)) {
|
||||
shape :: state.module.arrays.items[usize(type.shape)]
|
||||
if (!shape.is_union) {
|
||||
count usize :: if (type.kind == .record) shape.fields.len else shape.len
|
||||
for (0..count) |i| {
|
||||
child :: if (type.kind == .record) Type{ kind = shape.fields[i].type, shape = shape.fields[i].array_type } else Type{ kind = shape.element, shape = shape.element_array }
|
||||
offset usize :: if (type.kind == .record) shape.fields[i].byte_offset else i * ir.storage_size(state.module, shape.element, shape.element_array)
|
||||
element :: fresh(state)
|
||||
address :: fresh(state)
|
||||
try io.print(" %r{} = extractvalue ", state.output, {element})
|
||||
try write_type(state.output, state.module, type)
|
||||
try io.print(" %r{}, {}\n %r{} = getelementptr i8, ptr %r{}, i64 {}\n", state.output, {value, i, address, pointer, offset})
|
||||
try store_payload(state, child, element, address)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
try io.write_all(state.output, " store ")
|
||||
try write_type(state.output, state.module, type)
|
||||
try io.print(" %r{}, ptr %r{}\n", state.output, {value, pointer})
|
||||
}
|
||||
|
||||
@hide:file
|
||||
emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.WriteError {
|
||||
output :: state.output
|
||||
|
||||
@@ -143,6 +143,11 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
||||
}
|
||||
.record: {
|
||||
shape :: module.arrays.items[usize(type.shape)]
|
||||
if (shape.is_union) {
|
||||
words usize :: divtrunc!(shape.byte_size, 8)
|
||||
try io.print("{{ [{} x i64] }}", output, {words})
|
||||
return
|
||||
}
|
||||
try io.write_all(output, "{ ")
|
||||
for (shape.fields) |field, index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
@@ -307,6 +312,10 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
|
||||
emit_record_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void ! io.WriteError {
|
||||
shape :: module.arrays.items[usize(id)]
|
||||
type :: Type{ kind = .record, shape = id }
|
||||
if (shape.is_union) {
|
||||
try emit_union_printer(output, module, id)
|
||||
return
|
||||
}
|
||||
try io.print("define internal void @print_array{}(", output, {u32(id)})
|
||||
try write_type(output, module, type)
|
||||
try io.write_all(output, " %value) {\nentry:\n call void @out(i32 1, ptr @record_open, i64 1)\n")
|
||||
@@ -321,6 +330,51 @@ emit_record_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) voi
|
||||
try io.write_all(output, " call void @out(i32 1, ptr @record_close, i64 1)\n ret void\n}\n")
|
||||
}
|
||||
|
||||
@hide:file
|
||||
emit_union_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void ! io.WriteError {
|
||||
shape :: module.arrays.items[usize(id)]
|
||||
type :: Type{ kind = .record, shape = id }
|
||||
try io.print("define internal void @print_array{}(", output, {u32(id)})
|
||||
try write_type(output, module, type)
|
||||
try io.write_all(output, " %value) {\nentry:\n %storage = alloca ")
|
||||
try write_type(output, module, type)
|
||||
try io.write_all(output, "\n store ")
|
||||
try write_type(output, module, type)
|
||||
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @record_open, i64 1)\n")
|
||||
if (shape.tag_type != .void) {
|
||||
try io.write_all(output, " %tag = load ")
|
||||
try write_type(output, module, Type{ kind = shape.tag_type })
|
||||
try io.write_all(output, ", ptr %storage\n")
|
||||
target :: if (shape.fields.len == 0) " br label %end\n" else " br label %test0\n"
|
||||
try io.write_all(output, target)
|
||||
for (shape.fields) |field, index| {
|
||||
try io.print("test{}:\n %tag_match{} = icmp eq ", output, {index, index})
|
||||
try write_type(output, module, Type{ kind = shape.tag_type })
|
||||
try io.write_all(output, " %tag, ")
|
||||
try write_constant(output, module, field.tag)
|
||||
try io.print("\n br i1 %tag_match{}, label %case{}, label %", output, {index, index})
|
||||
if (index + 1 == shape.fields.len) try io.write_all(output, "end\n")
|
||||
else try io.print("test{}\n", output, {index + 1})
|
||||
}
|
||||
} else {
|
||||
target :: if (shape.fields.len == 0) " br label %end\n" else " br label %case0\n"
|
||||
try io.write_all(output, target)
|
||||
}
|
||||
for (shape.fields) |field, index| {
|
||||
if (shape.tag_type == .void and index != 0) break
|
||||
try io.print("case{}:\n", output, {index})
|
||||
if (field.type != .void) {
|
||||
try io.print(" %p{} = getelementptr i8, ptr %storage, i64 {}\n %r{} = load ", output, {index, shape.payload_offset, index})
|
||||
try write_type(output, module, Type{ kind = field.type, shape = field.array_type })
|
||||
try io.print(", ptr %p{}\n", output, {index})
|
||||
if (field.type != .function) try print_call(output, module, Type{ kind = field.type, shape = field.array_type }, index)
|
||||
else try io.write_all(output, " call void @exit(i32 2)\n")
|
||||
}
|
||||
try io.write_all(output, " br label %end\n")
|
||||
}
|
||||
try io.write_all(output, "end:\n call void @out(i32 1, ptr @record_close, i64 1)\n ret void\n}\n")
|
||||
}
|
||||
|
||||
@hide
|
||||
fresh proc(state @mut State) usize {
|
||||
value :: state.next_register
|
||||
@@ -357,6 +411,7 @@ trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.
|
||||
symbol []u8 :: match code {
|
||||
.integer_overflow: "overflow"
|
||||
.index_out_of_bounds: "bounds"
|
||||
.inactive_union_field: "inactive_union"
|
||||
else: unreachable
|
||||
}
|
||||
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
|
||||
@@ -398,6 +453,17 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
|
||||
}
|
||||
.record: {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
if (shape.is_union) {
|
||||
words usize :: divtrunc!(shape.byte_size, 8)
|
||||
try io.print("{{ [{} x i64] [", output, {words})
|
||||
for (0..words) |index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
word :: ir.read_storage(module, .u64, ir.no_array, value.data.elements[index * 8..], true)
|
||||
try io.print("i64 {}", output, {word.data.unsigned})
|
||||
}
|
||||
try io.write_all(output, "] }")
|
||||
return
|
||||
}
|
||||
try io.write_all(output, "{ ")
|
||||
for (shape.fields) |_, index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
@@ -457,6 +523,7 @@ emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.
|
||||
}
|
||||
}
|
||||
.trap: try io.print(" call void @trap(i64 {}, i64 0, i64 0, i64 0, ptr null, i64 0)\n unreachable\n", output, {u32(terminator.diagnostic)})
|
||||
.unreachable: try io.write_all(output, " unreachable\n")
|
||||
.jump: try io.print(" br label %b{}\n", output, {u32(terminator.target)})
|
||||
.branch: {
|
||||
condition :: try load(state, terminator.value)
|
||||
|
||||
@@ -52,6 +52,7 @@ runtime ::
|
||||
`@cycle = private constant [27 x i8] c"global initialization cycle"
|
||||
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
|
||||
`@bounds = private constant [19 x i8] c"index out of bounds"
|
||||
`@inactive_union = private constant [32 x i8] c"tagged union field is not active"
|
||||
`define internal void @out(i32 %fd, ptr %bytes, i64 %len) {
|
||||
`entry:
|
||||
` br label %loop
|
||||
|
||||
+242
-23
@@ -102,10 +102,11 @@ lower_package proc(
|
||||
}
|
||||
.statement: {
|
||||
if (typecheck.type_of(checked, id).kind == .type_value) continue
|
||||
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
||||
initializer :: try add_function(&module, ast.node_span(tree, id))
|
||||
global :: u32(module.globals.items.len)
|
||||
try arraylist.append(&module.globals, ir.Global{
|
||||
type = lower_type(typecheck.type_of(checked, id)),
|
||||
type = lower_type(&state, typecheck.type_of(checked, id)),
|
||||
array_type = try array_shape(&state, typecheck.type_of(checked, id)),
|
||||
initializer = initializer,
|
||||
span = ast.node_span(tree, id),
|
||||
@@ -121,6 +122,7 @@ lower_package proc(
|
||||
match ast.decl_kind(tree, id) {
|
||||
.function: try lower_function(&state, id)
|
||||
.statement: {
|
||||
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
||||
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
||||
}
|
||||
.import, .invalid: {}
|
||||
@@ -130,7 +132,10 @@ lower_package proc(
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_type proc(type typecheck.Type) ir.Type {
|
||||
lower_type proc(state @State, type typecheck.Type) ir.Type {
|
||||
if (type.kind == .enumeration) {
|
||||
return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
|
||||
}
|
||||
return match type.kind {
|
||||
.int: .int
|
||||
.usize: .usize
|
||||
@@ -149,6 +154,7 @@ lower_type proc(type typecheck.Type) ir.Type {
|
||||
.string: .string
|
||||
.void: .void
|
||||
.function: .function
|
||||
.enumeration: unreachable
|
||||
.none, .invalid, .range, .type_value: .invalid
|
||||
}
|
||||
}
|
||||
@@ -169,7 +175,7 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
|
||||
if (descriptor.element.kind == .array or descriptor.element.kind == .record) child = try array_shape(state, descriptor.element)
|
||||
result :: try ir.add_array(
|
||||
state.module,
|
||||
lower_type(descriptor.element),
|
||||
lower_type(state, descriptor.element),
|
||||
child,
|
||||
descriptor.len,
|
||||
descriptor.mutable,
|
||||
@@ -186,12 +192,24 @@ prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.A
|
||||
defer arraylist.deinit(&fields)
|
||||
try arraylist.reserve(&fields, descriptor.fields.len)
|
||||
for (descriptor.fields) |field| {
|
||||
tag := ir.Value{}
|
||||
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
|
||||
members :: typecheck.enum_type(state.checked, descriptor.tag_type.enum_type).members
|
||||
for members |member, index| {
|
||||
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)) {
|
||||
tag = enum_value(state, descriptor.tag_type, index)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
try arraylist.append(&fields, ir.RecordField{
|
||||
type = lower_type(field.type),
|
||||
type = lower_type(state, field.type),
|
||||
array_type = try array_shape(state, field.type),
|
||||
tag = tag,
|
||||
})
|
||||
}
|
||||
shape :: try ir.add_record(state.module, fields.items)
|
||||
shape :: if (descriptor.is_union) try ir.add_union(state.module, fields.items, descriptor.tag_type.kind == .enumeration)
|
||||
else try ir.add_record(state.module, fields.items)
|
||||
state.records[usize(id)] = shape
|
||||
return shape
|
||||
}
|
||||
@@ -225,7 +243,7 @@ find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocErro
|
||||
return
|
||||
}
|
||||
declaration :: ast.function_decl(tree, id)
|
||||
result :: lower_type(typecheck.type_of(state.checked, declaration.return_type))
|
||||
result :: lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||
if (ast.function_param_count(tree, id) != 0 or result == .function) {
|
||||
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
||||
return
|
||||
@@ -308,7 +326,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
||||
state.function = usize(state.storage[usize(id)].index)
|
||||
state.block = ir.BlockId(0)
|
||||
state.terminated = false
|
||||
state.module.functions.items[state.function].result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
|
||||
state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||
for (0..ast.function_param_count(tree, id)) |i| {
|
||||
parameter :: ast.function_param(tree, id, i)
|
||||
@@ -316,7 +334,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
||||
local :: try ir.add_parameter(
|
||||
state.module,
|
||||
ir.FunctionId(state.function),
|
||||
lower_type(type),
|
||||
lower_type(state, type),
|
||||
try array_shape(state, type),
|
||||
)
|
||||
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
|
||||
@@ -344,11 +362,12 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
||||
match ast.stmt_kind(state.tree, statement) {
|
||||
.declaration: {
|
||||
if (typecheck.type_of(state.checked, statement).kind == .type_value) continue
|
||||
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, statement).initializer) == .alias_expr) continue
|
||||
type :: typecheck.type_of(state.checked, statement)
|
||||
local :: try ir.add_local(
|
||||
state.module,
|
||||
ir.FunctionId(state.function),
|
||||
lower_type(type),
|
||||
lower_type(state, type),
|
||||
try array_shape(state, type),
|
||||
)
|
||||
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
|
||||
@@ -362,7 +381,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
||||
.for_stmt: {
|
||||
loop :: ast.for_stmt(state.tree, statement)
|
||||
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
||||
type :: lower_type(typecheck.Type{ kind = family })
|
||||
type :: lower_type(state, typecheck.Type{ kind = family })
|
||||
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
|
||||
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
|
||||
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
|
||||
@@ -385,6 +404,7 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
|
||||
declaration :: ast.block_statement(state.tree, block, i)
|
||||
if (ast.stmt_kind(state.tree, declaration) != .declaration) continue
|
||||
if (typecheck.type_of(state.checked, declaration).kind == .type_value) continue
|
||||
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, declaration).initializer) == .alias_expr) continue
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .reset_local,
|
||||
slot = state.storage[usize(declaration)].index,
|
||||
@@ -498,7 +518,7 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
||||
end :: try lower_expression(state, interval.end)
|
||||
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
|
||||
slot :: state.storage[usize(id)].index
|
||||
family :: lower_type(typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
|
||||
family :: lower_type(state, typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = start, slot = slot, span = interval.span })
|
||||
origin :: state.block
|
||||
condition :: try add_block(state)
|
||||
@@ -596,7 +616,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
binding :: sema.binding(state.resolution, assignment.target)
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .assign_local,
|
||||
type = lower_type(typecheck.type_of(state.checked, assignment.target)),
|
||||
type = lower_type(state, typecheck.type_of(state.checked, assignment.target)),
|
||||
array_type = try array_shape(state, typecheck.type_of(state.checked, assignment.target)),
|
||||
left = value,
|
||||
slot = state.storage[usize(binding.data.declaration)].index,
|
||||
@@ -610,6 +630,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
.expression: _ = try lower_expression(state, ast.statement_expr(tree, id))
|
||||
.declaration: {
|
||||
if (typecheck.type_of(state.checked, id).kind == .type_value) return
|
||||
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return
|
||||
if (trap_binding(state, id)) return
|
||||
declaration :: ast.statement_decl(tree, id)
|
||||
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
||||
@@ -617,7 +638,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
if (state.terminated or trap_type(state, id)) return
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .store_local,
|
||||
type = lower_type(typecheck.type_of(state.checked, id)),
|
||||
type = lower_type(state, typecheck.type_of(state.checked, id)),
|
||||
array_type = try array_shape(state, typecheck.type_of(state.checked, id)),
|
||||
left = value,
|
||||
slot = state.storage[usize(id)].index,
|
||||
@@ -676,7 +697,186 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
enum_value proc(state @State, type typecheck.Type, index usize) ir.Value {
|
||||
descriptor :: typecheck.enum_type(state.checked, type.enum_type)
|
||||
member :: descriptor.members[index]
|
||||
runtime :: lower_type(state, type)
|
||||
if (ir.unsigned_integer(runtime)) return ir.Value{ type = runtime, data = ir.ValueData{ unsigned = member.magnitude } }
|
||||
integer := i64(0)
|
||||
if (member.negative) {
|
||||
if (member.magnitude == u64(maxval!(i64)) + 1) integer = minval!(i64)
|
||||
else integer = -i64(member.magnitude)
|
||||
} else integer = i64(member.magnitude)
|
||||
return ir.Value{ type = runtime, data = ir.ValueData{ integer = integer } }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
match_condition proc(
|
||||
state @mut State, subject ir.ValueId, pattern ast.NodeId,
|
||||
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
span :: ast.node_span(state.tree, pattern)
|
||||
if (ast.expr_kind(state.tree, pattern) == .range) {
|
||||
interval :: ast.range_expr(state.tree, pattern)
|
||||
start :: try lower_expression(state, interval.start)
|
||||
if (state.terminated) return ir.no_value
|
||||
end :: try lower_expression(state, interval.end)
|
||||
if (state.terminated) return ir.no_value
|
||||
lower_bound :: try emit(state, ir.Instruction{ op = .greater_equal, type = .bool, left = subject, right = start, span = span })
|
||||
upper_op :: if (interval.inclusive) ir.Op.less_equal else ir.Op.less
|
||||
upper :: try emit(state, ir.Instruction{ op = upper_op, type = .bool, left = subject, right = end, span = span })
|
||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = lower_bound, slot = slot, span = span })
|
||||
origin :: state.block
|
||||
check_upper :: try add_block(state)
|
||||
join :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
||||
kind = .branch, value = lower_bound, target = check_upper, alternative = join, span = span,
|
||||
}
|
||||
state.block = check_upper
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = upper, slot = slot, span = span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
state.block = join
|
||||
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
||||
}
|
||||
value :: try lower_expression(state, pattern)
|
||||
if (state.terminated) return ir.no_value
|
||||
return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = subject, right = value, span = span })
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
span :: ast.node_span(tree, id)
|
||||
view :: ast.match_expr(tree, id)
|
||||
subject :: try lower_expression(state, view.subject)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
subject_type :: typecheck.type_of(state.checked, view.subject)
|
||||
tagged :: subject_type.kind == .record
|
||||
discriminant := subject
|
||||
if (tagged) {
|
||||
tag_type :: typecheck.record_type(state.checked, subject_type.record_type).tag_type
|
||||
discriminant = try emit(state, ir.Instruction{
|
||||
op = .union_tag, type = lower_type(state, tag_type), left = subject, span = span,
|
||||
})
|
||||
}
|
||||
type :: typecheck.type_of(state.checked, id)
|
||||
runtime :: lower_type(state, type)
|
||||
shape :: try array_shape(state, type)
|
||||
result_slot := u32(0)
|
||||
if (runtime != .void) result_slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
||||
join :: try add_block(state)
|
||||
falls := false
|
||||
count :: ast.match_arm_count(tree, id)
|
||||
for 0..count |i| {
|
||||
arm :: ast.match_arm(tree, id, i)
|
||||
arm_view :: ast.match_arm_view(tree, arm)
|
||||
body :: try add_block(state)
|
||||
if (arm_view.capture) |capture| {
|
||||
capture_type :: typecheck.type_of(state.checked, capture)
|
||||
local :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, capture_type), try array_shape(state, capture_type))
|
||||
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(local) }
|
||||
}
|
||||
if (!arm_view.expression) try predeclare_locals(state, arm_view.body)
|
||||
patterns :: ast.match_pattern_count(tree, arm)
|
||||
if (arm_view.catch_all) {
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = body, span = span,
|
||||
}
|
||||
} else {
|
||||
for 0..patterns |j| {
|
||||
pattern :: ast.match_pattern(tree, arm, j)
|
||||
selected :: try add_block(state)
|
||||
final :: i + 1 == count and j + 1 == patterns
|
||||
next := join
|
||||
if (!final) {
|
||||
condition :: try match_condition(state, discriminant, pattern)
|
||||
if (state.terminated) return ir.no_value
|
||||
next = try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = condition, target = selected, alternative = next, span = ast.node_span(tree, pattern),
|
||||
}
|
||||
} else {
|
||||
# checking proves exhaustiveness; the final pattern is the remaining case.
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = selected, span = span,
|
||||
}
|
||||
}
|
||||
state.block = selected
|
||||
if (arm_view.capture) |capture| {
|
||||
capture_type :: typecheck.type_of(state.checked, capture)
|
||||
captured := subject
|
||||
if (tagged) captured = try emit(state, ir.Instruction{
|
||||
op = .field, type = lower_type(state, capture_type), array_type = try array_shape(state, capture_type),
|
||||
left = subject, slot = u32(typecheck.union_variant_index(state.checked, pattern)), span = ast.node_span(tree, pattern),
|
||||
})
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .store_local, left = captured, slot = state.storage[usize(capture)].index, span = span,
|
||||
})
|
||||
}
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = body, span = span,
|
||||
}
|
||||
state.block = next
|
||||
}
|
||||
}
|
||||
next :: state.block
|
||||
state.block = body
|
||||
state.terminated = false
|
||||
if (arm_view.expression) {
|
||||
value :: try lower_expression(state, arm_view.body)
|
||||
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
||||
op = .store_local, left = value, slot = result_slot, span = span,
|
||||
})
|
||||
} else try lower_block(state, arm_view.body, true)
|
||||
if (!state.terminated) {
|
||||
falls = true
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = join, span = span,
|
||||
}
|
||||
}
|
||||
state.block = next
|
||||
state.terminated = false
|
||||
}
|
||||
state.block = join
|
||||
state.terminated = !falls
|
||||
if (!falls) state.module.functions.items[state.function].blocks.items[usize(join)].terminator = ir.Terminator{ kind = .unreachable }
|
||||
if (!falls or runtime == .void) return ir.no_value
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .load_local, type = runtime, array_type = shape, slot = result_slot, span = span,
|
||||
})
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_union proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
type :: typecheck.type_of(state.checked, id)
|
||||
slot :: typecheck.union_variant_index(state.checked, id)
|
||||
shape :: try array_shape(state, type)
|
||||
field :: state.module.arrays.items[usize(shape)].fields[slot]
|
||||
payload := ir.no_value
|
||||
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||
view :: ast.record_literal(tree, id)
|
||||
count :: ast.record_field_count(tree, id)
|
||||
contextual := false
|
||||
if (view.type) |annotation| contextual = ast.expr_kind(tree, annotation) == .enum_literal
|
||||
if (contextual and field.type == .record and (count != 1 or ast.field(tree, ast.record_field(tree, id, 0)).name != null)) {
|
||||
payload = try lower_record_as(state, id, typecheck.union_payload_type(state.checked, id))
|
||||
} else if (count != 0) {
|
||||
payload = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, 0)).value?)
|
||||
}
|
||||
}
|
||||
if (state.terminated) return ir.no_value
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .union_literal, type = .record, array_type = shape, slot = u32(slot),
|
||||
left = payload, constant = field.tag, span = ast.node_span(tree, id),
|
||||
})
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
scratch_start :: state.arguments.items.len
|
||||
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||
@@ -689,7 +889,6 @@ lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
||||
state.arguments.items[scratch_start + i] = value
|
||||
}
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
type :: typecheck.type_of(state.checked, id)
|
||||
descriptor :: typecheck.record_type(state.checked, type.record_type)
|
||||
ordered_start :: state.arguments.items.len
|
||||
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
|
||||
@@ -758,7 +957,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .field_address,
|
||||
type = .address,
|
||||
pointee = lower_type(checked),
|
||||
pointee = lower_type(state, checked),
|
||||
array_type = try array_shape(state, checked),
|
||||
left = base,
|
||||
slot = u32(typecheck.field_index(state.checked, id)),
|
||||
@@ -774,7 +973,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .index_address,
|
||||
type = .address,
|
||||
pointee = lower_type(checked),
|
||||
pointee = lower_type(state, checked),
|
||||
array_type = try array_shape(state, checked),
|
||||
left = base,
|
||||
right = index,
|
||||
@@ -788,7 +987,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
return try emit(state, ir.Instruction{
|
||||
op = operation,
|
||||
type = .address,
|
||||
pointee = lower_type(checked),
|
||||
pointee = lower_type(state, checked),
|
||||
array_type = try array_shape(state, checked),
|
||||
slot = location.index,
|
||||
span = span,
|
||||
@@ -799,8 +998,13 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
||||
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
span :: ast.node_span(tree, id)
|
||||
type :: lower_type(typecheck.type_of(state.checked, id))
|
||||
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
||||
shape :: try array_shape(state, typecheck.type_of(state.checked, id))
|
||||
if (typecheck.is_enum_member(state.checked, id)) {
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
return try constant(state, enum_value(state, typecheck.type_of(state.checked, id), typecheck.field_index(state.checked, id)), span)
|
||||
}
|
||||
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
|
||||
match ast.expr_kind(tree, id) {
|
||||
.invalid: {
|
||||
invalid :: ast.invalid(tree, id)
|
||||
@@ -810,12 +1014,23 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
# ranges have no physical value; only lower_for decomposes an iterable.
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.array_type, .record_type: {
|
||||
.array_type, .record_type, .enum_type, .union_type, .distinct_type: {
|
||||
if (!trap_type(state, id)) {
|
||||
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
||||
}
|
||||
}
|
||||
.record_literal: return try lower_record(state, id)
|
||||
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
|
||||
.enum_literal: {
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.match_expr: return try lower_match(state, id)
|
||||
.record_literal: {
|
||||
checked :: typecheck.type_of(state.checked, id)
|
||||
if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) {
|
||||
return try lower_union(state, id)
|
||||
}
|
||||
return try lower_record_as(state, id, checked)
|
||||
}
|
||||
.array_literal: {
|
||||
scratch_start :: state.arguments.items.len
|
||||
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||
@@ -948,7 +1163,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
}
|
||||
.call: {
|
||||
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
|
||||
if (target.kind == .builtin) {
|
||||
if (target.kind == .builtin or typecheck.is_type_constructor(state.checked, id)) {
|
||||
operand := ir.no_value
|
||||
for (0..ast.call_arg_count(tree, id)) |i| {
|
||||
value :: try lower_expression(state, ast.call_arg(tree, id, i))
|
||||
@@ -956,6 +1171,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
if (i == 0) operand = value
|
||||
}
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
if (typecheck.is_type_constructor(state.checked, id)) {
|
||||
source :: lower_type(state, typecheck.type_of(state.checked, ast.call_arg(tree, id, 0)))
|
||||
if (source == type) return operand
|
||||
}
|
||||
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
|
||||
}
|
||||
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
||||
@@ -1018,7 +1237,7 @@ exact_float_integer proc(text []u8, value f64) bool {
|
||||
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
span :: ast.node_span(state.tree, id)
|
||||
text :: ast.source_text(state.tree, span)
|
||||
type :: lower_type(typecheck.type_of(state.checked, id))
|
||||
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
||||
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
||||
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
|
||||
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
||||
|
||||
@@ -132,14 +132,14 @@ executes_forward_lazy_globals_and_function_aliases test {
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`forward :: later + 1
|
||||
`alias :: add
|
||||
`forwarded :: add
|
||||
`later :: 18
|
||||
`add proc(left int, right int) int {
|
||||
` local_alias :: alias
|
||||
` local_alias :: forwarded
|
||||
` return left + right
|
||||
`}
|
||||
`main proc() int {
|
||||
` invoke :: alias
|
||||
` invoke :: forwarded
|
||||
` return invoke(forward, 23)
|
||||
`}
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
@@ -1684,7 +1684,7 @@ array_context_inference_empty_shapes_and_scalar_leaf_families test {
|
||||
"main proc() string {\n a [2]mut string := [\"one\", \"two\"]\n b := a\n a[1] = \"three\"\n return b[1]\n}\n",
|
||||
"plus proc(n int) int { return n + 1 }\nminus proc(n int) int { return n - 1 }\nmain proc() bool {\n a :: [plus, minus]\n return a[0](41) == 42 and a[1](43) == 42\n}\n",
|
||||
"make proc() [2]i64 { return [20, 22] }\nmain proc() bool {\n return make()[0] + ([20, 22])[1] == 42\n}\n",
|
||||
"count :: alias + 1\nalias :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\n}\n",
|
||||
"count :: forwarded + 1\nforwarded :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\n}\n",
|
||||
]
|
||||
for (sources) |source| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
|
||||
@@ -214,6 +214,15 @@ print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.
|
||||
opening :: if (value.type == .array) "[" else "{"
|
||||
closing :: if (value.type == .array) "]" else "}"
|
||||
try io.write_all(output, opening)
|
||||
if (shape.is_union) {
|
||||
for (shape.fields) |field, index| {
|
||||
if (!ir.union_active(module, value, index)) continue
|
||||
if (field.type != .void) try print_value(output, module, ir.aggregate_item(module, value, index))
|
||||
break
|
||||
}
|
||||
try io.write_all(output, closing)
|
||||
return
|
||||
}
|
||||
for (0..count) |index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
try print_value(output, module, ir.aggregate_item(module, value, index))
|
||||
|
||||
+289
-13
@@ -30,6 +30,7 @@ State :: struct {
|
||||
next_token usize
|
||||
paren_depth usize = 0
|
||||
allow_record_literals bool = true
|
||||
match_arm_body bool = false
|
||||
}
|
||||
|
||||
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
|
||||
@@ -156,7 +157,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
while true {
|
||||
token :: try consume(state, .ident)
|
||||
try arraylist.append(&names, token)
|
||||
if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct)) break
|
||||
if (starts_type(current(state).kind)) break
|
||||
_ = try consume(state, .comma)
|
||||
}
|
||||
|
||||
@@ -237,6 +238,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
and (
|
||||
next_n_is(state, 1, .ident)
|
||||
or next_n_is(state, 1, .struct)
|
||||
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
||||
or next_n_is(state, 1, .distinct)
|
||||
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
|
||||
or next_n_is(state, 1, .double_colon)
|
||||
or next_n_is(state, 1, .colon_equal)
|
||||
@@ -250,7 +253,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.for: return try parse_for(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct: {
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
|
||||
.enum, .union, .distinct, .alias, .dot, .match: {
|
||||
expression :: try parse_expr(state)
|
||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||
return try ast.add_expression_stmt(state.builder, expression)
|
||||
@@ -264,6 +268,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .return)
|
||||
if (
|
||||
next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .eof)
|
||||
or (state.match_arm_body and next_is(state, .comma))
|
||||
or (allow_else and next_is(state, .else))
|
||||
) {
|
||||
return try ast.add_return_stmt(state.builder, keyword.span, null)
|
||||
@@ -272,6 +277,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
if (
|
||||
!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) {
|
||||
return .unexpected_token
|
||||
}
|
||||
@@ -297,6 +303,7 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
||||
and !next_is(state, .close_curly)
|
||||
and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) return .unexpected_token
|
||||
|
||||
if (keyword.kind == .break)
|
||||
@@ -314,6 +321,7 @@ parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.
|
||||
and !next_is(state, .close_curly)
|
||||
and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) {
|
||||
return .unexpected_token
|
||||
}
|
||||
@@ -569,7 +577,7 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
@hide:file
|
||||
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
name :: try consume(state, .ident)
|
||||
type_id ?ast.NodeId :: if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct))
|
||||
type_id ?ast.NodeId :: if (starts_type(current(state).kind))
|
||||
try parse_type(state)
|
||||
else
|
||||
null
|
||||
@@ -622,11 +630,14 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
)
|
||||
}
|
||||
if next_is(state, .struct) return try parse_record_type(state)
|
||||
if next_is(state, .enum) return try parse_enum_type(state)
|
||||
if next_is(state, .union) return try parse_union_type(state)
|
||||
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
||||
token :: try consume(state, .ident)
|
||||
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
while next_is(state, .dot) {
|
||||
state.next_token += 1
|
||||
member :: try consume(state, .ident)
|
||||
member :: try consume_member(state, false)
|
||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||
type = try ast.add_selection(state.builder, ast.Span{
|
||||
start = token.span.start,
|
||||
@@ -637,6 +648,261 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
return type
|
||||
}
|
||||
|
||||
@hide:file
|
||||
starts_type proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .ident or kind == .open_bracket or kind == .struct
|
||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
consume_member proc(state @mut State, numeric bool) lexer.Token ! SyntaxError {
|
||||
token :: current(state)
|
||||
if (!member_name(token.kind) and !(numeric and token.kind == .int)) return .unexpected_token
|
||||
state.next_token += 1
|
||||
return token
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_type_wrapper proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: current(state)
|
||||
state.next_token += 1
|
||||
operand :: try parse_type(state)
|
||||
return try ast.add_type_operand(state.builder, ast.Span{
|
||||
start = keyword.span.start,
|
||||
end = ast.builder_span(state.builder, operand).end,
|
||||
source = keyword.span.source,
|
||||
}, operand, keyword.kind == .alias)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_enum_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .enum)
|
||||
backing ?ast.NodeId := null
|
||||
if next_is(state, .open_paren) {
|
||||
state.next_token += 1
|
||||
backing = try parse_type(state)
|
||||
_ = try consume(state, .close_paren)
|
||||
}
|
||||
skip_newlines(state)
|
||||
_ = try consume(state, .open_curly)
|
||||
members arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&members)
|
||||
skip_newlines(state)
|
||||
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal) or next_n_is(state, 1, .proc))
|
||||
) return try invalid_record(state, keyword.span.start, false)
|
||||
start :: state.next_token
|
||||
member :: parse_enum_member(state) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
yield try ast.add_field(state.builder, span, null, null, invalid)
|
||||
}
|
||||
try arraylist.append(&members, member)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) state.next_token += 1
|
||||
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||
skip_newlines(state)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
return try ast.add_enum_type(state.builder, ast.Span{
|
||||
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||
}, backing, members.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_enum_member proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: try consume_member(state, false)
|
||||
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
value ?ast.NodeId := null
|
||||
if next_is(state, .equal) {
|
||||
state.next_token += 1
|
||||
value = try parse_expr(state)
|
||||
}
|
||||
end :: if (value) |id| ast.builder_span(state.builder, id).end else token.span.end
|
||||
return try ast.add_field(state.builder, ast.Span{
|
||||
start = token.span.start,
|
||||
end = end,
|
||||
source = token.span.source,
|
||||
}, name, null, value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_union_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .union)
|
||||
tag ?ast.NodeId := null
|
||||
auto_tag := false
|
||||
if next_is(state, .open_paren) {
|
||||
state.next_token += 1
|
||||
if next_is(state, .enum) {
|
||||
state.next_token += 1
|
||||
auto_tag = true
|
||||
} else {
|
||||
tag = try parse_type(state)
|
||||
}
|
||||
_ = try consume(state, .close_paren)
|
||||
}
|
||||
skip_newlines(state)
|
||||
_ = try consume(state, .open_curly)
|
||||
fields arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&fields)
|
||||
skip_newlines(state)
|
||||
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal) or next_n_is(state, 1, .proc))
|
||||
) return try invalid_record(state, keyword.span.start, false)
|
||||
start :: state.next_token
|
||||
field :: parse_union_field(state, auto_tag or tag != null) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
yield try ast.add_field(state.builder, span, null, invalid, null)
|
||||
}
|
||||
try arraylist.append(&fields, field)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) state.next_token += 1
|
||||
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||
skip_newlines(state)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
return try ast.add_union_type(state.builder, ast.Span{
|
||||
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||
}, tag, auto_tag, fields.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_union_field proc(state @mut State, tagged bool) ast.NodeId ! ParseError {
|
||||
token :: if (tagged) try consume_member(state, false) else try consume(state, .ident)
|
||||
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
type :: try parse_type(state)
|
||||
return try ast.add_field(state.builder, ast.Span{
|
||||
start = token.span.start, end = ast.builder_span(state.builder, type).end, source = token.span.source,
|
||||
}, name, type, null)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_match proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .match)
|
||||
skip_newlines(state)
|
||||
subject :: try parse_guard_expr(state)
|
||||
skip_newlines(state)
|
||||
_ = try consume(state, .open_curly)
|
||||
previous_depth :: state.paren_depth
|
||||
state.paren_depth = 0
|
||||
defer state.paren_depth = previous_depth
|
||||
arms arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&arms)
|
||||
skip_newlines(state)
|
||||
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal) or next_n_is(state, 1, .proc))
|
||||
) return try invalid_record(state, keyword.span.start, false)
|
||||
start :: state.next_token
|
||||
arm :: parse_match_arm(state) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
yield try ast.add_match_arm(state.builder, span, &[], null, invalid, true, false)
|
||||
}
|
||||
try arraylist.append(&arms, arm)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) state.next_token += 1
|
||||
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||
skip_newlines(state)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
return try ast.add_match(state.builder, ast.Span{
|
||||
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||
}, subject, arms.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
start :: current(state).span
|
||||
patterns arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&patterns)
|
||||
catch_all :: next_is(state, .else)
|
||||
capture ?ast.NodeId := null
|
||||
if (catch_all) state.next_token += 1
|
||||
else {
|
||||
while true {
|
||||
try arraylist.append(&patterns, try parse_guard_expr(state))
|
||||
if (!next_is(state, .comma)) break
|
||||
state.next_token += 1
|
||||
skip_newlines(state)
|
||||
}
|
||||
if next_is(state, .pipe) {
|
||||
state.next_token += 1
|
||||
# pointer payload captures are outside the current runtime representation.
|
||||
if (next_is(state, .at) or next_is(state, .star)) return .unexpected_token
|
||||
token :: try consume(state, .ident)
|
||||
if (
|
||||
token.span.end != token.span.start + 1
|
||||
or state.builder.source[usize(token.span.start)] != '_'
|
||||
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
_ = try consume(state, .pipe)
|
||||
}
|
||||
}
|
||||
_ = try consume(state, .colon)
|
||||
skip_newlines(state)
|
||||
previous :: state.match_arm_body
|
||||
state.match_arm_body = true
|
||||
defer state.match_arm_body = previous
|
||||
statement_body :: match current(state).kind {
|
||||
.open_curly, .return, .break, .continue, .if, .while, .for: true
|
||||
else: false
|
||||
}
|
||||
if (statement_body or named_binding_starts(state)) {
|
||||
body :: try parse_arm(state, false)
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, false, catch_all)
|
||||
}
|
||||
value :: try parse_expr(state)
|
||||
if next_is(state, .equal) {
|
||||
statement :: try parse_assignment(state, value, false)
|
||||
statements [1]ast.NodeId :: [statement]
|
||||
body :: try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, false, catch_all)
|
||||
}
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, value).end, source = start.source,
|
||||
}, patterns.items, capture, value, true, catch_all)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .struct)
|
||||
@@ -711,15 +977,16 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
|
||||
name ?ast.NodeId := null
|
||||
type ?ast.NodeId := null
|
||||
value ?ast.NodeId := null
|
||||
named :: next_is(state, .ident) and (
|
||||
named :: member_name(current(state).kind) and (
|
||||
(!declaration and next_n_is(state, 1, .equal))
|
||||
or (declaration and (
|
||||
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
|
||||
or next_n_is(state, 1, .struct)
|
||||
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .enum)
|
||||
or next_n_is(state, 1, .union) or next_n_is(state, 1, .distinct)
|
||||
))
|
||||
)
|
||||
if (named) {
|
||||
token :: try consume(state, .ident)
|
||||
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
|
||||
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
}
|
||||
if (declaration) {
|
||||
@@ -836,7 +1103,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
}
|
||||
if (token.kind == .dot) {
|
||||
state.next_token += 1
|
||||
member :: try consume_either(state, &[.ident, .int])
|
||||
member :: try consume_member(state, true)
|
||||
member_id :: if (member.kind == .int)
|
||||
try ast.add_literal(state.builder, .int, member.span)
|
||||
else
|
||||
@@ -999,7 +1266,7 @@ array_starts_type proc(state @State) bool {
|
||||
if (depth == 0) {
|
||||
if (i + 1 >= state.tokens.len) return false
|
||||
next :: state.tokens[i + 1].kind
|
||||
return next == .ident or next == .mut or next == .open_bracket or next == .struct
|
||||
return starts_type(next) or next == .mut
|
||||
}
|
||||
}
|
||||
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
||||
@@ -1073,6 +1340,18 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: current(state)
|
||||
match token.kind {
|
||||
.struct: return try parse_record_type(state)
|
||||
.enum: return try parse_enum_type(state)
|
||||
.union: return try parse_union_type(state)
|
||||
.distinct, .alias: return try parse_type_wrapper(state)
|
||||
.match: return try parse_match(state)
|
||||
.dot: {
|
||||
state.next_token += 1
|
||||
member :: try consume_member(state, false)
|
||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||
return try ast.add_enum_literal(state.builder, ast.Span{
|
||||
start = token.span.start, end = member.span.end, source = token.span.source,
|
||||
}, member_id)
|
||||
}
|
||||
.open_curly: {
|
||||
if (state.allow_record_literals) return try parse_record_literal(state, null, token.span.start)
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
@@ -1126,10 +1405,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
}
|
||||
else: {
|
||||
diagnostic :: if (token.data.diagnostic != diagnostics.no_diagnostic)
|
||||
token.data.diagnostic
|
||||
else
|
||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
|
||||
if !is_expression_recovery_boundary(token.kind) {
|
||||
state.next_token += 1
|
||||
|
||||
@@ -940,7 +940,6 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
|
||||
try testing.expect_equal(6, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
||||
@@ -952,6 +951,11 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator)
|
||||
second_and :: ast.binary_expr(&tree, second_or.left)
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right))
|
||||
missing :: ast.invalid(&tree, second_and.right)
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, missing.diagnostic)?
|
||||
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
|
||||
try testing.expect_equal(missing.span.start, diagnostic.span.start)
|
||||
try testing.expect_equal(missing.span.end, diagnostic.span.end)
|
||||
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right))
|
||||
third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2))
|
||||
try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer))
|
||||
@@ -1036,13 +1040,17 @@ rejects_control_tails_and_preserves_following_declarations test {
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(9, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(6, ast.package_decl_count(&tree))
|
||||
for (usize(0)..usize(2)) |index| try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, index)))
|
||||
for (usize(2)..usize(4)) |index| {
|
||||
declaration :: ast.package_decl(&tree, index)
|
||||
try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, declaration))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.statement_decl(&tree, declaration).initializer))
|
||||
invalid :: ast.invalid(&tree, ast.statement_decl(&tree, declaration).initializer)
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, invalid.diagnostic)?
|
||||
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
|
||||
try testing.expect_equal(invalid.span.start, diagnostic.span.start)
|
||||
try testing.expect_equal(invalid.span.end, diagnostic.span.end)
|
||||
}
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
||||
try testing.expect_equal(2, ast.block_statement_count(&tree, body))
|
||||
@@ -1588,3 +1596,117 @@ rolls_back_shared_nodes_extra_spans_and_diagnostics_on_allocation_failure test {
|
||||
}
|
||||
try testing.expect(failures > 0)
|
||||
}
|
||||
|
||||
parses_enum_union_wrappers_and_keyword_variants test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Tag :: enum(i32) { if = -2, else = 3 # preserve the member boundary
|
||||
` return
|
||||
`}
|
||||
`Payload :: union(Tag) { if i32, else void, return struct { value i32 } }
|
||||
`Auto :: union(enum) { value i32, empty void }
|
||||
`Raw :: union { integer i32, real f64 }
|
||||
`Id :: distinct i32
|
||||
`Other :: alias Id
|
||||
`value Payload :: Payload{ if = 4 }
|
||||
`context Payload :: .return{value = 5}
|
||||
`empty Payload :: .else
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(9, ast.package_decl_count(&tree))
|
||||
enumeration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
try testing.expect(ast.enum_type(&tree, enumeration).backing != null)
|
||||
try testing.expect_equal(3, ast.enum_member_count(&tree, enumeration))
|
||||
member :: ast.field(&tree, ast.enum_member(&tree, enumeration, 0))
|
||||
try testing.expect_equal("if", strpool.get_str(&strings, ast.name_id(&tree, member.name?))?)
|
||||
try testing.expect_equal(ast.ExprKind.unary, ast.expr_kind(&tree, member.value?))
|
||||
tagged :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
|
||||
automatic :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer
|
||||
raw :: ast.statement_decl(&tree, ast.package_decl(&tree, 3)).initializer
|
||||
try testing.expect(ast.union_type(&tree, tagged).tag != null)
|
||||
try testing.expect(ast.union_type(&tree, automatic).auto_tag)
|
||||
try testing.expect(!ast.union_type(&tree, raw).auto_tag)
|
||||
try testing.expect_equal(2, ast.record_field_count(&tree, raw))
|
||||
distinct_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 4)).initializer
|
||||
alias_value :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.distinct_type, ast.expr_kind(&tree, distinct_type))
|
||||
try testing.expect_equal(ast.ExprKind.alias_expr, ast.expr_kind(&tree, alias_value))
|
||||
contextual :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
|
||||
variant :: ast.record_literal(&tree, contextual).type?
|
||||
try testing.expect_equal(ast.ExprKind.enum_literal, ast.expr_kind(&tree, variant))
|
||||
try testing.expect_equal("return", strpool.get_str(&strings, ast.name_id(&tree, ast.enum_literal(&tree, variant)))?)
|
||||
}
|
||||
|
||||
parses_match_patterns_payload_captures_and_statement_arms test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`choose proc(value int) int {
|
||||
` return match value {
|
||||
` 0, 1..=3: 10,
|
||||
` 4: 20 # preserve the arm boundary
|
||||
` else: 30
|
||||
` }
|
||||
`}
|
||||
`use proc(value int) void {
|
||||
` match value {
|
||||
` .if, .return |payload|: { local :: payload }
|
||||
` .else: return,
|
||||
` else: {}
|
||||
` }
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
choose :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
value_match :: ast.return_stmt(&tree, ast.block_statement(&tree, choose.body, 0)).value?
|
||||
try testing.expect_equal(3, ast.match_arm_count(&tree, value_match))
|
||||
first :: ast.match_arm(&tree, value_match, 0)
|
||||
try testing.expect_equal(2, ast.match_pattern_count(&tree, first))
|
||||
try testing.expect_equal(ast.ExprKind.range, ast.expr_kind(&tree, ast.match_pattern(&tree, first, 1)))
|
||||
try testing.expect(ast.match_arm_view(&tree, first).expression)
|
||||
use :: ast.function_decl(&tree, ast.package_decl(&tree, 1))
|
||||
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, use.body, 0))
|
||||
captured :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
|
||||
try testing.expect(captured.capture != null)
|
||||
try testing.expect(!captured.expression)
|
||||
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 2)).catch_all)
|
||||
}
|
||||
|
||||
rejects_pointer_match_captures_and_preserves_siblings test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`broken :: enum { first
|
||||
`after :: 1
|
||||
`use proc(value int) void {
|
||||
` match value {
|
||||
` .item |@payload|: payload
|
||||
` else: {}
|
||||
` }
|
||||
`}
|
||||
`last :: 2
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||
try testing.expect_equal(4, ast.package_decl_count(&tree))
|
||||
try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
|
||||
try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name))
|
||||
}
|
||||
|
||||
+1091
-51
File diff suppressed because it is too large
Load Diff
@@ -69,8 +69,8 @@ propagates_numeric_context_before_defaulting_forward_and_mutable_aliases test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`forward float :: -alias * 2 + seed
|
||||
`alias :: seed
|
||||
`forward float :: -forwarded * 2 + seed
|
||||
`forwarded :: seed
|
||||
`seed := 3
|
||||
`backward :: 4
|
||||
`demand float :: backward
|
||||
@@ -81,7 +81,7 @@ propagates_numeric_context_before_defaulting_forward_and_mutable_aliases test {
|
||||
`text :: "honey"
|
||||
`fixed int :: 7
|
||||
`anchored :: fixed + 2
|
||||
`nested float :: quotient / (alias + 1)
|
||||
`nested float :: quotient / (forwarded + 1)
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
@@ -192,9 +192,9 @@ checks_forward_alias_calls_grouped_parameters_and_mutual_recursion test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`alias :: alpha
|
||||
`forwarded :: alpha
|
||||
`alpha proc(a, b int) void {
|
||||
` (alias)(a, b)
|
||||
` (forwarded)(a, b)
|
||||
` beta(a + b, seed)
|
||||
`}
|
||||
`beta proc(total int, amount float) void {
|
||||
@@ -203,7 +203,7 @@ checks_forward_alias_calls_grouped_parameters_and_mutual_recursion test {
|
||||
`seed := 3
|
||||
`main proc() void {
|
||||
` beta(1, 2)
|
||||
` (alias)(3, 4)
|
||||
` (forwarded)(3, 4)
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
@@ -359,8 +359,8 @@ reports_nonvoid_fallthrough_without_poisoning_signatures_and_checks_unused_bodie
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`answer :: alias()
|
||||
`alias :: make
|
||||
`answer :: forwarded()
|
||||
`forwarded :: make
|
||||
`make proc() int {}
|
||||
`unused proc() float {}
|
||||
`broken proc() void {
|
||||
@@ -762,13 +762,13 @@ checks_bool_comparisons_recursive_returns_and_nested_numeric_context test {
|
||||
`flag bool :: true
|
||||
`same :: flag == false
|
||||
`different :: true != flag
|
||||
`less :: alias < 4.0
|
||||
`less :: forwarded < 4.0
|
||||
`less_equal :: seed <= 5
|
||||
`greater :: 6 > seed
|
||||
`greater_equal :: seed >= 1
|
||||
`equal :: seed == 3
|
||||
`not_equal :: seed != 2
|
||||
`alias :: seed
|
||||
`forwarded :: seed
|
||||
`seed := 3
|
||||
`identity proc(value bool) bool { return value }
|
||||
`factorial proc(n int) int {
|
||||
@@ -1789,11 +1789,11 @@ range_checks_both_bounds_and_preserves_fixed_declarations_and_source_errors test
|
||||
source ::
|
||||
`real float :: 2
|
||||
`flag :: true
|
||||
`alias :: flag
|
||||
`forwarded :: flag
|
||||
`nothing proc() void {}
|
||||
`main proc() void {
|
||||
` for 1.5..2.5 |_| {}
|
||||
` for alias.."text" |_| {}
|
||||
` for forwarded.."text" |_| {}
|
||||
` for nothing..nothing() |_| {}
|
||||
` for real..3 |_| {}
|
||||
` for absent_start..absent_end |_| {}
|
||||
@@ -2206,8 +2206,8 @@ expect_array proc(result @Result, id ast.NodeId, len usize, mutable bool, elemen
|
||||
|
||||
fixed_arrays_contextualize_forward_aliases_counts_nested_literals_and_signatures test {
|
||||
source ::
|
||||
`context [2]f64 :: alias
|
||||
`alias :: seed
|
||||
`context [2]f64 :: forwarded
|
||||
`forwarded :: seed
|
||||
`seed := [1, 2]
|
||||
`count :: later * 2 - 1
|
||||
`later :: 2
|
||||
|
||||
+91
-1
@@ -1,6 +1,7 @@
|
||||
import "@std/debug"
|
||||
import "@std/mem"
|
||||
import "@std/hashmap"
|
||||
import "@std/arraylist"
|
||||
import "@source/strpool"
|
||||
|
||||
import "@source/ast"
|
||||
@@ -99,6 +100,8 @@ State :: struct {
|
||||
builtin_ids []strpool.StringId
|
||||
allocator mem.Allocator
|
||||
namespaces hashmap.AutoHashMap(ast.NodeId, Scope)
|
||||
aliases []mut ast.NodeId
|
||||
selections arraylist.ArrayList(ast.NodeId)
|
||||
}
|
||||
|
||||
#! borrows `tree`, its identifier `pool`, and `diagnostic_store` during resolution.
|
||||
@@ -138,6 +141,9 @@ resolve_packages proc(
|
||||
errdefer mem.free(allocator, bindings)
|
||||
mem.set(bindings, Binding{})
|
||||
builtin_ids :: try intern_builtins(pool, &builtins)
|
||||
aliases :: try mem.alloc(ast.NodeId, allocator, ast.node_count(tree))
|
||||
defer mem.free(allocator, aliases)
|
||||
mem.set(aliases, maxval!(ast.NodeId))
|
||||
|
||||
state := State{
|
||||
tree = tree,
|
||||
@@ -146,7 +152,10 @@ resolve_packages proc(
|
||||
builtin_ids = builtin_ids[..],
|
||||
allocator = allocator,
|
||||
namespaces = hashmap.init(allocator),
|
||||
aliases = aliases,
|
||||
selections = arraylist.init(allocator),
|
||||
}
|
||||
defer arraylist.deinit(&state.selections)
|
||||
defer {
|
||||
for (state.namespaces.entries) |entry| {
|
||||
if (entry.hash == 0) continue
|
||||
@@ -184,6 +193,7 @@ resolve_packages proc(
|
||||
}
|
||||
for (imports) |imported| try resolve_import(&state, &file_scopes[usize(ast.node_span(tree, imported.node).source)], imported)
|
||||
for (roots) |root| try resolve_package(&state, root, file_scopes)
|
||||
try canonicalize_aliases(&state)
|
||||
|
||||
return Resolution{ storage = Storage{
|
||||
bindings = bindings,
|
||||
@@ -402,10 +412,62 @@ resolve_statement_decl proc(
|
||||
id ast.NodeId,
|
||||
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
declaration :: ast.statement_decl(state.tree, id)
|
||||
if (ast.expr_kind(state.tree, declaration.initializer) == .alias_expr) {
|
||||
state.aliases[usize(id)] = ast.type_operand(state.tree, declaration.initializer)
|
||||
}
|
||||
if (declaration.type) |type| try resolve_expr(state, scope, type)
|
||||
try resolve_expr(state, scope, declaration.initializer)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
canonical_binding proc(state @State, original Binding) Binding {
|
||||
value := original
|
||||
for (0..(state.aliases.len)) |_| {
|
||||
if (value.kind != .declaration) break
|
||||
operand :: state.aliases[usize(value.data.declaration)]
|
||||
if (operand == maxval!(ast.NodeId)) break
|
||||
target :: state.bindings[usize(operand)]
|
||||
if (target.kind == .none) break
|
||||
if (target.kind == .declaration and target.data.declaration == value.data.declaration) break
|
||||
value = target
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@hide:file
|
||||
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
# aliases may forward-reference other aliases or imported namespace members.
|
||||
for (0..(state.aliases.len)) |_| {
|
||||
changed := false
|
||||
for (state.bindings) |@entry| {
|
||||
value :: canonical_binding(state, entry^)
|
||||
if (value.kind != entry.kind) changed = true
|
||||
else if (value.kind == .declaration and value.data.declaration != entry.data.declaration) changed = true
|
||||
entry^ = value
|
||||
}
|
||||
for (state.selections.items) |id| {
|
||||
expression :: ast.selection_expr(state.tree, id)
|
||||
base :: state.bindings[usize(expression.base)]
|
||||
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
|
||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
||||
found :: if (ast.expr_kind(state.tree, expression.member) == .identifier)
|
||||
hashmap.get(&namespace.names, ast.name_id(state.tree, expression.member))
|
||||
else
|
||||
null
|
||||
value :: if (found) |member| canonical_binding(state, member) else Binding{
|
||||
kind = .invalid,
|
||||
data = BindingData{ diagnostic = try diagnostics.add(
|
||||
state.diagnostics, .unknown_name, ast.node_span(state.tree, expression.member),
|
||||
) },
|
||||
}
|
||||
state.bindings[usize(id)] = value
|
||||
state.bindings[usize(expression.member)] = value
|
||||
changed = true
|
||||
}
|
||||
if (!changed) break
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
resolve_invalid proc(state @mut State, id ast.NodeId) void {
|
||||
if (state.bindings[usize(id)].kind != .none) return
|
||||
@@ -426,6 +488,30 @@ resolve_expr proc(
|
||||
tree :: state.tree
|
||||
match ast.expr_kind(tree, id) {
|
||||
.literal_int, .literal_float, .literal_bool, .literal_string: {}
|
||||
.enum_literal: {}
|
||||
.distinct_type, .alias_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||
.enum_type: {
|
||||
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
|
||||
for (0..ast.enum_member_count(tree, id)) |i| {
|
||||
member :: ast.field(tree, ast.enum_member(tree, id, i))
|
||||
if (member.value) |value| try resolve_expr(state, scope, value)
|
||||
}
|
||||
}
|
||||
.match_expr: {
|
||||
try resolve_expr(state, scope, ast.match_expr(tree, id).subject)
|
||||
for (0..ast.match_arm_count(tree, id)) |i| {
|
||||
arm_id :: ast.match_arm(tree, id, i)
|
||||
arm :: ast.match_arm_view(tree, arm_id)
|
||||
for (0..ast.match_pattern_count(tree, arm_id)) |j| {
|
||||
try resolve_expr(state, scope, ast.match_pattern(tree, arm_id, j))
|
||||
}
|
||||
arm_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
||||
defer hashmap.deinit(&arm_scope.names)
|
||||
if (arm.capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
||||
if (arm.expression) try resolve_expr(state, &arm_scope, arm.body)
|
||||
else try resolve_block(state, &arm_scope, arm.body)
|
||||
}
|
||||
}
|
||||
.identifier: {
|
||||
span :: ast.node_span(tree, id)
|
||||
name :: ast.name_id(tree, id)
|
||||
@@ -441,6 +527,7 @@ resolve_expr proc(
|
||||
}
|
||||
.selection: {
|
||||
expression :: ast.selection_expr(tree, id)
|
||||
try arraylist.append(&state.selections, id)
|
||||
try resolve_expr(state, scope, expression.base)
|
||||
base :: state.bindings[usize(expression.base)]
|
||||
value := Binding{}
|
||||
@@ -494,10 +581,13 @@ resolve_expr proc(
|
||||
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
||||
}
|
||||
}
|
||||
.record_type, .record_literal: {
|
||||
.record_type, .record_literal, .union_type: {
|
||||
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
||||
}
|
||||
if (ast.expr_kind(tree, id) == .union_type) {
|
||||
if (ast.union_type(tree, id).tag) |tag| try resolve_expr(state, scope, tag)
|
||||
}
|
||||
for (0..ast.record_field_count(tree, id)) |i| {
|
||||
field :: ast.field(tree, ast.record_field(tree, id, i))
|
||||
if (field.type) |type| try resolve_expr(state, scope, type)
|
||||
|
||||
@@ -809,3 +809,126 @@ resolves_interleaved_compilations_with_independent_identifier_orders test {
|
||||
try testing.expect_equal(usize(0), diagnostics.count(&first_diagnostics))
|
||||
try testing.expect_equal(usize(0), diagnostics.count(&second_diagnostics))
|
||||
}
|
||||
|
||||
resolves_match_capture_scopes_without_looking_up_member_names test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Tag :: enum { if, else }
|
||||
`Value :: union(Tag) { if i32, else void }
|
||||
`inspect proc(value Value) void {
|
||||
` match value {
|
||||
` .if |payload|: { local :: payload }
|
||||
` .else: { local :: 1 }
|
||||
` }
|
||||
` payload
|
||||
` local
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &pool, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
resolution := try resolve(mem.c_allocator, &tree, &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
|
||||
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
|
||||
first :: ast.match_arm(&tree, matched, 0)
|
||||
arm :: ast.match_arm_view(&tree, first)
|
||||
local :: ast.statement_decl(&tree, ast.block_statement(&tree, arm.body, 0))
|
||||
try testing.expect_equal(arm.capture?, binding(&resolution, local.initializer).data.declaration)
|
||||
member :: ast.enum_literal(&tree, ast.match_pattern(&tree, first, 0))
|
||||
try testing.expect_equal(BindingKind.none, binding(&resolution, member).kind)
|
||||
for (usize(1)..usize(3)) |index| {
|
||||
reference :: ast.statement_expr(&tree, ast.block_statement(&tree, body, index))
|
||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, reference).kind)
|
||||
}
|
||||
}
|
||||
|
||||
canonicalizes_forward_function_global_and_builtin_aliases test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`invoke :: alias call
|
||||
`copy :: alias storage
|
||||
`Number :: alias i32
|
||||
`call :: alias original
|
||||
`original proc() i32 { return storage }
|
||||
`storage i32 := 1
|
||||
`main proc() Number {
|
||||
` copy = invoke()
|
||||
` return copy
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &pool, &diagnostic_store)
|
||||
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
resolution := try resolve(mem.c_allocator, &tree, &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
original :: ast.package_decl(&tree, 4)
|
||||
storage :: ast.package_decl(&tree, 5)
|
||||
main :: ast.function_decl(&tree, ast.package_decl(&tree, 6))
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, main.body, 0))
|
||||
try testing.expect_equal(storage, binding(&resolution, assignment.target).data.declaration)
|
||||
try testing.expect_equal(original, binding(&resolution, ast.call_expr(&tree, assignment.value).callee).data.declaration)
|
||||
try testing.expect_equal(BindingKind.builtin, binding(&resolution, main.return_type).kind)
|
||||
try testing.expect_equal(BuiltinType.i32, binding(&resolution, main.return_type).data.builtin)
|
||||
try testing.expect_equal(original, binding(&resolution, ast.package_decl(&tree, 0)).data.declaration)
|
||||
}
|
||||
|
||||
canonicalizes_imported_aliases_and_namespace_alias_selections test {
|
||||
pool := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&pool)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
builder := ast.init_builder(mem.c_allocator, "")
|
||||
defer ast.deinit_builder(&builder)
|
||||
library_source :: "original proc() i32 { return storage }\nstorage i32 := 1\nexported :: alias original\n"
|
||||
library_id :: try ast.set_source(&builder, "library.hon", library_source)
|
||||
library_scan := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&library_scan)
|
||||
library_scan.source_id = library_id
|
||||
try lexer.scan(&library_scan, library_source, &pool, &diagnostic_store)
|
||||
library :: try parser.parse_source(&builder, library_scan.tokens.items, &diagnostic_store)
|
||||
main_source ::
|
||||
`lib :: import "library"
|
||||
`invoke :: alias namespace.exported
|
||||
`copy :: alias namespace.storage
|
||||
`namespace :: alias lib
|
||||
`main proc() i32 { copy = invoke()
|
||||
` return copy
|
||||
`}
|
||||
main_id :: try ast.set_source(&builder, "main.hon", main_source)
|
||||
main_scan := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&main_scan)
|
||||
main_scan.source_id = main_id
|
||||
try lexer.scan(&main_scan, main_source, &pool, &diagnostic_store)
|
||||
root :: try parser.parse_source(&builder, main_scan.tokens.items, &diagnostic_store)
|
||||
tree := ast.finish(&builder, root)
|
||||
defer ast.deinit(&tree)
|
||||
roots [2]ast.NodeId :: [root, library]
|
||||
imports [1]Import :: [Import{
|
||||
node = ast.package_decl_at(&tree, root, 0),
|
||||
name = strpool.get_id(&pool, "lib")?,
|
||||
target = library,
|
||||
}]
|
||||
resolution := try resolve_packages(mem.c_allocator, &tree, roots[..], imports[..], &pool, &diagnostic_store)
|
||||
defer deinit(&resolution)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
original :: ast.package_decl_at(&tree, library, 0)
|
||||
storage :: ast.package_decl_at(&tree, library, 1)
|
||||
main :: ast.function_decl(&tree, ast.package_decl_at(&tree, root, 4))
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, main.body, 0))
|
||||
try testing.expect_equal(storage, binding(&resolution, assignment.target).data.declaration)
|
||||
try testing.expect_equal(original, binding(&resolution, ast.call_expr(&tree, assignment.value).callee).data.declaration)
|
||||
namespace :: ast.package_decl_at(&tree, root, 3)
|
||||
try testing.expect_equal(BindingKind.namespace, binding(&resolution, namespace).kind)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user