feat: add records and tuples across execution paths
This commit is contained in:
+92
-2
@@ -1,9 +1,9 @@
|
||||
#! grammar:
|
||||
#! * type ..... -> identifier | '[' (expr | '_') ']' ['mut'] type
|
||||
#! * type ..... -> identifier {'.' identifier} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' [field {(',' | newline) field}] '}'
|
||||
#! * binding ... -> :: | :=
|
||||
#! * literal .. -> int | float | string | 'true' | 'false'
|
||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||
#! * expr ..... -> identifier | literal | type | '[' [expr {',' expr} [',']] ']' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' identifier | expr ('..' | '..=') expr
|
||||
#! * expr ..... -> identifier | literal | type | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (identifier | int) | expr ('..' | '..=') expr
|
||||
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
|
||||
#! * block .... -> '{' {stmt} '}'
|
||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||
@@ -43,6 +43,8 @@ ExprKind :: enum {
|
||||
range
|
||||
array_type
|
||||
array_literal
|
||||
record_type
|
||||
record_literal
|
||||
index
|
||||
selection
|
||||
invalid
|
||||
@@ -96,6 +98,9 @@ NodeKind :: enum {
|
||||
expr_range
|
||||
expr_array_type
|
||||
expr_array_literal
|
||||
expr_record_type
|
||||
expr_record_literal
|
||||
record_field
|
||||
expr_index
|
||||
expr_selection
|
||||
stmt_expr
|
||||
@@ -211,6 +216,18 @@ ArrayTypeView :: struct {
|
||||
mutable bool
|
||||
}
|
||||
|
||||
RecordLiteralView :: struct {
|
||||
span Span
|
||||
type ?NodeId
|
||||
}
|
||||
|
||||
FieldView :: struct {
|
||||
span Span
|
||||
name ?NodeId
|
||||
type ?NodeId
|
||||
value ?NodeId
|
||||
}
|
||||
|
||||
IndexView :: struct {
|
||||
span Span
|
||||
base NodeId
|
||||
@@ -450,6 +467,44 @@ add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeI
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_record_type proc(builder @mut Builder, span Span, fields []NodeId) NodeId ! mem.AllocError {
|
||||
start :: builder.storage.extra.items.len
|
||||
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_record_type,
|
||||
data0 = NodeData{ extra = u32(start) },
|
||||
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_record_literal proc(
|
||||
builder @mut Builder, span Span, type ?NodeId, fields []NodeId,
|
||||
) NodeId ! mem.AllocError {
|
||||
start :: builder.storage.extra.items.len
|
||||
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
|
||||
raw_type :: if (type) |id| id else no_node
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_record_literal,
|
||||
main = NodeData{ node_id = raw_type },
|
||||
data0 = NodeData{ extra = u32(start) },
|
||||
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_field proc(
|
||||
builder @mut Builder, span Span, name, type, value ?NodeId,
|
||||
) NodeId ! mem.AllocError {
|
||||
raw_name :: if (name) |id| id else no_node
|
||||
raw_type :: if (type) |id| id else no_node
|
||||
raw_value :: if (value) |id| id else no_node
|
||||
return try add_node(builder, Node{
|
||||
kind = .record_field,
|
||||
main = NodeData{ node_id = raw_name },
|
||||
data0 = NodeData{ node_id = raw_type },
|
||||
data1 = NodeData{ node_id = raw_value },
|
||||
}, span)
|
||||
}
|
||||
|
||||
add_index proc(builder @mut Builder, span Span, base, index NodeId) NodeId ! mem.AllocError {
|
||||
return try add_node(builder, Node{
|
||||
kind = .expr_index,
|
||||
@@ -922,6 +977,8 @@ expression_kind proc(kind NodeKind) ExprKind {
|
||||
.expr_range: .range
|
||||
.expr_array_type: .array_type
|
||||
.expr_array_literal: .array_literal
|
||||
.expr_record_type: .record_type
|
||||
.expr_record_literal: .record_literal
|
||||
.expr_index: .index
|
||||
.expr_selection: .selection
|
||||
.invalid_expr: .invalid
|
||||
@@ -1007,6 +1064,39 @@ array_element proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||
}
|
||||
|
||||
record_literal proc(tree @Tree, id NodeId) RecordLiteralView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_record_literal)
|
||||
return RecordLiteralView{
|
||||
span = get_span(&tree.storage, id),
|
||||
type = maybe_node_id(u32(node.main.node_id)),
|
||||
}
|
||||
}
|
||||
|
||||
field proc(tree @Tree, id NodeId) FieldView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .record_field)
|
||||
return FieldView{
|
||||
span = get_span(&tree.storage, id),
|
||||
name = maybe_node_id(u32(node.main.node_id)),
|
||||
type = maybe_node_id(u32(node.data0.node_id)),
|
||||
value = maybe_node_id(u32(node.data1.node_id)),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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(index < usize(node.data1.extra - node.data0.extra))
|
||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||
}
|
||||
|
||||
index_expr proc(tree @Tree, id NodeId) IndexView {
|
||||
node :: get_node(&tree.storage, id)
|
||||
debug.assert(node.kind == .expr_index)
|
||||
|
||||
@@ -54,6 +54,13 @@ Code :: enum {
|
||||
not_indexable
|
||||
invalid_index_assignment
|
||||
index_out_of_bounds
|
||||
unknown_field
|
||||
duplicate_field
|
||||
missing_field
|
||||
tuple_arity_mismatch
|
||||
not_record
|
||||
recursive_record
|
||||
invalid_record_field_type
|
||||
missing_entry
|
||||
invalid_entry
|
||||
literal_out_of_range
|
||||
@@ -144,6 +151,13 @@ messages :: $enummap.init(Code, []u8, {
|
||||
not_indexable = "indexing is currently implemented for fixed arrays only",
|
||||
invalid_index_assignment = "indexed assignment requires a writable array element",
|
||||
index_out_of_bounds = "index out of bounds",
|
||||
unknown_field = "record has no such field",
|
||||
duplicate_field = "record field is specified more than once",
|
||||
missing_field = "record initializer is missing a required field",
|
||||
tuple_arity_mismatch = "tuple initializer arity does not match its type",
|
||||
not_record = "field selection requires a record or tuple",
|
||||
recursive_record = "record has a recursive by-value layout",
|
||||
invalid_record_field_type = "record fields require a concrete value type",
|
||||
missing_entry = "program requires a main function",
|
||||
invalid_entry = "main must be a function with no parameters",
|
||||
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
||||
|
||||
@@ -11,7 +11,7 @@ Outcome :: struct {
|
||||
}
|
||||
|
||||
#! release an escaped aggregate with its retained allocator; scalar and trap outcomes need no allocation.
|
||||
#! strings and array descriptors borrow the module, which must outlive this outcome.
|
||||
#! strings and aggregate descriptors borrow the module, which must outlive this outcome.
|
||||
deinit proc(outcome @mut Outcome) void {
|
||||
if (outcome.owned_elements) |elements| mem.free(outcome.allocator?, elements)
|
||||
outcome.owned_elements = null
|
||||
@@ -57,7 +57,7 @@ Frame :: struct {
|
||||
}
|
||||
|
||||
#! executes typed IR with invocation/global storage released on every exit.
|
||||
#! array results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
|
||||
#! aggregate results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
|
||||
run proc(
|
||||
allocator mem.Allocator,
|
||||
module @ir.Module,
|
||||
@@ -75,7 +75,7 @@ run proc(
|
||||
mem.set(globals, GlobalState{})
|
||||
global_cells usize := 0
|
||||
for (module.globals.items) |global| {
|
||||
if (global.type != .array) continue
|
||||
if (!ir.aggregate(global.type)) continue
|
||||
cells :: module.arrays.items[usize(global.array_type)].cells
|
||||
if (cells > maxval!(usize) - global_cells) return .storage_limit_reached
|
||||
global_cells += cells
|
||||
@@ -84,9 +84,9 @@ run proc(
|
||||
defer mem.free(allocator, aggregate_globals)
|
||||
global_offset usize := 0
|
||||
for (module.globals.items) |global, i| {
|
||||
if (global.type != .array) continue
|
||||
if (!ir.aggregate(global.type)) continue
|
||||
cells :: module.arrays.items[usize(global.array_type)].cells
|
||||
globals[i].value = array_view(global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
||||
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
||||
global_offset += cells
|
||||
}
|
||||
frames := arraylist.init(Frame, allocator)
|
||||
@@ -125,7 +125,7 @@ run proc(
|
||||
globals[global_index].status = .ready
|
||||
}
|
||||
if (frames.items.len == 1) {
|
||||
if (value.type == .array) {
|
||||
if (ir.aggregate(value.type)) {
|
||||
escaped :: try mem.alloc(ir.Value, allocator, value.data.elements.len)
|
||||
memcopy!(escaped, value.data.elements)
|
||||
value.data.elements = escaped
|
||||
@@ -204,13 +204,13 @@ run proc(
|
||||
}
|
||||
continue
|
||||
}
|
||||
.array_literal: {
|
||||
.array_literal, .record_literal: {
|
||||
destination :: &frames.items[frame_index].values[usize(instruction.destination)]
|
||||
offset usize := 0
|
||||
for (0..instruction.arguments_count) |i| {
|
||||
operand :: block.arguments.items[instruction.arguments_start + i]
|
||||
element :: frames.items[frame_index].values[usize(operand)]
|
||||
if (element.type == .array) {
|
||||
if (ir.aggregate(element.type)) {
|
||||
count :: element.data.elements.len
|
||||
memcopy!(destination.data.elements[offset..offset + count], element.data.elements)
|
||||
offset += count
|
||||
@@ -228,7 +228,11 @@ run proc(
|
||||
if (index >= u64(shape.len)) {
|
||||
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||
}
|
||||
value = indexed_value(module, base.array_type, base.data.elements, usize(index))
|
||||
value = ir.aggregate_item(module, base, usize(index))
|
||||
}
|
||||
.field: {
|
||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
value = ir.aggregate_item(module, base, usize(instruction.slot))
|
||||
}
|
||||
.address_local: {
|
||||
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||
@@ -238,7 +242,7 @@ run proc(
|
||||
cells = &[],
|
||||
initialized = &local.initialized,
|
||||
}
|
||||
if (address.type == .array) address.cells = local.value.data.elements
|
||||
if (ir.aggregate(address.type)) address.cells = local.value.data.elements
|
||||
else address.scalar = &local.value
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{
|
||||
@@ -264,7 +268,7 @@ run proc(
|
||||
array_type = instruction.array_type,
|
||||
cells = &[],
|
||||
}
|
||||
if (address.type == .array) address.cells = global.value.data.elements
|
||||
if (ir.aggregate(address.type)) address.cells = global.value.data.elements
|
||||
else address.scalar = &global.value
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{
|
||||
@@ -286,7 +290,20 @@ run proc(
|
||||
type = instruction.pointee, array_type = instruction.array_type,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized,
|
||||
}
|
||||
if (address.type != .array) address.scalar = &address.cells[0]
|
||||
if (!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
|
||||
address := Address{
|
||||
type = instruction.pointee, array_type = instruction.array_type,
|
||||
cells = base.cells[field.offset..field.offset + width], initialized = base.initialized,
|
||||
}
|
||||
if (!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) } }
|
||||
}
|
||||
@@ -300,10 +317,10 @@ run proc(
|
||||
}
|
||||
}
|
||||
if (instruction.op == .load_address) {
|
||||
value = if (address.type == .array) array_view(address.array_type, address.cells) else address.scalar?^
|
||||
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 (address.type == .array) memcopy!(address.cells, source.data.elements)
|
||||
if (ir.aggregate(address.type)) memcopy!(address.cells, source.data.elements)
|
||||
else address.scalar?^ = source
|
||||
continue
|
||||
}
|
||||
@@ -432,7 +449,7 @@ push_frame proc(
|
||||
errdefer mem.free(allocator, addresses)
|
||||
for (signature.array_storage.items) |storage| {
|
||||
cells :: module.arrays.items[usize(storage.array_type)].cells
|
||||
value :: array_view(storage.array_type, aggregate[storage.offset..storage.offset + cells])
|
||||
value :: aggregate_view(module.arrays.items[usize(storage.array_type)].kind, storage.array_type, aggregate[storage.offset..storage.offset + cells])
|
||||
if (storage.local) locals[usize(storage.index)].value = value
|
||||
else values[usize(storage.index)] = value
|
||||
}
|
||||
@@ -496,13 +513,13 @@ checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
||||
}
|
||||
|
||||
@hide:file
|
||||
array_view proc(shape ir.ArrayId, cells []mut ir.Value) ir.Value {
|
||||
return ir.Value{ type = .array, array_type = shape, data = ir.ValueData{ elements = cells } }
|
||||
aggregate_view proc(type ir.Type, shape ir.ArrayId, cells []mut ir.Value) ir.Value {
|
||||
return ir.Value{ type = type, array_type = shape, data = ir.ValueData{ elements = cells } }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
||||
if (source.type == .array) {
|
||||
if (ir.aggregate(source.type)) {
|
||||
# aggregate destinations already own disjoint fixed segments; never install an alias.
|
||||
memcopy!(destination.data.elements, source.data.elements)
|
||||
} else {
|
||||
@@ -512,17 +529,9 @@ copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
||||
|
||||
@hide:file
|
||||
element_cells proc(module @ir.Module, shape ir.ArrayType) usize {
|
||||
return if (shape.element == .array) module.arrays.items[usize(shape.element_array)].cells else 1
|
||||
return if (ir.aggregate(shape.element)) module.arrays.items[usize(shape.element_array)].cells else 1
|
||||
}
|
||||
|
||||
@hide:file
|
||||
indexed_value proc(module @ir.Module, shape_id ir.ArrayId, cells []mut ir.Value, index usize) ir.Value {
|
||||
shape :: module.arrays.items[usize(shape_id)]
|
||||
if (shape.element != .array) return cells[index]
|
||||
width :: element_cells(module, shape)
|
||||
start :: index * width
|
||||
return array_view(shape.element_array, cells[start..start + width])
|
||||
}
|
||||
|
||||
@hide:file
|
||||
checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
|
||||
|
||||
+85
-24
@@ -14,18 +14,22 @@ no_array :: maxval!(ArrayId)
|
||||
no_value :: maxval!(ValueId)
|
||||
|
||||
Type :: enum {
|
||||
invalid,
|
||||
int,
|
||||
float,
|
||||
bool,
|
||||
string,
|
||||
void,
|
||||
function,
|
||||
array,
|
||||
usize,
|
||||
address,
|
||||
i8, i16, i32, isize,
|
||||
u8, u16, u32, u64,
|
||||
invalid
|
||||
int
|
||||
float
|
||||
bool
|
||||
string
|
||||
void
|
||||
function
|
||||
array
|
||||
record
|
||||
address
|
||||
i8, i16, i32, isize
|
||||
u8, u16, u32, u64, usize
|
||||
}
|
||||
|
||||
aggregate proc(type Type) bool {
|
||||
return type == .array or type == .record
|
||||
}
|
||||
|
||||
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
||||
@@ -84,13 +88,22 @@ Value :: struct {
|
||||
data ValueData = ValueData{ integer = 0 }
|
||||
}
|
||||
|
||||
#! fixed recursive shape; add_array derives `cells`, the flattened scalar-leaf count.
|
||||
RecordField :: struct {
|
||||
type Type
|
||||
array_type ArrayId = no_array
|
||||
offset usize = 0
|
||||
}
|
||||
|
||||
#! fixed recursive aggregate shape; constructors derive the flattened scalar-leaf count.
|
||||
#! record fields are owned by the module; array metadata retains its existing representation.
|
||||
ArrayType :: struct {
|
||||
element Type
|
||||
kind Type = .array
|
||||
element Type = .invalid
|
||||
element_array ArrayId = no_array
|
||||
len usize
|
||||
cells usize
|
||||
mutable bool
|
||||
len usize = 0
|
||||
cells usize = 0
|
||||
mutable bool = false
|
||||
fields []mut RecordField = &[]
|
||||
}
|
||||
|
||||
#! maps a local slot or temporary to its preallocated, by-value frame segment.
|
||||
@@ -109,10 +122,13 @@ Op :: enum {
|
||||
reset_local
|
||||
load_global
|
||||
array_literal
|
||||
record_literal
|
||||
field
|
||||
index
|
||||
address_local
|
||||
address_global
|
||||
index_address
|
||||
field_address
|
||||
load_address
|
||||
store_address
|
||||
negate
|
||||
@@ -244,8 +260,8 @@ add_array proc(
|
||||
len usize,
|
||||
mutable bool,
|
||||
) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
debug.assert((element == .array) == (element_array != no_array))
|
||||
width :: if (element == .array) module.arrays.items[usize(element_array)].cells else 1
|
||||
debug.assert(aggregate(element) == (element_array != no_array))
|
||||
width :: if (aggregate(element)) module.arrays.items[usize(element_array)].cells else 1
|
||||
if (width != 0 and len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
|
||||
cells :: len * width
|
||||
if (cells > divtrunc!(maxval!(usize), sizeof!(Value))) return .storage_limit_reached
|
||||
@@ -261,6 +277,48 @@ add_array proc(
|
||||
return id
|
||||
}
|
||||
|
||||
#! copies declaration-order fields and derives their flattened offsets; child shapes must exist.
|
||||
add_record proc(
|
||||
module @mut Module,
|
||||
fields []RecordField,
|
||||
) 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)
|
||||
cells usize := 0
|
||||
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 }
|
||||
cells += width
|
||||
}
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
try arraylist.append(&module.arrays, ArrayType{ kind = .record, fields = owned, cells = cells })
|
||||
return id
|
||||
}
|
||||
|
||||
#! returns a borrowed child value from flattened aggregate storage, preserving zero-cell shapes.
|
||||
aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
type := shape.element
|
||||
child := shape.element_array
|
||||
start usize := 0
|
||||
width usize := 1
|
||||
if (shape.kind == .record) {
|
||||
field :: shape.fields[index]
|
||||
type = field.type
|
||||
child = field.array_type
|
||||
start = field.offset
|
||||
} 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] } }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
prepare_storage proc(module @Module, function @mut Function, index u32, shape ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
cells :: module.arrays.items[usize(shape)].cells
|
||||
@@ -281,13 +339,13 @@ add_local proc(
|
||||
type Type,
|
||||
shape ArrayId,
|
||||
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
debug.assert((type == .array) == (shape != no_array))
|
||||
debug.assert(aggregate(type) == (shape != no_array))
|
||||
body :: &module.functions.items[usize(function)]
|
||||
if (body.locals.items.len >= usize(maxval!(LocalId))) return .storage_limit_reached
|
||||
id :: LocalId(body.locals.items.len)
|
||||
try arraylist.append(&body.locals, type)
|
||||
errdefer _ = arraylist.pop(&body.locals)
|
||||
if (type == .array) try prepare_storage(module, body, u32(id), shape, true)
|
||||
if (aggregate(type)) try prepare_storage(module, body, u32(id), shape, true)
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -305,9 +363,9 @@ add_parameter proc(
|
||||
return try add_local(module, function, type, shape)
|
||||
}
|
||||
|
||||
#! appends an instruction and derives its result count, array backing, and address presence.
|
||||
#! appends an instruction and derives its result count, aggregate backing, and address presence.
|
||||
#! no_value requests a fresh result; explicit result IDs support non-SSA definitions across blocks.
|
||||
#! repeated array results must retain their shape and reuse their existing segment.
|
||||
#! repeated aggregate results must retain their shape and reuse their existing segment.
|
||||
#! failures leave instruction and storage lengths, totals, counts, and flags unchanged.
|
||||
emit proc(
|
||||
module @mut Module,
|
||||
@@ -336,7 +394,7 @@ emit proc(
|
||||
}
|
||||
try arraylist.append(&target.instructions, operation)
|
||||
errdefer _ = arraylist.pop(&target.instructions)
|
||||
if (produces_value and operation.type == .array) {
|
||||
if (produces_value and aggregate(operation.type)) {
|
||||
debug.assert(operation.array_type != no_array)
|
||||
registered := false
|
||||
if (usize(operation.destination) < body.value_count) {
|
||||
@@ -377,6 +435,9 @@ deinit proc(module @mut Module) void {
|
||||
for (module.strings.items) |string|
|
||||
mem.free(module.allocator, string.allocation)
|
||||
|
||||
for (module.arrays.items) |shape| {
|
||||
if (shape.kind == .record) mem.free(module.allocator, shape.fields)
|
||||
}
|
||||
arraylist.deinit(&module.functions)
|
||||
arraylist.deinit(&module.globals)
|
||||
arraylist.deinit(&module.strings)
|
||||
|
||||
@@ -33,6 +33,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "and", .and },
|
||||
{ "or", .or },
|
||||
{ "mut", .mut },
|
||||
{ "struct", .struct },
|
||||
{ "import", .import },
|
||||
{ "test", .test },
|
||||
])
|
||||
@@ -102,7 +103,12 @@ scan proc(
|
||||
|
||||
if is_digit(char) {
|
||||
start :: cursor
|
||||
result :: scan_number(start, program) catch |err| {
|
||||
member :: (
|
||||
state.tokens.items.len > 0
|
||||
and state.tokens.items[state.tokens.items.len - 1].kind == .dot
|
||||
and usize(state.tokens.items[state.tokens.items.len - 1].span.end) == start
|
||||
)
|
||||
result :: scan_number(start, program, !member) catch |err| {
|
||||
id :: try diagnostics.add(
|
||||
diagnostic_store,
|
||||
diagnostic_code(err.code),
|
||||
@@ -362,12 +368,12 @@ ScanNumResult :: struct {
|
||||
}
|
||||
|
||||
@hide:file
|
||||
scan_number proc(start usize, program []u8) ScanNumResult ! ScanError {
|
||||
scan_number proc(start usize, program []u8, allow_decimal bool) ScanNumResult ! ScanError {
|
||||
cursor := start
|
||||
has_decimal := false
|
||||
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
||||
if (
|
||||
cursor < program.len and program[cursor] == '.'
|
||||
allow_decimal and cursor < program.len and program[cursor] == '.'
|
||||
and !(cursor + 1 < program.len and program[cursor + 1] == '.')
|
||||
) {
|
||||
has_decimal = true
|
||||
|
||||
@@ -25,6 +25,7 @@ TokenKind :: enum {
|
||||
true, false
|
||||
and, or
|
||||
mut
|
||||
struct
|
||||
import, test
|
||||
|
||||
# literals
|
||||
|
||||
@@ -43,19 +43,41 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
try write_type(output, state.module, type)
|
||||
try io.print(", ptr %r{}\n", output, {pointer})
|
||||
}
|
||||
.array_literal: {
|
||||
.array_literal, .record_literal: {
|
||||
for (0..instruction.arguments_count) |index| {
|
||||
operand :: block.arguments.items[instruction.arguments_start + index]
|
||||
value :: try load(state, operand)
|
||||
pointer :: fresh(state)
|
||||
try io.print(" %r{} = getelementptr ", output, {pointer})
|
||||
try write_type(output, state.module, type)
|
||||
try io.print(", ptr %v{}, i64 0, i64 {}\n store ", output, {u32(instruction.destination), index})
|
||||
index_type []u8 :: if (instruction.op == .record_literal) "i32" else "i64"
|
||||
try io.print(", ptr %v{}, i64 0, {s} {}\n store ", output, {u32(instruction.destination), index_type, index})
|
||||
try write_type(output, state.module, state.values[usize(operand)])
|
||||
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
|
||||
}
|
||||
return
|
||||
}
|
||||
.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})
|
||||
}
|
||||
.field_address: {
|
||||
base_type :: state.values[usize(instruction.left)]
|
||||
base :: try load(state, instruction.left)
|
||||
data :: fresh(state)
|
||||
flag :: fresh(state)
|
||||
pointer :: fresh(state)
|
||||
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})
|
||||
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})
|
||||
}
|
||||
.index, .index_address: {
|
||||
index :: try load(state, instruction.right)
|
||||
base_type :: state.values[usize(instruction.left)]
|
||||
|
||||
+58
-20
@@ -72,7 +72,10 @@ emit proc(
|
||||
try io.print(" zeroinitializer\n@gs{} = internal global i8 0\n", output, {index})
|
||||
try emit_global(output, module, global, index)
|
||||
}
|
||||
for (module.arrays.items) |_, index| try emit_array_printer(output, module, ir.ArrayId(index))
|
||||
for (module.arrays.items) |shape, index| {
|
||||
if (shape.kind == .record) try emit_record_printer(output, module, ir.ArrayId(index))
|
||||
else try emit_array_printer(output, module, ir.ArrayId(index))
|
||||
}
|
||||
for (module.functions.items) |function, index| {
|
||||
values :: try mem.alloc(Type, allocator, function.value_count)
|
||||
defer mem.free(allocator, values)
|
||||
@@ -91,13 +94,13 @@ emit proc(
|
||||
if (module.entry) |entry| {
|
||||
function :: module.functions.items[usize(entry)]
|
||||
result :: Type{ kind = function.result, shape = function.result_array_type }
|
||||
if (result.kind != .void) try io.write_all(output, " %result = ")
|
||||
if (result.kind != .void) try io.write_all(output, " %r0 = ")
|
||||
else try io.write_all(output, " ")
|
||||
try io.write_all(output, "call ")
|
||||
try write_type(output, module, result)
|
||||
try io.print(" @f{}()\n", output, {u32(entry)})
|
||||
if (result.kind != .void and result.kind != .invalid) {
|
||||
try print_call(output, module, result, "%result")
|
||||
try print_call(output, module, result, 0)
|
||||
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
|
||||
}
|
||||
try io.write_all(output, " call void @report(i64 -1)\n")
|
||||
@@ -138,6 +141,15 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
||||
try write_type(output, module, Type{ kind = shape.element, shape = shape.element_array })
|
||||
try io.write_all(output, "]")
|
||||
}
|
||||
.record: {
|
||||
shape :: module.arrays.items[usize(type.shape)]
|
||||
try io.write_all(output, "{ ")
|
||||
for (shape.fields) |field, index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
try write_type(output, module, Type{ kind = field.type, shape = field.array_type })
|
||||
}
|
||||
try io.write_all(output, " }")
|
||||
}
|
||||
.void: try io.write_all(output, "void")
|
||||
# invalid storage only belongs to recovery paths that trap before reading it.
|
||||
.invalid: try io.write_all(output, "i8")
|
||||
@@ -148,7 +160,7 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
||||
local_type proc(function @ir.Function, index usize) Type {
|
||||
type := Type{ kind = function.locals.items[index] }
|
||||
if (type.kind == .void) type.kind = .invalid
|
||||
if (type.kind == .array) {
|
||||
if (ir.aggregate(type.kind)) {
|
||||
for (function.array_storage.items) |storage| {
|
||||
if (storage.local and usize(storage.index) == index) {
|
||||
type.shape = storage.array_type
|
||||
@@ -244,24 +256,24 @@ emit_global proc(output io.Writer, module @ir.Module, global ir.Global, index us
|
||||
}
|
||||
|
||||
@hide:file
|
||||
print_call proc(output io.Writer, module @ir.Module, type Type, operand []u8) void ! io.WriteError {
|
||||
print_call proc(output io.Writer, module @ir.Module, type Type, operand usize) void ! io.WriteError {
|
||||
match type.kind {
|
||||
.i8, .i16, .i32, .int, .isize, .u8, .u16, .u32, .u64, .usize: {
|
||||
bits :: ir.integer_bits(type.kind)
|
||||
unsigned :: ir.unsigned_integer(type.kind)
|
||||
if (bits < 64) {
|
||||
extension []u8 :: if (unsigned) "zext" else "sext"
|
||||
try io.print(" %print_value = {s} i{} {s} to i64\n", output, {extension, bits, operand})
|
||||
try io.print(" call void @print_integer(i32 1, i64 %print_value, i1 {})\n", output, {unsigned})
|
||||
} else try io.print(" call void @print_integer(i32 1, i64 {s}, i1 {})\n", output, {operand, unsigned})
|
||||
try io.print(" %print_value{} = {s} i{} %r{} to i64\n", output, {operand, extension, bits, operand})
|
||||
try io.print(" call void @print_integer(i32 1, i64 %print_value{}, i1 {})\n", output, {operand, unsigned})
|
||||
} else try io.print(" call void @print_integer(i32 1, i64 %r{}, i1 {})\n", output, {operand, unsigned})
|
||||
}
|
||||
.float: try io.print(" call void @print_float(double {s})\n", output, {operand})
|
||||
.bool: try io.print(" call void @print_bool(i1 {s})\n", output, {operand})
|
||||
.string: try io.print(" call void @print_string(%string {s})\n", output, {operand})
|
||||
.array: {
|
||||
.float: try io.print(" call void @print_float(double %r{})\n", output, {operand})
|
||||
.bool: try io.print(" call void @print_bool(i1 %r{})\n", output, {operand})
|
||||
.string: try io.print(" call void @print_string(%string %r{})\n", output, {operand})
|
||||
.array, .record: {
|
||||
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
|
||||
try write_type(output, module, type)
|
||||
try io.print(" {s})\n", output, {operand})
|
||||
try io.print(" %r{})\n", output, {operand})
|
||||
}
|
||||
else: unreachable
|
||||
}
|
||||
@@ -282,15 +294,33 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
|
||||
try io.print(" %done = icmp eq i64 %index, {}\n", output, {shape.len})
|
||||
try io.write_all(output, " br i1 %done, label %end, label %separator\nseparator:\n %first = icmp eq i64 %index, 0\n br i1 %first, label %element, label %comma\ncomma:\n call void @out(i32 1, ptr @separator, i64 2)\n br label %element\nelement:\n %pointer = getelementptr ")
|
||||
try write_type(output, module, type)
|
||||
try io.write_all(output, ", ptr %storage, i64 0, i64 %index\n %item = load ")
|
||||
try io.write_all(output, ", ptr %storage, i64 0, i64 %index\n %r0 = load ")
|
||||
try write_type(output, module, element)
|
||||
try io.write_all(output, ", ptr %pointer\n")
|
||||
# functions may occur inside arrays without being printable source results.
|
||||
if (element.kind != .function) try print_call(output, module, element, "%item")
|
||||
if (element.kind != .function) try print_call(output, module, element, 0)
|
||||
else try io.write_all(output, " call void @exit(i32 2)\n")
|
||||
try io.write_all(output, " %next = add i64 %index, 1\n br label %loop\nend:\n call void @out(i32 1, ptr @close, i64 1)\n ret void\n}\n")
|
||||
}
|
||||
|
||||
@hide:file
|
||||
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 }
|
||||
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")
|
||||
for (shape.fields) |field, index| {
|
||||
if (index != 0) try io.write_all(output, " call void @out(i32 1, ptr @separator, i64 2)\n")
|
||||
try io.print(" %r{} = extractvalue ", output, {index})
|
||||
try write_type(output, module, type)
|
||||
try io.print(" %value, {}\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, " 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
|
||||
@@ -356,20 +386,28 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
|
||||
.function: try io.print("@f{}", output, {u32(value.data.function)})
|
||||
.array: {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
width :: if (shape.element == .array) module.arrays.items[usize(shape.element_array)].cells else 1
|
||||
try io.write_all(output, "[")
|
||||
for (0..shape.len) |index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
element := ir.Value{}
|
||||
if (shape.element == .array) {
|
||||
element = ir.Value{ type = .array, array_type = shape.element_array, data = ir.ValueData{ elements = value.data.elements[index * width..(index + 1) * width] } }
|
||||
} else element = value.data.elements[index]
|
||||
element :: ir.aggregate_item(module, value, index)
|
||||
try write_type(output, module, Type{ kind = element.type, shape = element.array_type })
|
||||
try io.write_all(output, " ")
|
||||
try write_constant(output, module, element)
|
||||
}
|
||||
try io.write_all(output, "]")
|
||||
}
|
||||
.record: {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
try io.write_all(output, "{ ")
|
||||
for (shape.fields) |_, index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
field :: ir.aggregate_item(module, value, index)
|
||||
try write_type(output, module, Type{ kind = field.type, shape = field.array_type })
|
||||
try io.write_all(output, " ")
|
||||
try write_constant(output, module, field)
|
||||
}
|
||||
try io.write_all(output, " }")
|
||||
}
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ runtime ::
|
||||
`@newline = private constant [1 x i8] c"\0A"
|
||||
`@open = private constant [1 x i8] c"["
|
||||
`@close = private constant [1 x i8] c"]"
|
||||
`@record_open = private constant [1 x i8] c"{"
|
||||
`@record_close = private constant [1 x i8] c"}"
|
||||
`@separator = private constant [2 x i8] c", "
|
||||
`@write_error = private constant [44 x i8] c": infrastructure error: cannot write output\0A"
|
||||
`@read = private constant [42 x i8] c"local value was read before initialization"
|
||||
|
||||
+126
-5
@@ -33,6 +33,7 @@ State :: struct {
|
||||
storage []mut Storage
|
||||
arrays []mut ir.ArrayId
|
||||
arguments arraylist.ArrayList(ir.ValueId)
|
||||
records []mut ir.ArrayId
|
||||
function usize = 0
|
||||
block ir.BlockId = ir.BlockId(0)
|
||||
terminated bool = false
|
||||
@@ -75,6 +76,9 @@ lower_package proc(
|
||||
arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked))
|
||||
defer mem.free(allocator, arrays)
|
||||
mem.set(arrays, ir.no_array)
|
||||
records :: try mem.alloc(ir.ArrayId, allocator, typecheck.record_type_count(checked))
|
||||
defer mem.free(allocator, records)
|
||||
mem.set(records, ir.no_array)
|
||||
state := State{
|
||||
tree = tree,
|
||||
resolution = resolution,
|
||||
@@ -83,6 +87,7 @@ lower_package proc(
|
||||
module = &module,
|
||||
storage = storage,
|
||||
arrays = arrays,
|
||||
records = records,
|
||||
arguments = arraylist.init(ir.ValueId, allocator),
|
||||
}
|
||||
defer arraylist.deinit(&state.arguments)
|
||||
@@ -96,6 +101,7 @@ lower_package proc(
|
||||
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
|
||||
}
|
||||
.statement: {
|
||||
if (typecheck.type_of(checked, id).kind == .type_value) continue
|
||||
initializer :: try add_function(&module, ast.node_span(tree, id))
|
||||
global :: u32(module.globals.items.len)
|
||||
try arraylist.append(&module.globals, ir.Global{
|
||||
@@ -114,7 +120,9 @@ lower_package proc(
|
||||
id :: ast.package_decl(tree, i)
|
||||
match ast.decl_kind(tree, id) {
|
||||
.function: try lower_function(&state, id)
|
||||
.statement: try lower_global(&state, id)
|
||||
.statement: {
|
||||
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
||||
}
|
||||
.import, .invalid: {}
|
||||
}
|
||||
}
|
||||
@@ -135,17 +143,19 @@ lower_type proc(type typecheck.Type) ir.Type {
|
||||
.u32: .u32
|
||||
.u64: .u64
|
||||
.array: .array
|
||||
.record: .record
|
||||
.float: .float
|
||||
.bool: .bool
|
||||
.string: .string
|
||||
.void: .void
|
||||
.function: .function
|
||||
.none, .invalid, .range: .invalid
|
||||
.none, .invalid, .range, .type_value: .invalid
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (type.kind == .record) return try prepare_record(state, type.record_type)
|
||||
if (type.kind != .array) return ir.no_array
|
||||
# checker history includes unresolved shapes; materialize only final used types.
|
||||
return try prepare_array(state, type.array_type)
|
||||
@@ -156,7 +166,7 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
|
||||
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
|
||||
descriptor :: typecheck.array_type(state.checked, id)
|
||||
child := ir.no_array
|
||||
if (descriptor.element.kind == .array) child = try prepare_array(state, descriptor.element.array_type)
|
||||
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),
|
||||
@@ -168,6 +178,24 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
|
||||
return result
|
||||
}
|
||||
|
||||
@hide:file
|
||||
prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.records[usize(id)] != ir.no_array) return state.records[usize(id)]
|
||||
descriptor :: typecheck.record_type(state.checked, id)
|
||||
fields := arraylist.init(ir.RecordField, state.module.allocator)
|
||||
defer arraylist.deinit(&fields)
|
||||
try arraylist.reserve(&fields, descriptor.fields.len)
|
||||
for (descriptor.fields) |field| {
|
||||
try arraylist.append(&fields, ir.RecordField{
|
||||
type = lower_type(field.type),
|
||||
array_type = try array_shape(state, field.type),
|
||||
})
|
||||
}
|
||||
shape :: try ir.add_record(state.module, fields.items)
|
||||
state.records[usize(id)] = shape
|
||||
return shape
|
||||
}
|
||||
|
||||
@hide:file
|
||||
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
||||
id :: ir.FunctionId(module.functions.items.len)
|
||||
@@ -315,6 +343,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
||||
statement :: ast.block_statement(state.tree, block, i)
|
||||
match ast.stmt_kind(state.tree, statement) {
|
||||
.declaration: {
|
||||
if (typecheck.type_of(state.checked, statement).kind == .type_value) continue
|
||||
type :: typecheck.type_of(state.checked, statement)
|
||||
local :: try ir.add_local(
|
||||
state.module,
|
||||
@@ -355,6 +384,7 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
|
||||
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
||||
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
|
||||
_ = try emit(state, ir.Instruction{
|
||||
op = .reset_local,
|
||||
slot = state.storage[usize(declaration)].index,
|
||||
@@ -539,7 +569,9 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
}
|
||||
.assignment_stmt: {
|
||||
assignment :: ast.assignment_stmt(tree, id)
|
||||
if (ast.expr_kind(tree, assignment.target) == .index) {
|
||||
if (ast.expr_kind(tree, assignment.target) == .index
|
||||
or (ast.expr_kind(tree, assignment.target) == .selection
|
||||
and sema.binding(state.resolution, assignment.target).kind == .none)) {
|
||||
value :: try lower_expression(state, assignment.value)
|
||||
if (state.terminated) return
|
||||
if (!is_place(state, assignment.target)) {
|
||||
@@ -577,6 +609,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 (trap_binding(state, id)) return
|
||||
declaration :: ast.statement_decl(tree, id)
|
||||
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
||||
@@ -642,10 +675,66 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
||||
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_record proc(state @mut State, id ast.NodeId) 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]
|
||||
explicit_count :: ast.record_field_count(tree, id)
|
||||
try arraylist.reserve(&state.arguments, scratch_start + explicit_count)
|
||||
for (0..explicit_count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||
for (0..explicit_count) |i| {
|
||||
value :: try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?)
|
||||
if (state.terminated) return ir.no_value
|
||||
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)
|
||||
for (descriptor.fields) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||
for (0..(descriptor.fields.len)) |i| {
|
||||
initializer :: typecheck.initializer_field(state.checked, id, i)
|
||||
value := ir.no_value
|
||||
# ponytail: quadratic field reorder; retain an inverse map if large records make lowering costly.
|
||||
for (0..explicit_count) |j| {
|
||||
if (ast.field(tree, ast.record_field(tree, id, j)).value? == initializer) {
|
||||
value = state.arguments.items[scratch_start + j]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (value == ir.no_value) value = try lower_expression(state, initializer)
|
||||
if (state.terminated) return ir.no_value
|
||||
state.arguments.items[ordered_start + i] = value
|
||||
}
|
||||
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
||||
start :: block.arguments.items.len
|
||||
try arraylist.reserve(&block.arguments, start + descriptor.fields.len)
|
||||
for (0..(descriptor.fields.len)) |i| try arraylist.append(&block.arguments, state.arguments.items[ordered_start + i])
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .record_literal,
|
||||
type = .record,
|
||||
array_type = try array_shape(state, type),
|
||||
arguments_start = start,
|
||||
arguments_count = descriptor.fields.len,
|
||||
span = ast.node_span(tree, id),
|
||||
})
|
||||
}
|
||||
|
||||
@hide:file
|
||||
is_place proc(state @State, id ast.NodeId) bool {
|
||||
match ast.expr_kind(state.tree, id) {
|
||||
.index: return is_place(state, ast.index_expr(state.tree, id).base)
|
||||
.selection: {
|
||||
if (sema.binding(state.resolution, id).kind == .none) {
|
||||
return is_place(state, ast.selection_expr(state.tree, id).base)
|
||||
}
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind != .declaration) return false
|
||||
location :: state.storage[usize(binding.data.declaration)]
|
||||
return location.kind == .local or location.kind == .global
|
||||
}
|
||||
.identifier: {
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind == .invalid) return true
|
||||
@@ -661,6 +750,21 @@ is_place proc(state @State, id ast.NodeId) bool {
|
||||
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
span :: ast.node_span(state.tree, id)
|
||||
checked :: typecheck.type_of(state.checked, id)
|
||||
if (ast.expr_kind(state.tree, id) == .selection
|
||||
and sema.binding(state.resolution, id).kind == .none) {
|
||||
expression :: ast.selection_expr(state.tree, id)
|
||||
base :: try lower_place(state, expression.base)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .field_address,
|
||||
type = .address,
|
||||
pointee = lower_type(checked),
|
||||
array_type = try array_shape(state, checked),
|
||||
left = base,
|
||||
slot = u32(typecheck.field_index(state.checked, id)),
|
||||
span = span,
|
||||
})
|
||||
}
|
||||
if (ast.expr_kind(state.tree, id) == .index) {
|
||||
expression :: ast.index_expr(state.tree, id)
|
||||
base :: try lower_place(state, expression.base)
|
||||
@@ -706,11 +810,12 @@ 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: {
|
||||
.array_type, .record_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)
|
||||
.array_literal: {
|
||||
scratch_start :: state.arguments.items.len
|
||||
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||
@@ -769,6 +874,22 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
return try lower_string(state, id)
|
||||
}
|
||||
.identifier, .selection: {
|
||||
if (ast.expr_kind(tree, id) == .selection
|
||||
and sema.binding(state.resolution, id).kind == .none) {
|
||||
if (is_place(state, id)) {
|
||||
address :: try lower_place(state, id)
|
||||
if (state.terminated) return ir.no_value
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .load_address, type = type, array_type = shape, left = address, span = span,
|
||||
})
|
||||
}
|
||||
base :: try lower_expression(state, ast.selection_expr(tree, id).base)
|
||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .field, type = type, array_type = shape, left = base,
|
||||
slot = u32(typecheck.field_index(state.checked, id)), span = span,
|
||||
})
|
||||
}
|
||||
if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
location :: state.storage[usize(binding.data.declaration)]
|
||||
|
||||
+9
-21
@@ -208,29 +208,17 @@ print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.
|
||||
.float: try io.print("{d}", output, {value.data.real})
|
||||
.bool: try io.print("{}", output, {value.data.boolean})
|
||||
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
||||
.array: {
|
||||
.array, .record: {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
stride usize :: if (shape.element == .array)
|
||||
module.arrays.items[usize(shape.element_array)].cells
|
||||
else
|
||||
1
|
||||
try io.print("[", output, {})
|
||||
for 0..(shape.len) |index| {
|
||||
if (index != 0) try io.print(", ", output, {})
|
||||
element := ir.Value{}
|
||||
if shape.element == .array {
|
||||
start :: index * stride
|
||||
element = ir.Value{
|
||||
type = .array,
|
||||
array_type = shape.element_array,
|
||||
data = ir.ValueData{ elements = value.data.elements[start..(start + stride)] },
|
||||
}
|
||||
} else {
|
||||
element = value.data.elements[index]
|
||||
}
|
||||
try print_value(output, module, element)
|
||||
count :: if (value.type == .array) shape.len else shape.fields.len
|
||||
opening :: if (value.type == .array) "[" else "{"
|
||||
closing :: if (value.type == .array) "]" else "}"
|
||||
try io.write_all(output, opening)
|
||||
for (0..count) |index| {
|
||||
if (index != 0) try io.write_all(output, ", ")
|
||||
try print_value(output, module, ir.aggregate_item(module, value, index))
|
||||
}
|
||||
try io.print("]", output, {})
|
||||
try io.write_all(output, closing)
|
||||
}
|
||||
else: unreachable
|
||||
}
|
||||
|
||||
+220
-10
@@ -29,6 +29,7 @@ State :: struct {
|
||||
allocator mem.Allocator
|
||||
next_token usize
|
||||
paren_depth usize = 0
|
||||
allow_record_literals bool = true
|
||||
}
|
||||
|
||||
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
|
||||
@@ -155,7 +156,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)) break
|
||||
if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct)) break
|
||||
_ = try consume(state, .comma)
|
||||
}
|
||||
|
||||
@@ -235,6 +236,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
next_is(state, .ident)
|
||||
and (
|
||||
next_n_is(state, 1, .ident)
|
||||
or next_n_is(state, 1, .struct)
|
||||
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)
|
||||
@@ -248,7 +250,7 @@ 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: {
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct: {
|
||||
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)
|
||||
@@ -424,7 +426,7 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .for)
|
||||
skip_newlines(state)
|
||||
iterable_start :: state.next_token
|
||||
iterable := try parse_expr(state)
|
||||
iterable := try parse_guard_expr(state)
|
||||
grouped :: parenthesized_guard(state, iterable_start)
|
||||
skip_newlines(state)
|
||||
captures :: parse_captures(state) catch |err| {
|
||||
@@ -478,10 +480,18 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_guard_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||
previous :: state.allow_record_literals
|
||||
state.allow_record_literals = false
|
||||
defer state.allow_record_literals = previous
|
||||
return try parse_expr(state)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
||||
condition_start :: state.next_token
|
||||
condition := parse_expr(state) catch |err| {
|
||||
condition := parse_guard_expr(state) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
@@ -559,7 +569,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))
|
||||
type_id ?ast.NodeId :: if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct))
|
||||
try parse_type(state)
|
||||
else
|
||||
null
|
||||
@@ -611,8 +621,169 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
count, element, mutable,
|
||||
)
|
||||
}
|
||||
if next_is(state, .struct) return try parse_record_type(state)
|
||||
token :: try consume(state, .ident)
|
||||
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
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_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,
|
||||
end = member.span.end,
|
||||
source = token.span.source,
|
||||
}, type, member_id)
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .struct)
|
||||
return try parse_record(state, null, keyword.span.start, true)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_record_literal proc(
|
||||
state @mut State, type ?ast.NodeId, start u32,
|
||||
) ast.NodeId ! ParseError {
|
||||
return try parse_record(state, type, start, false)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_record proc(
|
||||
state @mut State, type ?ast.NodeId, start u32, declaration bool,
|
||||
) ast.NodeId ! ParseError {
|
||||
open :: try consume(state, .open_curly)
|
||||
previous :: state.allow_record_literals
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
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)) {
|
||||
# leave the next declaration intact when a record's closing brace is missing.
|
||||
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, start, false)
|
||||
field_start :: state.next_token
|
||||
field :: parse_record_field(state, declaration) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, field_start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
recovered_type ?ast.NodeId :: if (declaration) invalid else null
|
||||
recovered_value ?ast.NodeId :: if (declaration) null else invalid
|
||||
yield try ast.add_field(state.builder, span, null, recovered_type, recovered_value)
|
||||
}
|
||||
try arraylist.append(&fields, field)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) {
|
||||
state.next_token += 1
|
||||
skip_newlines(state)
|
||||
continue
|
||||
}
|
||||
if (declaration and next_is(state, .newline)) {
|
||||
skip_newlines(state)
|
||||
continue
|
||||
}
|
||||
after_field :: state.next_token
|
||||
skip_newlines(state)
|
||||
if next_is(state, .close_curly) break
|
||||
state.next_token = after_field
|
||||
return try invalid_record(state, start, true)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
span :: ast.Span{ start = start, end = close.span.end, source = open.span.source }
|
||||
if (declaration) return try ast.add_record_type(state.builder, span, fields.items)
|
||||
return try ast.add_record_literal(state.builder, span, type, fields.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseError {
|
||||
start :: current(state).span
|
||||
start_token :: state.next_token
|
||||
name ?ast.NodeId := null
|
||||
type ?ast.NodeId := null
|
||||
value ?ast.NodeId := null
|
||||
named :: next_is(state, .ident) 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)
|
||||
))
|
||||
)
|
||||
if (named) {
|
||||
token :: try consume(state, .ident)
|
||||
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
}
|
||||
if (declaration) {
|
||||
type = try parse_type(state)
|
||||
if next_is(state, .equal) {
|
||||
state.next_token += 1
|
||||
value = try parse_expr(state)
|
||||
}
|
||||
} else {
|
||||
if (named) _ = try consume(state, .equal)
|
||||
value = try parse_expr(state)
|
||||
}
|
||||
end :: if (state.next_token > start_token) state.tokens[state.next_token - 1].span.end else start.end
|
||||
return try ast.add_field(state.builder, ast.Span{
|
||||
start = start.start,
|
||||
end = end,
|
||||
source = start.source,
|
||||
}, name, type, value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
recover_record_field proc(state @mut State, start usize) ast.Span {
|
||||
depth usize := 0
|
||||
for (start..state.next_token) |index| {
|
||||
kind :: state.tokens[index].kind
|
||||
if (kind == .open_curly or kind == .open_paren or kind == .open_bracket) depth += 1
|
||||
if ((kind == .close_curly or kind == .close_paren or kind == .close_bracket) and depth > 0) depth -= 1
|
||||
}
|
||||
while (!next_is(state, .eof)) {
|
||||
kind :: current(state).kind
|
||||
if (depth == 0 and (kind == .comma or kind == .newline or kind == .close_curly)) break
|
||||
if (kind == .open_curly or kind == .open_paren or kind == .open_bracket) depth += 1
|
||||
if (kind == .close_curly or kind == .close_paren or kind == .close_bracket) {
|
||||
if (depth == 0) break
|
||||
depth -= 1
|
||||
}
|
||||
state.next_token += 1
|
||||
}
|
||||
first :: state.tokens[start].span
|
||||
end :: if (state.next_token > start) state.tokens[state.next_token - 1].span.end else first.end
|
||||
return ast.Span{
|
||||
start = first.start,
|
||||
end = end,
|
||||
source = first.source,
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
invalid_record proc(state @mut State, start u32, recover_tail bool) ast.NodeId ! ParseError {
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
if (recover_tail) {
|
||||
while (!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
before :: state.next_token
|
||||
_ = recover_record_field(state, before)
|
||||
if (next_is(state, .comma)) state.next_token += 1
|
||||
if (state.next_token == before) break
|
||||
}
|
||||
if (next_is(state, .close_curly)) state.next_token += 1
|
||||
}
|
||||
return try ast.add_invalid_expr(state.builder, ast.Span{
|
||||
start = start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = state.builder.source_id,
|
||||
}, diagnostic)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -658,10 +829,18 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .open_curly and state.allow_record_literals) {
|
||||
left = try parse_record_literal(state, left, state.tokens[expression_start].span.start)
|
||||
left_grouped = false
|
||||
continue
|
||||
}
|
||||
if (token.kind == .dot) {
|
||||
state.next_token += 1
|
||||
member :: try consume(state, .ident)
|
||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||
member :: try consume_either(state, &[.ident, .int])
|
||||
member_id :: if (member.kind == .int)
|
||||
try ast.add_literal(state.builder, .int, member.span)
|
||||
else
|
||||
try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||
left = try ast.add_selection(
|
||||
state.builder,
|
||||
ast.Span{
|
||||
@@ -736,11 +915,18 @@ validate_range_bound proc(
|
||||
@hide:file
|
||||
parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .open_paren)
|
||||
previous :: state.allow_record_literals
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
state.paren_depth += 1
|
||||
defer state.paren_depth -= 1
|
||||
skip_operand_newlines(state)
|
||||
arguments arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&arguments)
|
||||
argument_start :: state.next_token
|
||||
node_count :: state.builder.storage.nodes.items.len
|
||||
extra_count :: state.builder.storage.extra.items.len
|
||||
diagnostic_count :: state.diagnostics.list.items.len
|
||||
|
||||
while (!is_call_boundary(current(state).kind)) {
|
||||
argument :: try parse_expr(state)
|
||||
@@ -749,6 +935,15 @@ parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! Par
|
||||
state.next_token += 1
|
||||
skip_operand_newlines(state)
|
||||
}
|
||||
if (!previous and !next_is(state, .close_paren)) {
|
||||
# a guard's body may look like a record argument until its missing ')' is known.
|
||||
state.next_token = argument_start
|
||||
state.builder.storage.nodes.items = state.builder.storage.nodes.items.ptr[..node_count]
|
||||
state.builder.storage.spans.items = state.builder.storage.spans.items.ptr[..node_count]
|
||||
state.builder.storage.extra.items = state.builder.storage.extra.items.ptr[..extra_count]
|
||||
state.diagnostics.list.items = state.diagnostics.list.items.ptr[..diagnostic_count]
|
||||
return .unexpected_token
|
||||
}
|
||||
close :: try consume(state, .close_paren)
|
||||
return try ast.add_call(
|
||||
state.builder,
|
||||
@@ -765,6 +960,9 @@ parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! Par
|
||||
@hide:file
|
||||
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .open_bracket)
|
||||
previous :: state.allow_record_literals
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
index :: try parse_expr(state)
|
||||
if !next_is(state, .close_bracket) {
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||
@@ -801,7 +999,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
|
||||
return next == .ident or next == .mut or next == .open_bracket or next == .struct
|
||||
}
|
||||
}
|
||||
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
||||
@@ -812,6 +1010,9 @@ array_starts_type proc(state @State) bool {
|
||||
@hide:file
|
||||
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
||||
open :: try consume(state, .open_bracket)
|
||||
previous :: state.allow_record_literals
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&elements)
|
||||
after_open :: state.next_token
|
||||
@@ -871,12 +1072,21 @@ named_binding_starts proc(state @State) bool {
|
||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: current(state)
|
||||
match token.kind {
|
||||
.struct: return try parse_record_type(state)
|
||||
.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)
|
||||
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
|
||||
}
|
||||
.open_bracket: {
|
||||
if array_starts_type(state) return try parse_type(state)
|
||||
return try parse_array_literal(state)
|
||||
}
|
||||
.open_paren: {
|
||||
state.next_token += 1
|
||||
previous :: state.allow_record_literals
|
||||
state.allow_record_literals = true
|
||||
defer state.allow_record_literals = previous
|
||||
state.paren_depth += 1
|
||||
defer state.paren_depth -= 1
|
||||
expression :: try parse_expr(state)
|
||||
@@ -943,7 +1153,7 @@ is_call_boundary proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .break
|
||||
or kind == .continue
|
||||
or is_expression_boundary(kind)
|
||||
or (kind != .open_curly and is_expression_boundary(kind))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -745,41 +745,6 @@ rejects_top_level_assignment_loop_else_and_assignment_expressions test {
|
||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
||||
}
|
||||
|
||||
recovers_nested_braces_in_malformed_loop_body_and_assignment test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`main proc() void {
|
||||
` while (true) @ {
|
||||
` bad()
|
||||
` }
|
||||
` x = {
|
||||
` bad()
|
||||
` }
|
||||
` kept :: 7
|
||||
`}
|
||||
`tail :: 8
|
||||
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(2, ast.package_decl_count(&tree))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0)))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1)))
|
||||
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2))
|
||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||
}
|
||||
|
||||
parses_logical_precedence_and_associativity test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
|
||||
+484
-25
@@ -7,25 +7,39 @@ import "@source/diagnostics"
|
||||
sema :: import "@source/sema"
|
||||
|
||||
TypeKind :: enum {
|
||||
none,
|
||||
invalid,
|
||||
int,
|
||||
float,
|
||||
bool,
|
||||
string,
|
||||
void,
|
||||
function,
|
||||
range,
|
||||
array,
|
||||
usize,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
isize,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
none
|
||||
invalid
|
||||
int
|
||||
float
|
||||
bool
|
||||
string
|
||||
void
|
||||
function
|
||||
range
|
||||
array
|
||||
record
|
||||
type_value
|
||||
u8, u16, u32, u64, usize
|
||||
i8, i16, i32, isize
|
||||
}
|
||||
|
||||
RecordId :: distinct u32
|
||||
no_record :: maxval!(RecordId)
|
||||
|
||||
RecordField :: struct {
|
||||
name ?ast.NodeId
|
||||
type Type
|
||||
default ?ast.NodeId
|
||||
}
|
||||
|
||||
RecordType :: struct {
|
||||
declaration ast.NodeId
|
||||
fields []RecordField
|
||||
}
|
||||
|
||||
@hide:file
|
||||
RecordInitializer :: struct {
|
||||
fields []mut ast.NodeId
|
||||
}
|
||||
|
||||
ArrayId :: distinct u32
|
||||
@@ -43,17 +57,21 @@ Type :: struct {
|
||||
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||
array_type ArrayId = no_array
|
||||
range_element TypeKind = .none
|
||||
record_type RecordId = no_record
|
||||
}
|
||||
|
||||
@hide:file
|
||||
Storage :: struct {
|
||||
types []mut Type
|
||||
arrays arraylist.ArrayList(ArrayType)
|
||||
records arraylist.ArrayList(RecordType)
|
||||
initializers arraylist.ArrayList(RecordInitializer)
|
||||
slots []mut usize
|
||||
fallthroughs hashmap.AutoHashMap(ast.NodeId, diagnostics.DiagnosticId)
|
||||
allocator mem.Allocator
|
||||
}
|
||||
|
||||
#! owns node-indexed types, interned array descriptors, and function fallthrough obligations; release with deinit.
|
||||
#! owns node-indexed types, aggregate descriptors, initializer mappings, and function fallthrough obligations; release with deinit.
|
||||
#! function IDs refer to the caller-owned AST; diagnostic IDs refer to the caller-owned store.
|
||||
Result :: struct { storage Storage }
|
||||
|
||||
@@ -104,6 +122,8 @@ Cell :: struct {
|
||||
literal ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
producer ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
capture ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
record_checking bool = false
|
||||
nominal bool = false
|
||||
first_edge usize = no_index
|
||||
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
||||
}
|
||||
@@ -124,12 +144,15 @@ State :: struct {
|
||||
groups []mut GroupLink
|
||||
edges arraylist.ArrayList(Edge)
|
||||
arrays arraylist.ArrayList(ArrayType)
|
||||
records arraylist.ArrayList(RecordType)
|
||||
initializers arraylist.ArrayList(RecordInitializer)
|
||||
slots []mut usize
|
||||
collecting_assignment bool = false
|
||||
has_assignments bool = false
|
||||
}
|
||||
|
||||
#! borrows the AST, resolution, and diagnostic store only during checking.
|
||||
#! temporary constraints are freed on every exit; Result owns types, array descriptors, and fallthrough obligations.
|
||||
#! temporary constraints are freed on every exit; Result owns types, aggregate descriptors, initializer mappings, and fallthrough obligations.
|
||||
check proc(
|
||||
allocator mem.Allocator,
|
||||
tree @ast.Tree,
|
||||
@@ -149,6 +172,9 @@ check proc(
|
||||
for (0..count) |i| groups[i].parent = i
|
||||
queue :: try mem.alloc(ast.NodeId, allocator, count)
|
||||
defer mem.free(allocator, queue)
|
||||
slots :: try mem.alloc(usize, allocator, count)
|
||||
errdefer mem.free(allocator, slots)
|
||||
mem.set(slots, no_index)
|
||||
state := State{
|
||||
tree = tree,
|
||||
resolution = resolution,
|
||||
@@ -158,9 +184,18 @@ check proc(
|
||||
groups = groups,
|
||||
edges = arraylist.init(allocator),
|
||||
arrays = arraylist.init(allocator),
|
||||
records = arraylist.init(allocator),
|
||||
initializers = arraylist.init(allocator),
|
||||
slots = slots,
|
||||
}
|
||||
defer arraylist.deinit(&state.edges)
|
||||
errdefer arraylist.deinit(&state.arrays)
|
||||
errdefer {
|
||||
for (state.records.items) |record| mem.free(allocator, record.fields)
|
||||
arraylist.deinit(&state.records)
|
||||
for (state.initializers.items) |initializer| mem.free(allocator, initializer.fields)
|
||||
arraylist.deinit(&state.initializers)
|
||||
}
|
||||
|
||||
for 0..ast.package_decl_count(tree) |i| {
|
||||
id :: ast.package_decl(tree, i)
|
||||
@@ -195,6 +230,7 @@ check proc(
|
||||
for (0..count) |i| {
|
||||
id :: ast.NodeId(i)
|
||||
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
||||
if (cells[i].type.kind == .invalid) continue
|
||||
binding :: sema.binding(resolution, id)
|
||||
if (binding.kind != .declaration) continue
|
||||
target :: binding.data.declaration
|
||||
@@ -207,6 +243,7 @@ check proc(
|
||||
join(&state, id, ast.statement_decl(tree, id).initializer)
|
||||
}
|
||||
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
||||
if (cells[i].type.kind == .invalid) continue
|
||||
binding :: sema.binding(resolution, id)
|
||||
if (binding.kind == .declaration and !cells[i].assignment_value and !cells[usize(binding.data.declaration)].fixed) {
|
||||
join(&state, id, binding.data.declaration)
|
||||
@@ -225,6 +262,7 @@ check proc(
|
||||
or cells[i].role != .expression
|
||||
or !declaration_reference(tree, id)
|
||||
) continue
|
||||
if (cells[i].type.kind == .invalid) continue
|
||||
binding :: sema.binding(resolution, id)
|
||||
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
|
||||
}
|
||||
@@ -356,6 +394,11 @@ check proc(
|
||||
}
|
||||
}
|
||||
for (0..count) |i| try validate(&state, ast.NodeId(i))
|
||||
for state.records.items |record| {
|
||||
for record.fields |field| {
|
||||
if (field.default) |value| try compatible(&state, value, field.type)
|
||||
}
|
||||
}
|
||||
|
||||
# errors travel only from dependencies to users. A rejected use never poisons
|
||||
# an explicitly typed declaration, and body errors do not erase signatures.
|
||||
@@ -401,6 +444,9 @@ check proc(
|
||||
return Result{ storage = Storage{
|
||||
types = types,
|
||||
arrays = state.arrays,
|
||||
records = state.records,
|
||||
initializers = state.initializers,
|
||||
slots = slots,
|
||||
fallthroughs = fallthroughs,
|
||||
allocator = allocator,
|
||||
} }
|
||||
@@ -427,11 +473,36 @@ array_type proc(result @Result, id ArrayId) ArrayType {
|
||||
return result.storage.arrays.items[usize(id)]
|
||||
}
|
||||
|
||||
record_type_count proc(result @Result) usize {
|
||||
return result.storage.records.items.len
|
||||
}
|
||||
|
||||
record_type proc(result @Result, id RecordId) RecordType {
|
||||
debug.assert(usize(id) < result.storage.records.items.len)
|
||||
return result.storage.records.items[usize(id)]
|
||||
}
|
||||
|
||||
field_index proc(result @Result, selection ast.NodeId) usize {
|
||||
debug.assert(result.storage.slots[usize(selection)] != no_index)
|
||||
return result.storage.slots[usize(selection)]
|
||||
}
|
||||
|
||||
initializer_field proc(result @Result, literal ast.NodeId, index usize) ast.NodeId {
|
||||
slot :: result.storage.slots[usize(literal)]
|
||||
debug.assert(slot != no_index)
|
||||
return result.storage.initializers.items[slot].fields[index]
|
||||
}
|
||||
|
||||
#! releases type, descriptor, and fallthrough storage using the allocator retained by check.
|
||||
deinit proc(result @mut Result) void {
|
||||
mem.free(result.storage.allocator, result.storage.types)
|
||||
arraylist.deinit(&result.storage.arrays)
|
||||
hashmap.deinit(&result.storage.fallthroughs)
|
||||
for (result.storage.records.items) |record| mem.free(result.storage.allocator, record.fields)
|
||||
arraylist.deinit(&result.storage.records)
|
||||
for (result.storage.initializers.items) |initializer| mem.free(result.storage.allocator, initializer.fields)
|
||||
arraylist.deinit(&result.storage.initializers)
|
||||
mem.free(result.storage.allocator, result.storage.slots)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -518,6 +589,7 @@ join proc(state @mut State, left, right ast.NodeId) void {
|
||||
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
|
||||
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
|
||||
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
|
||||
if (state.cells[a].expected.kind == .none) state.cells[a].expected = state.cells[b].expected
|
||||
state.cells[a].conversion_unsigned = state.cells[a].conversion_unsigned or state.cells[b].conversion_unsigned
|
||||
if (state.cells[a].literal == ast.NodeId(maxval!(u32))) state.cells[a].literal = state.cells[b].literal
|
||||
if (state.cells[a].producer == ast.NodeId(maxval!(u32))) state.cells[a].producer = state.cells[b].producer
|
||||
@@ -540,6 +612,12 @@ collect_invalid proc(state @mut State, id ast.NodeId) void {
|
||||
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
|
||||
state.cells[usize(id)].role = .annotation
|
||||
if (ast.expr_kind(state.tree, id) == .record_type) {
|
||||
type :: try check_record(state, id, false)
|
||||
state.types[usize(id)] = type
|
||||
state.cells[usize(id)].type = type
|
||||
return type
|
||||
}
|
||||
if (ast.expr_kind(state.tree, id) == .array_type) {
|
||||
view :: ast.array_type(state.tree, id)
|
||||
element := try check_annotation(state, view.element, true)
|
||||
@@ -582,6 +660,20 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
}
|
||||
.declaration: {
|
||||
target :: binding.data.declaration
|
||||
role :: state.cells[usize(target)].role
|
||||
if (role != .parameter and role != .loop_capture and ast.decl_kind(state.tree, target) == .statement) {
|
||||
initializer :: ast.statement_decl(state.tree, target).initializer
|
||||
if (ast.expr_kind(state.tree, initializer) == .record_type) {
|
||||
type = try check_record(state, initializer, true)
|
||||
} else {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
} else {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
}
|
||||
else: type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
state.types[usize(id)] = type
|
||||
@@ -593,6 +685,9 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
||||
collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
declaration :: ast.statement_decl(state.tree, id)
|
||||
state.cells[usize(id)].role = .declaration
|
||||
if (ast.expr_kind(state.tree, declaration.initializer) == .record_type) {
|
||||
state.cells[usize(declaration.initializer)].nominal = true
|
||||
}
|
||||
if (declaration.type) |type| {
|
||||
state.cells[usize(id)].fixed = true
|
||||
state.cells[usize(id)].type = try check_annotation(state, type, false)
|
||||
@@ -604,6 +699,11 @@ collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
||||
}
|
||||
try collect_expression(state, declaration.initializer)
|
||||
if (ast.expr_kind(state.tree, declaration.initializer) == .record_type
|
||||
and state.cells[usize(id)].type.kind != .invalid) {
|
||||
state.cells[usize(id)].fixed = true
|
||||
state.cells[usize(id)].type = state.cells[usize(declaration.initializer)].type
|
||||
}
|
||||
try depend(state, declaration.initializer, id)
|
||||
}
|
||||
|
||||
@@ -772,6 +872,19 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
|
||||
.identifier, .selection: {
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind == .none and ast.expr_kind(tree, id) == .selection) {
|
||||
base :: ast.selection_expr(tree, id).base
|
||||
try collect_expression(state, base)
|
||||
try depend(state, base, id)
|
||||
}
|
||||
if (binding.kind == .declaration) {
|
||||
target :: binding.data.declaration
|
||||
role :: state.cells[usize(target)].role
|
||||
if (role != .parameter and role != .loop_capture and ast.decl_kind(tree, target) == .statement
|
||||
and ast.expr_kind(tree, ast.statement_decl(tree, target).initializer) == .record_type) {
|
||||
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||
}
|
||||
}
|
||||
match binding.kind {
|
||||
.invalid: state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
||||
.builtin: state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||
@@ -839,6 +952,23 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
.array_type: {
|
||||
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||
}
|
||||
.record_type: {
|
||||
type := try check_record(state, id, state.cells[usize(id)].nominal)
|
||||
if (type.kind == .record) type.kind = .type_value
|
||||
state.cells[usize(id)].type = type
|
||||
}
|
||||
.record_literal: {
|
||||
state.cells[usize(id)].literal = id
|
||||
if (ast.record_literal(tree, id).type) |annotation| {
|
||||
state.cells[usize(id)].expected = try check_annotation(state, annotation, false)
|
||||
try depend(state, annotation, id)
|
||||
}
|
||||
for 0..ast.record_field_count(tree, id) |i| {
|
||||
value :: ast.field(tree, ast.record_field(tree, id, i)).value?
|
||||
try collect_expression(state, value)
|
||||
try depend(state, value, id)
|
||||
}
|
||||
}
|
||||
.array_literal: {
|
||||
state.cells[usize(id)].literal = id
|
||||
for 0..ast.array_element_count(tree, id) |i| {
|
||||
@@ -992,9 +1122,24 @@ numeric_constraint proc(state @mut State, expression, operand, operation ast.Nod
|
||||
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.types[usize(id)].kind == .invalid) return
|
||||
index :: root(state, usize(id))
|
||||
if (expected.kind == .record) {
|
||||
literal :: state.cells[index].literal
|
||||
if (literal == ast.NodeId(maxval!(u32)) or ast.expr_kind(state.tree, literal) != .record_literal) return
|
||||
if (ast.record_literal(state.tree, literal).type == null) {
|
||||
previous :: state.cells[index].expected
|
||||
if (previous.kind == .record and !same_type(state, previous, expected)) {
|
||||
try fail(state, literal, code, ast.node_span(state.tree, literal))
|
||||
return
|
||||
}
|
||||
state.cells[index].expected = expected
|
||||
}
|
||||
_ = try infer_record_literal(state, literal, index, false)
|
||||
return
|
||||
}
|
||||
if (expected.kind == .array) {
|
||||
literal :: state.cells[index].literal
|
||||
if (literal == ast.NodeId(maxval!(u32))) return
|
||||
if (ast.expr_kind(state.tree, literal) != .array_literal) return
|
||||
previous :: state.cells[index].expected
|
||||
if (previous.kind == .array and !same_type(state, previous, expected)) {
|
||||
if (!has_inferred_count(state, previous) and !has_inferred_count(state, expected)) {
|
||||
@@ -1050,6 +1195,11 @@ mutable_target proc(state @State, id ast.NodeId) bool {
|
||||
base :: state.types[usize(view.base)]
|
||||
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
|
||||
}
|
||||
if (ast.expr_kind(state.tree, id) == .selection
|
||||
and sema.binding(state.resolution, id).kind == .none) {
|
||||
view :: ast.selection_expr(state.tree, id)
|
||||
return state.types[usize(view.base)].kind == .record and mutable_target(state, view.base)
|
||||
}
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind != .declaration) return false
|
||||
target :: binding.data.declaration
|
||||
@@ -1178,6 +1328,18 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
||||
}
|
||||
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
|
||||
}
|
||||
.record_literal: {
|
||||
actual :: state.types[usize(id)]
|
||||
if (actual.kind != .record) return
|
||||
fields :: state.records.items[usize(actual.record_type)].fields
|
||||
slot :: state.slots[usize(id)]
|
||||
if (slot == no_index) return
|
||||
values :: state.initializers.items[slot].fields
|
||||
for fields |field, i| {
|
||||
if (values[i] != ast.NodeId(maxval!(u32))) try compatible(state, values[i], field.type)
|
||||
}
|
||||
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
|
||||
}
|
||||
.index: {
|
||||
view :: ast.index_expr(tree, id)
|
||||
base :: state.types[usize(view.base)]
|
||||
@@ -1334,6 +1496,7 @@ intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError
|
||||
if (existing.len == descriptor.len and existing.mutable == descriptor.mutable
|
||||
and existing.element.kind == descriptor.element.kind
|
||||
and existing.element.function == descriptor.element.function
|
||||
and existing.element.record_type == descriptor.element.record_type
|
||||
and existing.element.array_type == descriptor.element.array_type) {
|
||||
return Type{ kind = .array, array_type = ArrayId(i) }
|
||||
}
|
||||
@@ -1346,6 +1509,22 @@ intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError
|
||||
same_type proc(state @State, left, right Type) bool {
|
||||
if (left.kind != right.kind) return false
|
||||
if (left.kind == .function) return same_signature(state, left.function, right.function)
|
||||
if (left.kind == .record or left.kind == .type_value) {
|
||||
if (left.record_type == right.record_type) return true
|
||||
a :: state.records.items[usize(left.record_type)]
|
||||
b :: state.records.items[usize(right.record_type)]
|
||||
if (a.declaration != ast.NodeId(maxval!(u32)) or b.declaration != ast.NodeId(maxval!(u32))
|
||||
or a.fields.len != b.fields.len) return false
|
||||
for a.fields |field, i| {
|
||||
other :: b.fields[i]
|
||||
if ((field.name == null) != (other.name == null)) return false
|
||||
if (field.name) |name| {
|
||||
if (ast.name_id(state.tree, name) != ast.name_id(state.tree, other.name?)) return false
|
||||
}
|
||||
if (!same_type(state, field.type, other.type)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (left.kind != .array) return true
|
||||
a :: state.arrays.items[usize(left.array_type)]
|
||||
b :: state.arrays.items[usize(right.array_type)]
|
||||
@@ -1354,6 +1533,16 @@ same_type proc(state @State, left, right Type) bool {
|
||||
|
||||
@hide:file
|
||||
array_cells proc(state @State, type Type) ?usize {
|
||||
if (type.kind == .record) {
|
||||
total := usize(0)
|
||||
for state.records.items[usize(type.record_type)].fields |field| {
|
||||
child := usize(0)
|
||||
if (array_cells(state, field.type)) |value| child = value else return null
|
||||
if (total > maxval!(usize) - child) return null
|
||||
total += child
|
||||
}
|
||||
return total
|
||||
}
|
||||
if (type.kind != .array) return usize(1)
|
||||
view :: state.arrays.items[usize(type.array_type)]
|
||||
if (view.len == no_index) return null
|
||||
@@ -1366,7 +1555,7 @@ array_cells proc(state @State, type Type) ?usize {
|
||||
@hide:file
|
||||
array_origin proc(state @mut State, id ast.NodeId) ast.NodeId {
|
||||
origin :: state.cells[root(state, usize(id))].literal
|
||||
if (origin != ast.NodeId(maxval!(u32))) return origin
|
||||
if (origin != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, origin) == .array_literal) return origin
|
||||
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
|
||||
base :: array_origin(state, ast.index_expr(state.tree, id).base)
|
||||
if (base != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, base) != 0) {
|
||||
@@ -1396,8 +1585,8 @@ unify_elements proc(state @mut State, left, right ast.NodeId) void ! (mem.AllocE
|
||||
if (state.cells[left_root].numeric and state.cells[right_root].numeric) {
|
||||
try numeric_constraint(state, left, right, right)
|
||||
}
|
||||
if (left_type.kind == .array) try demand(state, right, left_type, .type_mismatch)
|
||||
if (right_type.kind == .array) try demand(state, left, right_type, .type_mismatch)
|
||||
if (left_type.kind == .array or left_type.kind == .record) try demand(state, right, left_type, .type_mismatch)
|
||||
if (right_type.kind == .array or right_type.kind == .record) try demand(state, left, right_type, .type_mismatch)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -1414,6 +1603,48 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
return group_type(state, id)
|
||||
}
|
||||
}
|
||||
if (state.cells[usize(id)].role == .expression
|
||||
and ast.expr_kind(state.tree, id) == .selection
|
||||
and sema.binding(state.resolution, id).kind == .none) {
|
||||
view :: ast.selection_expr(state.tree, id)
|
||||
base :: try infer_shape(state, view.base, defaults)
|
||||
if (base.kind == .record) {
|
||||
fields :: state.records.items[usize(base.record_type)].fields
|
||||
slot :: selected_field(state, fields, view.member)
|
||||
if (slot != no_index) {
|
||||
state.slots[usize(id)] = slot
|
||||
seed(state, id, fields[slot].type)
|
||||
return fields[slot].type
|
||||
}
|
||||
try fail(state, id, .unknown_field, ast.node_span(state.tree, view.member))
|
||||
} else if (base.kind == .invalid) {
|
||||
seed(state, id, base)
|
||||
} else if (base.kind != .none) {
|
||||
try fail(state, id, .not_record, ast.node_span(state.tree, view.base))
|
||||
}
|
||||
if (base.kind == .none) {
|
||||
origin :: state.cells[root(state, usize(view.base))].literal
|
||||
if (origin != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, origin) == .record_literal) {
|
||||
for 0..ast.record_field_count(state.tree, origin) |i| {
|
||||
field :: ast.field(state.tree, ast.record_field(state.tree, origin, i))
|
||||
matches := false
|
||||
if (field.name) |name| {
|
||||
matches = (
|
||||
ast.expr_kind(state.tree, view.member) == .identifier
|
||||
and ast.name_id(state.tree, name) == ast.name_id(state.tree, view.member)
|
||||
)
|
||||
} else if (constant_integer(state, view.member)) |ordinal| {
|
||||
matches = !ordinal.negative and ordinal.magnitude == u64(i)
|
||||
}
|
||||
if (!matches) continue
|
||||
child :: field.value?
|
||||
if (state.cells[root(state, usize(child))].numeric) try numeric_constraint(state, id, child, id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return group_type(state, id)
|
||||
}
|
||||
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)) {
|
||||
binding :: sema.binding(state.resolution, id)
|
||||
if (binding.kind == .declaration) {
|
||||
@@ -1431,6 +1662,9 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
||||
}
|
||||
}
|
||||
literal :: state.cells[index].literal
|
||||
if (literal != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal) == .record_literal) {
|
||||
return try infer_record_literal(state, literal, index, defaults)
|
||||
}
|
||||
if (literal != ast.NodeId(maxval!(u32))) {
|
||||
len :: ast.array_element_count(state.tree, literal)
|
||||
expected :: state.cells[index].expected
|
||||
@@ -1495,6 +1729,14 @@ prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (
|
||||
@hide:file
|
||||
apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
if (!assignment_values) {
|
||||
for 0..(state.records.items.len) |i| {
|
||||
record :: state.records.items[i]
|
||||
for record.fields |field| {
|
||||
if (field.default) |value| try context_demand(state, value, field.type, .type_mismatch, early)
|
||||
}
|
||||
}
|
||||
}
|
||||
for 0..(state.cells.len) |i| {
|
||||
id :: ast.NodeId(i)
|
||||
if (!assignment_values and state.cells[i].role == .declaration) {
|
||||
@@ -1535,7 +1777,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
||||
|
||||
@hide:file
|
||||
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (early and expected.kind != .array and !integer_kind(expected.kind)) return
|
||||
if (early and expected.kind != .array and expected.kind != .record and !integer_kind(expected.kind)) return
|
||||
try demand(state, id, expected, code)
|
||||
}
|
||||
|
||||
@@ -1654,3 +1896,220 @@ specialize_annotations proc(state @mut State) void ! (mem.AllocError | diagnosti
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
named_field proc(tree @ast.Tree, fields []RecordField, name ast.NodeId) usize {
|
||||
for fields |field, i| {
|
||||
if (field.name) |existing| {
|
||||
if (ast.name_id(tree, existing) == ast.name_id(tree, name)) return i
|
||||
}
|
||||
}
|
||||
return no_index
|
||||
}
|
||||
|
||||
@hide:file
|
||||
selected_field proc(state @State, fields []RecordField, member ast.NodeId) usize {
|
||||
if (ast.expr_kind(state.tree, member) == .identifier) return named_field(state.tree, fields, member)
|
||||
value := usize(0)
|
||||
for ast.source_text(state.tree, ast.node_span(state.tree, member)) |byte| {
|
||||
if (byte < '0' or byte > '9') return no_index
|
||||
digit :: usize(byte - '0')
|
||||
if (value > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
||||
value = value * 10 + digit
|
||||
}
|
||||
if (value >= fields.len or fields[value].name != null) return no_index
|
||||
return value
|
||||
}
|
||||
|
||||
@hide:file
|
||||
intern_record proc(state @mut State, declaration ast.NodeId, fields []mut RecordField) Type ! mem.AllocError {
|
||||
for state.records.items |existing, i| {
|
||||
if (existing.declaration != declaration or existing.fields.len != fields.len) continue
|
||||
equal := true
|
||||
same_defaults := true
|
||||
for fields |field, j| {
|
||||
slot := j
|
||||
if (field.name) |name| slot = named_field(state.tree, existing.fields, name)
|
||||
if (slot == no_index or (field.name == null and existing.fields[slot].name != null)
|
||||
or !same_type(state, field.type, existing.fields[slot].type)) {
|
||||
equal = false
|
||||
break
|
||||
}
|
||||
other_default :: existing.fields[slot].default
|
||||
if ((field.default == null) != (other_default == null)) {
|
||||
same_defaults = false
|
||||
} else if (field.default) |value| {
|
||||
if (value != other_default?) same_defaults = false
|
||||
}
|
||||
}
|
||||
if (equal and same_defaults) {
|
||||
mem.free(state.records.allocator, fields)
|
||||
return Type{ kind = .record, record_type = RecordId(i) }
|
||||
}
|
||||
if (equal) {
|
||||
for existing.fields |field, j| {
|
||||
if (field.name) |name| {
|
||||
slot :: named_field(state.tree, fields, name)
|
||||
saved :: fields[j]
|
||||
fields[j] = fields[slot]
|
||||
fields[slot] = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try arraylist.append(&state.records, RecordType{ declaration = declaration, fields = fields })
|
||||
return Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
|
||||
}
|
||||
|
||||
@hide:file
|
||||
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
cached := state.cells[usize(id)].type
|
||||
if (cached.kind == .record or cached.kind == .type_value) {
|
||||
cached.kind = .record
|
||||
return cached
|
||||
}
|
||||
if (cached.kind == .invalid) return cached
|
||||
if (state.cells[usize(id)].record_checking) {
|
||||
type :: invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
|
||||
state.cells[usize(id)].type = type
|
||||
return type
|
||||
}
|
||||
state.cells[usize(id)].record_checking = true
|
||||
defer state.cells[usize(id)].record_checking = false
|
||||
count :: ast.record_field_count(state.tree, id)
|
||||
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||
owned := true
|
||||
defer {
|
||||
if (owned) mem.free(state.records.allocator, fields)
|
||||
}
|
||||
failure := Type{}
|
||||
for 0..count |i| {
|
||||
node :: ast.record_field(state.tree, id, i)
|
||||
view :: ast.field(state.tree, node)
|
||||
type := try check_annotation(state, view.type?, false)
|
||||
if (type.kind == .void or type.kind == .range or type.kind == .type_value or type.kind == .none) {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_record_field_type, view.span))
|
||||
}
|
||||
if (view.name) |name| {
|
||||
if (named_field(state.tree, fields[0..i], name) != no_index) {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .duplicate_field, ast.node_span(state.tree, name)))
|
||||
}
|
||||
}
|
||||
fields[i] = RecordField{ name = view.name, type = type, default = view.value }
|
||||
if (type.kind == .invalid) failure = type
|
||||
}
|
||||
if (failure.kind == .invalid) {
|
||||
state.cells[usize(id)].type = failure
|
||||
return failure
|
||||
}
|
||||
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
|
||||
type :: try intern_record(state, declaration, fields)
|
||||
owned = false
|
||||
state.cells[usize(id)].type = type
|
||||
state.cells[usize(id)].record_checking = false
|
||||
saved_assignment :: state.collecting_assignment
|
||||
state.collecting_assignment = false
|
||||
defer state.collecting_assignment = saved_assignment
|
||||
for 0..count |i| {
|
||||
if (ast.field(state.tree, ast.record_field(state.tree, id, i)).value) |value| {
|
||||
try collect_expression(state, value)
|
||||
}
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
@hide:file
|
||||
infer_record_literal proc(state @mut State, literal ast.NodeId, index usize, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.types[usize(literal)].kind == .invalid) return state.types[usize(literal)]
|
||||
count :: ast.record_field_count(state.tree, literal)
|
||||
expected := state.cells[index].expected
|
||||
if (expected.kind == .none and state.cells[index].type.kind == .record) expected = state.cells[index].type
|
||||
if (expected.kind == .invalid) {
|
||||
seed(state, literal, expected)
|
||||
return expected
|
||||
}
|
||||
if (expected.kind != .none and expected.kind != .record) {
|
||||
try fail(state, literal, .expected_type, ast.node_span(state.tree, literal))
|
||||
return state.types[usize(literal)]
|
||||
}
|
||||
if (expected.kind == .none) {
|
||||
ready := true
|
||||
for 0..count |i| {
|
||||
value :: ast.field(state.tree, ast.record_field(state.tree, literal, i)).value?
|
||||
actual :: try infer_shape(state, value, defaults)
|
||||
if (actual.kind == .none) ready = false
|
||||
if (actual.kind == .invalid) {
|
||||
seed(state, literal, actual)
|
||||
return actual
|
||||
}
|
||||
}
|
||||
if (!ready) return Type{}
|
||||
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||
errdefer mem.free(state.records.allocator, fields)
|
||||
for 0..count |i| {
|
||||
view :: ast.field(state.tree, ast.record_field(state.tree, literal, i))
|
||||
type := group_type(state, view.value?)
|
||||
if (type.kind == .void or type.kind == .type_value or type.kind == .range) {
|
||||
try fail(state, literal, .invalid_record_field_type, view.span)
|
||||
mem.free(state.records.allocator, fields)
|
||||
return state.types[usize(literal)]
|
||||
}
|
||||
if (view.name) |name| {
|
||||
if (named_field(state.tree, fields[0..i], name) != no_index) {
|
||||
try fail(state, literal, .duplicate_field, ast.node_span(state.tree, name))
|
||||
mem.free(state.records.allocator, fields)
|
||||
return state.types[usize(literal)]
|
||||
}
|
||||
}
|
||||
fields[i] = RecordField{ name = view.name, type = type, default = null }
|
||||
}
|
||||
expected = try intern_record(state, ast.NodeId(maxval!(u32)), fields)
|
||||
}
|
||||
fields :: state.records.items[usize(expected.record_type)].fields
|
||||
slot := state.slots[usize(literal)]
|
||||
fresh :: slot == no_index
|
||||
if (slot == no_index) {
|
||||
values :: try mem.alloc(ast.NodeId, state.initializers.allocator, fields.len)
|
||||
errdefer mem.free(state.initializers.allocator, values)
|
||||
try arraylist.append(&state.initializers, RecordInitializer{ fields = values })
|
||||
slot = state.initializers.items.len - 1
|
||||
state.slots[usize(literal)] = slot
|
||||
}
|
||||
values :: state.initializers.items[slot].fields
|
||||
if (values.len != fields.len) {
|
||||
try fail(state, literal, .type_mismatch, ast.node_span(state.tree, literal))
|
||||
return state.types[usize(literal)]
|
||||
}
|
||||
mem.set(values, ast.NodeId(maxval!(u32)))
|
||||
for 0..count |i| {
|
||||
node :: ast.record_field(state.tree, literal, i)
|
||||
view :: ast.field(state.tree, node)
|
||||
field_slot := i
|
||||
if (view.name) |name| field_slot = named_field(state.tree, fields, name)
|
||||
if (field_slot >= fields.len) {
|
||||
code :: if (view.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
|
||||
try fail(state, literal, code, view.span)
|
||||
continue
|
||||
}
|
||||
if (values[field_slot] != ast.NodeId(maxval!(u32))) {
|
||||
try fail(state, literal, .duplicate_field, view.span)
|
||||
continue
|
||||
}
|
||||
values[field_slot] = view.value?
|
||||
try demand(state, view.value?, fields[field_slot].type, .type_mismatch)
|
||||
_ = try infer_shape(state, view.value?, defaults)
|
||||
}
|
||||
for fields |field, i| {
|
||||
if (values[i] != ast.NodeId(maxval!(u32))) continue
|
||||
if (field.default) |value| {
|
||||
values[i] = value
|
||||
if (fresh) try depend(state, value, literal)
|
||||
} else {
|
||||
code :: if (field.name == null) diagnostics.Code.tuple_arity_mismatch else diagnostics.Code.missing_field
|
||||
try fail(state, literal, code, ast.node_span(state.tree, literal))
|
||||
}
|
||||
}
|
||||
if (state.types[usize(literal)].kind == .invalid) return state.types[usize(literal)]
|
||||
state.cells[index].type = expected
|
||||
return expected
|
||||
}
|
||||
|
||||
+26
-12
@@ -443,20 +443,24 @@ resolve_expr proc(
|
||||
expression :: ast.selection_expr(tree, id)
|
||||
try resolve_expr(state, scope, expression.base)
|
||||
base :: state.bindings[usize(expression.base)]
|
||||
value := base
|
||||
if (base.kind != .invalid) {
|
||||
if (base.kind == .namespace) {
|
||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
||||
if hashmap.get(&namespace.names, ast.name_id(tree, expression.member)) |found| {
|
||||
value = found
|
||||
} else {
|
||||
value = Binding{ kind = .invalid, data = BindingData{
|
||||
diagnostic = try diagnostics.add(state.diagnostics, .unknown_name, ast.node_span(tree, expression.member)),
|
||||
} }
|
||||
}
|
||||
value := Binding{}
|
||||
if (base.kind == .invalid) {
|
||||
value = base
|
||||
} else if (base.kind == .namespace) {
|
||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
||||
found :: if (ast.expr_kind(tree, expression.member) == .identifier)
|
||||
hashmap.get(&namespace.names, ast.name_id(tree, expression.member))
|
||||
else
|
||||
null
|
||||
if (found) |member| {
|
||||
value = member
|
||||
} else {
|
||||
value = Binding{ kind = .invalid, data = BindingData{
|
||||
diagnostic = try diagnostics.add(state.diagnostics, .not_namespace, ast.node_span(tree, expression.base)),
|
||||
diagnostic = try diagnostics.add(
|
||||
state.diagnostics,
|
||||
.unknown_name,
|
||||
ast.node_span(tree, expression.member),
|
||||
),
|
||||
} }
|
||||
}
|
||||
}
|
||||
@@ -490,6 +494,16 @@ resolve_expr proc(
|
||||
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
||||
}
|
||||
}
|
||||
.record_type, .record_literal: {
|
||||
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
||||
}
|
||||
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)
|
||||
if (field.value) |value| try resolve_expr(state, scope, value)
|
||||
}
|
||||
}
|
||||
.index: {
|
||||
expression :: ast.index_expr(tree, id)
|
||||
try resolve_expr(state, scope, expression.base)
|
||||
|
||||
Reference in New Issue
Block a user