refactor: precompute compiler enum mappings
This commit is contained in:
@@ -243,6 +243,8 @@ AST nodes retain a compact 16-byte layout on the bootstrap target: a kind, three
|
|||||||
|
|
||||||
identifier interning is compilation-owned: initialize a `strpool.StringPool`, pass it to `lexer.scan` and `sema.resolve` before the diagnostic store, and release it after lowering. scanning interns identifier spellings once per pool; parsing preserves their IDs in compact AST storage, exposed through `ast.name_id`, while retaining source spans for diagnostics. name resolution keys scopes by those IDs and interns builtin names into the same pool, so identifier hashing and equality do not scan spelling bytes. IDs are meaningful only within their originating pool; independent compilations may assign the same numeric ID to different names. interning failures propagate as infrastructure errors rather than producing an invalid identifier ID. IR retains no pool dependency.
|
identifier interning is compilation-owned: initialize a `strpool.StringPool`, pass it to `lexer.scan` and `sema.resolve` before the diagnostic store, and release it after lowering. scanning interns identifier spellings once per pool; parsing preserves their IDs in compact AST storage, exposed through `ast.name_id`, while retaining source spans for diagnostics. name resolution keys scopes by those IDs and interns builtin names into the same pool, so identifier hashing and equality do not scan spelling bytes. IDs are meaningful only within their originating pool; independent compilations may assign the same numeric ID to different names. interning failures propagate as infrastructure errors rather than producing an invalid identifier ID. IR retains no pool dependency.
|
||||||
|
|
||||||
|
compiler token/operator mappings, scalar type metadata and backend spellings use compile-time `@std/enummap` tables; state-dependent layout and control-flow dispatch remain ordinary code. `enummap.init_filled(E, V, storage_default, fields)` initializes unused value slots so sparse maps can become owned compile-time constants without changing which keys are present. `enummap.get` still performs runtime linear lookup; static construction is not a constant-time lookup guarantee.
|
||||||
|
|
||||||
resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`, `get` and `deinit` with custom `HashMap` and `StringHashMap`. automatic keys support integers, booleans, enums, distinct types, fixed arrays and records recursively; record padding is ignored. slices, floating-point values, pointers, optionals and unions require explicit hashing and equality through `HashMap` (`StringHashMap` supplies content-based byte-string operations). keys and values are stored by value; referenced resources remain caller-owned. the builtin declaration count is inferred, and builtin ID storage derives its fixed stack-array length from that declaration array rather than repeating a count or initializer.
|
resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`, `get` and `deinit` with custom `HashMap` and `StringHashMap`. automatic keys support integers, booleans, enums, distinct types, fixed arrays and records recursively; record padding is ignored. slices, floating-point values, pointers, optionals and unions require explicit hashing and equality through `HashMap` (`StringHashMap` supplies content-based byte-string operations). keys and values are stored by value; referenced resources remain caller-owned. the builtin declaration count is inferred, and builtin ID storage derives its fixed stack-array length from that declaration array rather than repeating a count or initializer.
|
||||||
|
|
||||||
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
|
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
|
import "@std/enummap"
|
||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
|
|
||||||
#! owns output bytes and retains its allocator; release with deinit.
|
#! owns output bytes and retains its allocator; release with deinit.
|
||||||
@@ -161,19 +162,23 @@ append_text proc(renderer @mut Renderer, text []u8) void ! mem.AllocError {
|
|||||||
for (text) |byte| try arraylist.append(&renderer.bytes, byte)
|
for (text) |byte| try arraylist.append(&renderer.bytes, byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
assignment_operator_texts :: $enummap.init_filled(
|
||||||
|
ast.BinaryOperator, []u8, "", {
|
||||||
|
add = "+",
|
||||||
|
subtract = "-",
|
||||||
|
multiply = "*",
|
||||||
|
divide = "/",
|
||||||
|
bit_and = "&",
|
||||||
|
bit_or = "|",
|
||||||
|
bit_xor = "xor",
|
||||||
|
shift_left = "<<",
|
||||||
|
shift_right = ">>",
|
||||||
|
shift_left_saturating = "<<|",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
assignment_operator_text proc(operator ast.BinaryOperator) []u8 {
|
assignment_operator_text proc(operator ast.BinaryOperator) []u8 {
|
||||||
return match operator {
|
return enummap.get(&assignment_operator_texts, operator)?
|
||||||
.add: "+"
|
|
||||||
.subtract: "-"
|
|
||||||
.multiply: "*"
|
|
||||||
.divide: "/"
|
|
||||||
.bit_and: "&"
|
|
||||||
.bit_or: "|"
|
|
||||||
.bit_xor: "xor"
|
|
||||||
.shift_left: "<<"
|
|
||||||
.shift_right: ">>"
|
|
||||||
.shift_left_saturating: "<<|"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-34
@@ -1,6 +1,7 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
|
import "@std/enummap"
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
|
|
||||||
FunctionId :: distinct u32
|
FunctionId :: distinct u32
|
||||||
@@ -33,44 +34,51 @@ aggregate proc(type Type) bool {
|
|||||||
return type == .array or type == .record
|
return type == .array or type == .record
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
integer_widths :: $enummap.init_filled(Type, usize, usize(0), {
|
||||||
|
i8 = 8, u8 = 8,
|
||||||
|
i16 = 16, u16 = 16,
|
||||||
|
i32 = 32, u32 = 32,
|
||||||
|
int = 64, isize = 64, usize = 64, u64 = 64,
|
||||||
|
})
|
||||||
|
|
||||||
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
||||||
integer_bits proc(type Type) usize {
|
integer_bits proc(type Type) usize {
|
||||||
return match type {
|
if (enummap.get(&integer_widths, type)) |width| return width
|
||||||
.i8, .u8: 8
|
return 0
|
||||||
.i16, .u16: 16
|
|
||||||
.i32, .u32: 32
|
|
||||||
.int, .isize, .usize, .u64: 64
|
|
||||||
else: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned_integer proc(type Type) bool {
|
unsigned_integer proc(type Type) bool {
|
||||||
return type == .u8 or type == .u16 or type == .u32 or type == .u64 or type == .usize
|
return type == .u8 or type == .u16 or type == .u32 or type == .u64 or type == .usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
integer_maxima :: $enummap.init_filled(Type, u64, u64(0), {
|
||||||
|
i8 = 127,
|
||||||
|
i16 = 32767,
|
||||||
|
i32 = 2147483647,
|
||||||
|
int = u64(maxval!(i64)), isize = u64(maxval!(i64)),
|
||||||
|
u8 = 255,
|
||||||
|
u16 = 65535,
|
||||||
|
u32 = 4294967295,
|
||||||
|
u64 = maxval!(u64), usize = maxval!(u64),
|
||||||
|
})
|
||||||
|
|
||||||
integer_max proc(type Type) u64 {
|
integer_max proc(type Type) u64 {
|
||||||
return match type {
|
return enummap.get(&integer_maxima, type)?
|
||||||
.i8: 127
|
|
||||||
.i16: 32767
|
|
||||||
.i32: 2147483647
|
|
||||||
.int, .isize: u64(maxval!(i64))
|
|
||||||
.u8: 255
|
|
||||||
.u16: 65535
|
|
||||||
.u32: 4294967295
|
|
||||||
.u64, .usize: maxval!(u64)
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
integer_minima :: $enummap.init_filled(Type, i64, i64(0), {
|
||||||
|
i8 = i64(-128),
|
||||||
|
i16 = i64(-32768),
|
||||||
|
i32 = i64(-2147483648),
|
||||||
|
int = minval!(i64), isize = minval!(i64),
|
||||||
|
u8 = 0, u16 = 0, u32 = 0, u64 = 0, usize = 0,
|
||||||
|
})
|
||||||
|
|
||||||
integer_min proc(type Type) i64 {
|
integer_min proc(type Type) i64 {
|
||||||
return match type {
|
return enummap.get(&integer_minima, type)?
|
||||||
.i8: -128
|
|
||||||
.i16: -32768
|
|
||||||
.i32: -2147483648
|
|
||||||
.int, .isize: minval!(i64)
|
|
||||||
.u8, .u16, .u32, .u64, .usize: 0
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueData :: union {
|
ValueData :: union {
|
||||||
@@ -412,24 +420,32 @@ aligned_size proc(size, alignment usize) usize {
|
|||||||
return divtrunc!(size + alignment - 1, alignment) * alignment
|
return divtrunc!(size + alignment - 1, alignment) * alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
scalar_alignments :: $enummap.init_filled(Type, usize, usize(1), {
|
||||||
|
float = 8, string = 8, function = 8, pointer = 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
scalar_sizes :: $enummap.init_filled(Type, usize, usize(0), {
|
||||||
|
float = 8, function = 8, pointer = 8,
|
||||||
|
string = 16,
|
||||||
|
bool = 1,
|
||||||
|
void = 0,
|
||||||
|
})
|
||||||
|
|
||||||
storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
|
storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
|
||||||
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
|
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
|
||||||
bits :: integer_bits(type)
|
bits :: integer_bits(type)
|
||||||
if (bits != 0) return divtrunc!(bits, 8)
|
if (bits != 0) return divtrunc!(bits, 8)
|
||||||
return if (type == .float or type == .string or type == .function or type == .pointer) 8 else 1
|
if (enummap.get(&scalar_alignments, type)) |alignment| return alignment
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
storage_size proc(module @Module, type Type, shape ArrayId) usize {
|
storage_size proc(module @Module, type Type, shape ArrayId) usize {
|
||||||
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
|
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
|
||||||
bits :: integer_bits(type)
|
bits :: integer_bits(type)
|
||||||
if (bits != 0) return divtrunc!(bits, 8)
|
if (bits != 0) return divtrunc!(bits, 8)
|
||||||
return match type {
|
return enummap.get(&scalar_sizes, type)?
|
||||||
.float, .function, .pointer: 8
|
|
||||||
.string: 16
|
|
||||||
.bool: 1
|
|
||||||
.void: 0
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#! loads a scalar or borrows an aggregate from byte cells without allocating.
|
#! loads a scalar or borrows an aggregate from byte cells without allocating.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/strmap"
|
import "@std/strmap"
|
||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
|
import "@std/enummap"
|
||||||
|
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
import "@source/strpool"
|
import "@source/strpool"
|
||||||
@@ -474,12 +475,15 @@ is_digit proc(char u8) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
diagnostic_codes :: $enummap.init(ErrorCode, diagnostics.Code, {
|
||||||
|
float_must_end_with_digit = .float_must_end_with_digit,
|
||||||
|
unterminated_string = .unterminated_string,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
diagnostic_code proc(code ErrorCode) diagnostics.Code {
|
diagnostic_code proc(code ErrorCode) diagnostics.Code {
|
||||||
return match code {
|
return enummap.get(&diagnostic_codes, code)?
|
||||||
.float_must_end_with_digit: .float_must_end_with_digit
|
|
||||||
.unterminated_string: .unterminated_string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -1,7 +1,64 @@
|
|||||||
import "@std/io"
|
import "@std/io"
|
||||||
|
import "@std/enummap"
|
||||||
import "@source/ir"
|
import "@source/ir"
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
bitwise_opcodes :: $enummap.init_filled(ir.Op, []u8, "", {
|
||||||
|
bit_and = "and",
|
||||||
|
bit_or = "or",
|
||||||
|
bit_xor = "xor",
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
float_opcodes :: $enummap.init_filled(ir.Op, []u8, "", {
|
||||||
|
add = "fadd",
|
||||||
|
subtract = "fsub",
|
||||||
|
multiply = "fmul",
|
||||||
|
divide = "fdiv",
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
integer_opcodes :: $enummap.init_filled(ir.Op, []u8, "", {
|
||||||
|
add = "add",
|
||||||
|
subtract = "sub",
|
||||||
|
multiply = "mul",
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
float_predicates :: $enummap.init_filled(ir.Op, []u8, "", {
|
||||||
|
equal = "oeq",
|
||||||
|
not_equal = "une",
|
||||||
|
less = "olt",
|
||||||
|
less_equal = "ole",
|
||||||
|
greater = "ogt",
|
||||||
|
greater_equal = "oge",
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
integer_predicates :: $enummap.init_filled(ir.Op, []u8, "", {
|
||||||
|
equal = "eq",
|
||||||
|
not_equal = "ne",
|
||||||
|
less = "lt",
|
||||||
|
less_equal = "le",
|
||||||
|
greater = "gt",
|
||||||
|
greater_equal = "ge",
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
conversion_upper_bounds :: $enummap.init_filled(ir.Type, []u8, "", {
|
||||||
|
i8 = "1.280000e+02",
|
||||||
|
i16 = "3.276800e+04",
|
||||||
|
i32 = "2.147483648e+09",
|
||||||
|
int = "9.223372036854775808e+18",
|
||||||
|
isize = "9.223372036854775808e+18",
|
||||||
|
u8 = "2.560000e+02",
|
||||||
|
u16 = "6.553600e+04",
|
||||||
|
u32 = "4.294967296e+09",
|
||||||
|
u64 = "1.8446744073709551616e+19",
|
||||||
|
usize = "1.8446744073709551616e+19",
|
||||||
|
})
|
||||||
|
|
||||||
@hide
|
@hide
|
||||||
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
|
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
|
||||||
output :: state.output
|
output :: state.output
|
||||||
@@ -311,12 +368,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
.bit_and, .bit_or, .bit_xor: {
|
.bit_and, .bit_or, .bit_xor: {
|
||||||
left :: try load(state, instruction.left)
|
left :: try load(state, instruction.left)
|
||||||
right :: try load(state, instruction.right)
|
right :: try load(state, instruction.right)
|
||||||
opcode []u8 :: match instruction.op {
|
opcode :: enummap.get(&bitwise_opcodes, instruction.op)?
|
||||||
.bit_and: "and"
|
|
||||||
.bit_or: "or"
|
|
||||||
.bit_xor: "xor"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
|
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
|
||||||
}
|
}
|
||||||
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
|
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
|
||||||
@@ -324,13 +376,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
left :: try load(state, instruction.left)
|
left :: try load(state, instruction.left)
|
||||||
right :: try load(state, instruction.right)
|
right :: try load(state, instruction.right)
|
||||||
if (instruction.type == .float) {
|
if (instruction.type == .float) {
|
||||||
opcode []u8 :: match instruction.op {
|
opcode :: enummap.get(&float_opcodes, instruction.op)?
|
||||||
.add: "fadd"
|
|
||||||
.subtract: "fsub"
|
|
||||||
.multiply: "fmul"
|
|
||||||
.divide: "fdiv"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
|
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
|
||||||
} else if (instruction.op == .divide) {
|
} else if (instruction.op == .divide) {
|
||||||
bits :: ir.integer_bits(instruction.type)
|
bits :: ir.integer_bits(instruction.type)
|
||||||
@@ -349,12 +395,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
|
opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
|
||||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
|
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
|
||||||
} else {
|
} else {
|
||||||
opcode []u8 :: match instruction.op {
|
opcode :: enummap.get(&integer_opcodes, instruction.op)?
|
||||||
.add: "add"
|
|
||||||
.subtract: "sub"
|
|
||||||
.multiply: "mul"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
bits :: ir.integer_bits(instruction.type)
|
bits :: ir.integer_bits(instruction.type)
|
||||||
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
|
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
|
||||||
pair :: fresh(state)
|
pair :: fresh(state)
|
||||||
@@ -370,15 +411,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
right :: try load(state, instruction.right)
|
right :: try load(state, instruction.right)
|
||||||
operand_type :: state.values[usize(instruction.left)]
|
operand_type :: state.values[usize(instruction.left)]
|
||||||
if (operand_type.kind == .float) {
|
if (operand_type.kind == .float) {
|
||||||
predicate []u8 :: match instruction.op {
|
predicate :: enummap.get(&float_predicates, instruction.op)?
|
||||||
.equal: "oeq"
|
|
||||||
.not_equal: "une"
|
|
||||||
.less: "olt"
|
|
||||||
.less_equal: "ole"
|
|
||||||
.greater: "ogt"
|
|
||||||
.greater_equal: "oge"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right})
|
try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right})
|
||||||
} else {
|
} else {
|
||||||
prefix []u8 := ""
|
prefix []u8 := ""
|
||||||
@@ -386,15 +419,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
if (ir.unsigned_integer(operand_type.kind)) prefix = "u"
|
if (ir.unsigned_integer(operand_type.kind)) prefix = "u"
|
||||||
else prefix = "s"
|
else prefix = "s"
|
||||||
}
|
}
|
||||||
predicate []u8 :: match instruction.op {
|
predicate :: enummap.get(&integer_predicates, instruction.op)?
|
||||||
.equal: "eq"
|
|
||||||
.not_equal: "ne"
|
|
||||||
.less: "lt"
|
|
||||||
.less_equal: "le"
|
|
||||||
.greater: "gt"
|
|
||||||
.greater_equal: "ge"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate})
|
try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate})
|
||||||
try write_type(output, state.module, operand_type)
|
try write_type(output, state.module, operand_type)
|
||||||
try io.print(" %r{}, %r{}\n", output, {left, right})
|
try io.print(" %r{}, %r{}\n", output, {left, right})
|
||||||
@@ -644,17 +669,7 @@ emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.Wr
|
|||||||
upper_valid :: fresh(state)
|
upper_valid :: fresh(state)
|
||||||
valid :: fresh(state)
|
valid :: fresh(state)
|
||||||
failed :: fresh(state)
|
failed :: fresh(state)
|
||||||
upper []u8 :: match destination {
|
upper :: enummap.get(&conversion_upper_bounds, destination)?
|
||||||
.i8: "1.280000e+02"
|
|
||||||
.i16: "3.276800e+04"
|
|
||||||
.i32: "2.147483648e+09"
|
|
||||||
.int, .isize: "9.223372036854775808e+18"
|
|
||||||
.u8: "2.560000e+02"
|
|
||||||
.u16: "6.553600e+04"
|
|
||||||
.u32: "4.294967296e+09"
|
|
||||||
.u64, .usize: "1.8446744073709551616e+19"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
try io.print(" %r{} = call double @llvm.trunc.f64(double %r{})\n", output, {truncated, operand})
|
try io.print(" %r{} = call double @llvm.trunc.f64(double %r{})\n", output, {truncated, operand})
|
||||||
if (destination_unsigned) try io.print(" %r{} = fcmp oge double %r{}, 0.0\n", output, {lower_valid, truncated})
|
if (destination_unsigned) try io.print(" %r{} = fcmp oge double %r{}, 0.0\n", output, {lower_valid, truncated})
|
||||||
else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper})
|
else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper})
|
||||||
|
|||||||
+33
-23
@@ -211,13 +211,19 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
abi_extensions :: $enummap.init_filled(ir.Type, []u8, "", {
|
||||||
|
i8 = "signext",
|
||||||
|
i16 = "signext",
|
||||||
|
u8 = "zeroext",
|
||||||
|
u16 = "zeroext",
|
||||||
|
bool = "zeroext",
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
abi_extension proc(kind ir.Type) []u8 {
|
abi_extension proc(kind ir.Type) []u8 {
|
||||||
return match kind {
|
if (enummap.get(&abi_extensions, kind)) |extension| return extension
|
||||||
.i8, .i16: "signext"
|
return ""
|
||||||
.u8, .u16, .bool: "zeroext"
|
|
||||||
else: ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide
|
||||||
@@ -495,34 +501,38 @@ check_local proc(state @State, slot u32, read bool, span diagnostics.Span) void
|
|||||||
try io.print(" call void @check_init(ptr %i{}, i1 {}, i64 {}, i64 {}, i64 {})\n", state.output, {slot, read, usize(span.source), position.line, position.column})
|
try io.print(" call void @check_init(ptr %i{}, i1 {}, i64 {}, i64 {}, i64 {})\n", state.output, {slot, read, usize(span.source), position.line, position.column})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
memory_symbols :: $enummap.init_filled(diagnostics.Code, []u8, "", {
|
||||||
|
null_unwrap = "null_unwrap",
|
||||||
|
invalid_slice_bounds = "invalid_slice_bounds",
|
||||||
|
sentinel_mismatch = "sentinel_mismatch",
|
||||||
|
overlapping_memory_copy = "overlapping_memory_copy",
|
||||||
|
unsupported_foreign_call = "unsupported_foreign_call",
|
||||||
|
invalid_pointer_access = "invalid_pointer_access",
|
||||||
|
division_by_zero = "division_by_zero",
|
||||||
|
invalid_shift_count = "invalid_shift_count",
|
||||||
|
})
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
trap_symbols :: $enummap.init_filled(diagnostics.Code, []u8, "", {
|
||||||
|
integer_overflow = "overflow",
|
||||||
|
index_out_of_bounds = "bounds",
|
||||||
|
inactive_union_field = "inactive_union",
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
memory_symbol proc(code diagnostics.Code) []u8 {
|
memory_symbol proc(code diagnostics.Code) []u8 {
|
||||||
return match code {
|
return enummap.get(&memory_symbols, code)?
|
||||||
.null_unwrap: "null_unwrap"
|
|
||||||
.invalid_slice_bounds: "invalid_slice_bounds"
|
|
||||||
.sentinel_mismatch: "sentinel_mismatch"
|
|
||||||
.overlapping_memory_copy: "overlapping_memory_copy"
|
|
||||||
.unsupported_foreign_call: "unsupported_foreign_call"
|
|
||||||
.invalid_pointer_access: "invalid_pointer_access"
|
|
||||||
.division_by_zero: "division_by_zero"
|
|
||||||
.invalid_shift_count: "invalid_shift_count"
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
|
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
|
||||||
position :: location(state.line_starts, span)
|
position :: location(state.line_starts, span)
|
||||||
symbol []u8 :: match code {
|
symbol :: enummap.get(&trap_symbols, code)
|
||||||
.integer_overflow: "overflow"
|
|
||||||
.index_out_of_bounds: "bounds"
|
|
||||||
.inactive_union_field: "inactive_union"
|
|
||||||
else: ""
|
|
||||||
}
|
|
||||||
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
|
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
|
||||||
length :: message.len
|
length :: message.len
|
||||||
try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, i64 {}, ptr @", state.output, {usize(span.source), position.line, position.column})
|
try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, i64 {}, ptr @", state.output, {usize(span.source), position.line, position.column})
|
||||||
if (symbol.len != 0) try io.write_all(state.output, symbol)
|
if (symbol) |name| try io.write_all(state.output, name)
|
||||||
else try io.print("memory_{s}", state.output, {memory_symbol(code)})
|
else try io.print("memory_{s}", state.output, {memory_symbol(code)})
|
||||||
try io.print(", i64 {})\n unreachable\n", state.output, {length})
|
try io.print(", i64 {})\n unreachable\n", state.output, {length})
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-48
@@ -1,6 +1,7 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
import "@std/strconv"
|
import "@std/strconv"
|
||||||
|
import "@std/enummap"
|
||||||
import "@ffi/c"
|
import "@ffi/c"
|
||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
@@ -181,35 +182,43 @@ lower_module proc(
|
|||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
base_types :: $enummap.init_filled(
|
||||||
|
typecheck.TypeKind, ir.Type, ir.Type.invalid, {
|
||||||
|
int = .int,
|
||||||
|
usize = .usize,
|
||||||
|
i8 = .i8,
|
||||||
|
i16 = .i16,
|
||||||
|
i32 = .i32,
|
||||||
|
isize = .isize,
|
||||||
|
u8 = .u8,
|
||||||
|
u16 = .u16,
|
||||||
|
u32 = .u32,
|
||||||
|
u64 = .u64,
|
||||||
|
array = .array,
|
||||||
|
record = .record,
|
||||||
|
pointer = .pointer,
|
||||||
|
float = .float,
|
||||||
|
bool = .bool,
|
||||||
|
string = .string,
|
||||||
|
void = .void,
|
||||||
|
function = .function,
|
||||||
|
none = .invalid,
|
||||||
|
invalid = .invalid,
|
||||||
|
range = .invalid,
|
||||||
|
type_value = .invalid,
|
||||||
|
null = .invalid,
|
||||||
|
anyopaque = .invalid,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
lower_type proc(state @State, type typecheck.Type) ir.Type {
|
lower_type proc(state @State, type typecheck.Type) ir.Type {
|
||||||
if (type.kind == .enumeration) return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
|
if (type.kind == .enumeration) return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
|
||||||
if (type.kind == .slice) return .record
|
if (type.kind == .slice) return .record
|
||||||
if (type.kind == .fallible) return .record
|
if (type.kind == .fallible) return .record
|
||||||
if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element)
|
if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element)
|
||||||
return match type.kind {
|
return enummap.get(&base_types, type.kind)?
|
||||||
.int: .int
|
|
||||||
.usize: .usize
|
|
||||||
.i8: .i8
|
|
||||||
.i16: .i16
|
|
||||||
.i32: .i32
|
|
||||||
.isize: .isize
|
|
||||||
.u8: .u8
|
|
||||||
.u16: .u16
|
|
||||||
.u32: .u32
|
|
||||||
.u64: .u64
|
|
||||||
.array: .array
|
|
||||||
.record: .record
|
|
||||||
.pointer: .pointer
|
|
||||||
.float: .float
|
|
||||||
.bool: .bool
|
|
||||||
.string: .string
|
|
||||||
.void: .void
|
|
||||||
.function: .function
|
|
||||||
.enumeration: unreachable
|
|
||||||
.none, .invalid, .range, .type_value, .null, .anyopaque: .invalid
|
|
||||||
.slice, .optional, .fallible: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -1046,27 +1055,40 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
binary_ops :: $enummap.init_filled(
|
||||||
|
ast.BinaryOperator, ir.Op, ir.Op.constant, {
|
||||||
|
add = .add,
|
||||||
|
subtract = .subtract,
|
||||||
|
multiply = .multiply,
|
||||||
|
divide = .divide,
|
||||||
|
bit_and = .bit_and,
|
||||||
|
bit_or = .bit_or,
|
||||||
|
bit_xor = .bit_xor,
|
||||||
|
shift_left = .shift_left,
|
||||||
|
shift_right = .shift_right,
|
||||||
|
shift_left_saturating = .shift_left_saturating,
|
||||||
|
equal = .equal,
|
||||||
|
not_equal = .not_equal,
|
||||||
|
less = .less,
|
||||||
|
less_equal = .less_equal,
|
||||||
|
greater = .greater,
|
||||||
|
greater_equal = .greater_equal,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
unary_ops :: $enummap.init_filled(
|
||||||
|
ast.UnaryOperator, ir.Op, ir.Op.constant, {
|
||||||
|
negate = .negate,
|
||||||
|
logical_not = .logical_not,
|
||||||
|
bit_not = .bit_not,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
binary_op proc(operator ast.BinaryOperator) ir.Op {
|
binary_op proc(operator ast.BinaryOperator) ir.Op {
|
||||||
return match operator {
|
return enummap.get(&binary_ops, operator)?
|
||||||
.add: .add
|
|
||||||
.subtract: .subtract
|
|
||||||
.multiply: .multiply
|
|
||||||
.divide: .divide
|
|
||||||
.bit_and: .bit_and
|
|
||||||
.bit_or: .bit_or
|
|
||||||
.bit_xor: .bit_xor
|
|
||||||
.shift_left: .shift_left
|
|
||||||
.shift_right: .shift_right
|
|
||||||
.shift_left_saturating: .shift_left_saturating
|
|
||||||
.equal: .equal
|
|
||||||
.not_equal: .not_equal
|
|
||||||
.less: .less
|
|
||||||
.less_equal: .less_equal
|
|
||||||
.greater: .greater
|
|
||||||
.greater_equal: .greater_equal
|
|
||||||
.logical_and, .logical_or: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -2249,12 +2271,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
}
|
}
|
||||||
operand :: try lower_expression(state, expression.operand)
|
operand :: try lower_expression(state, expression.operand)
|
||||||
if (state.terminated or trap_type(state, id)) return ir.no_value
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
op ir.Op :: match expression.operator {
|
op :: enummap.get(&unary_ops, expression.operator)?
|
||||||
.negate: .negate
|
|
||||||
.logical_not: .logical_not
|
|
||||||
.bit_not: .bit_not
|
|
||||||
.address_of, .dereference, .unwrap: unreachable
|
|
||||||
}
|
|
||||||
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
|
|||||||
+97
-61
@@ -1,5 +1,6 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
|
import "@std/enummap"
|
||||||
|
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
import "@source/lexer"
|
import "@source/lexer"
|
||||||
@@ -937,24 +938,32 @@ parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.Nod
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
type_starters :: $enummap.init_filled(lexer.TokenKind, bool, false, {
|
||||||
|
ident = true, open_bracket = true, struct = true, c_struct = true, opaque = true,
|
||||||
|
enum = true, union = true, distinct = true, alias = true, match = true, if = true,
|
||||||
|
at = true, star = true, question = true, open_paren = true, proc = true, c_func = true,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
starts_type proc(kind lexer.TokenKind) bool {
|
starts_type proc(kind lexer.TokenKind) bool {
|
||||||
return (
|
return enummap.get(&type_starters, kind) != null
|
||||||
kind == .ident or kind == .open_bracket or kind == .struct or kind == .c_struct or kind == .opaque
|
|
||||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias or kind == .match
|
|
||||||
or kind == .if
|
|
||||||
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc or kind == .c_func
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
member_names :: $enummap.init_filled(lexer.TokenKind, bool, false, {
|
||||||
|
ident = true, if = true, else = true, for = true, inline = true, while = true,
|
||||||
|
proc = true, return = true, yield = true, break = true, continue = true,
|
||||||
|
true = true, false = true, and = true, or = true, xor = true, mut = true,
|
||||||
|
struct = true, c_struct = true, opaque = true, enum = true, union = true,
|
||||||
|
distinct = true, alias = true, match = true, import = true, test = true,
|
||||||
|
c_func = true, null = true, undefined = true, try = true, catch = true,
|
||||||
|
defer = true, errdefer = true,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
member_name proc(kind lexer.TokenKind) bool {
|
member_name proc(kind lexer.TokenKind) bool {
|
||||||
return match kind {
|
return enummap.get(&member_names, kind) != null
|
||||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .yield, .break, .continue,
|
|
||||||
.true, .false, .and, .or, .xor, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
|
|
||||||
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
|
|
||||||
else: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -2112,52 +2121,77 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
|
|||||||
return .unexpected_token
|
return .unexpected_token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
prefix_binding_powers :: $enummap.init_filled(
|
||||||
|
lexer.TokenKind, BindingPowerScalar, BindingPowerScalar(0), {
|
||||||
|
minus = BindingPowerScalar(50), bang = BindingPowerScalar(50), tilde = BindingPowerScalar(50),
|
||||||
|
ampersand = BindingPowerScalar(50), try = BindingPowerScalar(50),
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||||
return match kind {
|
return enummap.get(&prefix_binding_powers, kind)
|
||||||
.minus, .bang, .tilde, .ampersand, .try: BindingPowerScalar(50)
|
|
||||||
else: null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
infix_binding_powers :: $enummap.init_filled(lexer.TokenKind, BindingPower,
|
||||||
|
BindingPower{ left = BindingPowerScalar(0), right = BindingPowerScalar(0) },
|
||||||
|
{
|
||||||
|
or = BindingPower{ left = BindingPowerScalar(2), right = BindingPowerScalar(3) },
|
||||||
|
and = BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) },
|
||||||
|
equal_equal = BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) },
|
||||||
|
not_equal = BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) },
|
||||||
|
less = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
|
||||||
|
less_equal = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
|
||||||
|
greater = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
|
||||||
|
greater_equal = BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) },
|
||||||
|
ampersand = BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) },
|
||||||
|
xor = BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) },
|
||||||
|
pipe = BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) },
|
||||||
|
shift_left = BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) },
|
||||||
|
shift_right = BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) },
|
||||||
|
shift_left_saturating = BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) },
|
||||||
|
plus = BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) },
|
||||||
|
minus = BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) },
|
||||||
|
star = BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) },
|
||||||
|
slash = BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
||||||
return match kind {
|
return enummap.get(&infix_binding_powers, kind)
|
||||||
.or: BindingPower{ left = BindingPowerScalar(2), right = BindingPowerScalar(3) }
|
|
||||||
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
|
|
||||||
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
|
||||||
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
|
||||||
.ampersand, .xor, .pipe: BindingPower{ left = BindingPowerScalar(22), right = BindingPowerScalar(23) }
|
|
||||||
.shift_left, .shift_right, .shift_left_saturating: BindingPower{ left = BindingPowerScalar(25), right = BindingPowerScalar(26) }
|
|
||||||
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
|
|
||||||
.star, .slash: BindingPower{ left = BindingPowerScalar(40), right = BindingPowerScalar(41) }
|
|
||||||
else: null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
binary_operators :: $enummap.init_filled(
|
||||||
|
lexer.TokenKind,
|
||||||
|
ast.BinaryOperator,
|
||||||
|
ast.BinaryOperator.add, {
|
||||||
|
and = .logical_and,
|
||||||
|
or = .logical_or,
|
||||||
|
plus = .add,
|
||||||
|
minus = .subtract,
|
||||||
|
star = .multiply,
|
||||||
|
slash = .divide,
|
||||||
|
ampersand = .bit_and,
|
||||||
|
pipe = .bit_or,
|
||||||
|
xor = .bit_xor,
|
||||||
|
shift_left = .shift_left,
|
||||||
|
shift_right = .shift_right,
|
||||||
|
shift_left_saturating = .shift_left_saturating,
|
||||||
|
equal_equal = .equal,
|
||||||
|
not_equal = .not_equal,
|
||||||
|
less = .less,
|
||||||
|
less_equal = .less_equal,
|
||||||
|
greater = .greater,
|
||||||
|
greater_equal = .greater_equal,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
||||||
return match kind {
|
return enummap.get(&binary_operators, kind)?
|
||||||
.and: .logical_and
|
|
||||||
.or: .logical_or
|
|
||||||
.plus: .add
|
|
||||||
.minus: .subtract
|
|
||||||
.star: .multiply
|
|
||||||
.slash: .divide
|
|
||||||
.ampersand: .bit_and
|
|
||||||
.pipe: .bit_or
|
|
||||||
.xor: .bit_xor
|
|
||||||
.shift_left: .shift_left
|
|
||||||
.shift_right: .shift_right
|
|
||||||
.shift_left_saturating: .shift_left_saturating
|
|
||||||
.equal_equal: .equal
|
|
||||||
.not_equal: .not_equal
|
|
||||||
.less: .less
|
|
||||||
.less_equal: .less_equal
|
|
||||||
.greater: .greater
|
|
||||||
.greater_equal: .greater_equal
|
|
||||||
else: unreachable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -2165,19 +2199,21 @@ is_assignment_operator proc(kind lexer.TokenKind) bool {
|
|||||||
return kind == .equal or assignment_operator(kind) != null
|
return kind == .equal or assignment_operator(kind) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
assignment_operators :: $enummap.init_filled(lexer.TokenKind, ast.BinaryOperator, ast.BinaryOperator.add, {
|
||||||
|
plus_equal = .add,
|
||||||
|
minus_equal = .subtract,
|
||||||
|
star_equal = .multiply,
|
||||||
|
slash_equal = .divide,
|
||||||
|
ampersand_equal = .bit_and,
|
||||||
|
pipe_equal = .bit_or,
|
||||||
|
xor_equal = .bit_xor,
|
||||||
|
shift_left_equal = .shift_left,
|
||||||
|
shift_right_equal = .shift_right,
|
||||||
|
shift_left_saturating_equal = .shift_left_saturating,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
assignment_operator proc(kind lexer.TokenKind) ?ast.BinaryOperator {
|
assignment_operator proc(kind lexer.TokenKind) ?ast.BinaryOperator {
|
||||||
return match kind {
|
return enummap.get(&assignment_operators, kind)
|
||||||
.plus_equal: .add
|
|
||||||
.minus_equal: .subtract
|
|
||||||
.star_equal: .multiply
|
|
||||||
.slash_equal: .divide
|
|
||||||
.ampersand_equal: .bit_and
|
|
||||||
.pipe_equal: .bit_or
|
|
||||||
.xor_equal: .bit_xor
|
|
||||||
.shift_left_equal: .shift_left
|
|
||||||
.shift_right_equal: .shift_right
|
|
||||||
.shift_left_saturating_equal: .shift_left_saturating
|
|
||||||
else: null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-34
@@ -3,6 +3,7 @@ import "@std/mem"
|
|||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
import "@std/hashmap"
|
import "@std/hashmap"
|
||||||
import "@std/strconv"
|
import "@std/strconv"
|
||||||
|
import "@std/enummap"
|
||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
sema :: import "@source/sema"
|
sema :: import "@source/sema"
|
||||||
@@ -1521,35 +1522,38 @@ numeric_kind proc(kind TypeKind) bool {
|
|||||||
return integer_kind(kind) or kind == .float
|
return integer_kind(kind) or kind == .float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
builtin_kinds :: $enummap.init(sema.BuiltinType, TypeKind, {
|
||||||
|
int = .int, i64 = .int,
|
||||||
|
float = .float, f64 = .float,
|
||||||
|
i8 = .i8,
|
||||||
|
i16 = .i16,
|
||||||
|
i32 = .i32,
|
||||||
|
isize = .isize,
|
||||||
|
u8 = .u8,
|
||||||
|
u16 = .u16,
|
||||||
|
u32 = .u32,
|
||||||
|
u64 = .u64, uint = .u64,
|
||||||
|
usize = .usize,
|
||||||
|
bool = .bool,
|
||||||
|
string = .string,
|
||||||
|
void = .void,
|
||||||
|
anyopaque = .anyopaque,
|
||||||
|
type = .type_value,
|
||||||
|
c_int = .i32,
|
||||||
|
c_uint = .u32,
|
||||||
|
c_long = .int, c_longlong = .int,
|
||||||
|
c_ulong = .u64, c_ulonglong = .u64,
|
||||||
|
c_char = .i8,
|
||||||
|
c_uchar = .u8,
|
||||||
|
c_short = .i16,
|
||||||
|
c_ushort = .u16,
|
||||||
|
unavailable = .none,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
builtin_kind proc(type sema.BuiltinType) TypeKind {
|
builtin_kind proc(type sema.BuiltinType) TypeKind {
|
||||||
return match type {
|
return enummap.get(&builtin_kinds, type)?
|
||||||
.int, .i64: .int
|
|
||||||
.float, .f64: .float
|
|
||||||
.i8: .i8
|
|
||||||
.i16: .i16
|
|
||||||
.i32: .i32
|
|
||||||
.isize: .isize
|
|
||||||
.u8: .u8
|
|
||||||
.u16: .u16
|
|
||||||
.u32: .u32
|
|
||||||
.u64, .uint: .u64
|
|
||||||
.usize: .usize
|
|
||||||
.bool: .bool
|
|
||||||
.string: .string
|
|
||||||
.void: .void
|
|
||||||
.anyopaque: .anyopaque
|
|
||||||
.type: .type_value
|
|
||||||
.c_int: .i32
|
|
||||||
.c_uint: .u32
|
|
||||||
.c_long, .c_longlong: .int
|
|
||||||
.c_ulong, .c_ulonglong: .u64
|
|
||||||
.c_char: .i8
|
|
||||||
.c_uchar: .u8
|
|
||||||
.c_short: .i16
|
|
||||||
.c_ushort: .u16
|
|
||||||
.unavailable: .none
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -1584,15 +1588,19 @@ bitwise proc(operator ast.BinaryOperator) bool {
|
|||||||
return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator)
|
return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
integer_widths :: $enummap.init_filled(
|
||||||
|
TypeKind, usize, usize(0), {
|
||||||
|
i8 = 8, u8 = 8,
|
||||||
|
i16 = 16, u16 = 16,
|
||||||
|
i32 = 32, u32 = 32,
|
||||||
|
int = 64, isize = 64, u64 = 64, usize = 64,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
integer_width proc(kind TypeKind) usize {
|
integer_width proc(kind TypeKind) usize {
|
||||||
return match kind {
|
if (enummap.get(&integer_widths, kind)) |width| return width
|
||||||
.i8, .u8: 8
|
return 0
|
||||||
.i16, .u16: 16
|
|
||||||
.i32, .u32: 32
|
|
||||||
.int, .isize, .u64, .usize: 64
|
|
||||||
else: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
import "@std/hashmap"
|
import "@std/hashmap"
|
||||||
|
import "@std/enummap"
|
||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
import "@source/strpool"
|
import "@source/strpool"
|
||||||
@@ -5295,20 +5296,26 @@ replace proc(state @mut State, target, source ast.NodeId) void ! mem.AllocError
|
|||||||
ast.replace_node(state.builder, target, source)
|
ast.replace_node(state.builder, target, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
builtin_identities :: $enummap.init_filled(
|
||||||
|
sema.BuiltinType,
|
||||||
|
sema.BuiltinType,
|
||||||
|
sema.BuiltinType.type, {
|
||||||
|
i64 = .int, c_long = .int, c_longlong = .int,
|
||||||
|
f64 = .float,
|
||||||
|
uint = .u64, c_ulong = .u64, c_ulonglong = .u64,
|
||||||
|
c_char = .i8,
|
||||||
|
c_uchar = .u8,
|
||||||
|
c_short = .i16,
|
||||||
|
c_ushort = .u16,
|
||||||
|
c_int = .i32,
|
||||||
|
c_uint = .u32,
|
||||||
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
builtin_identity proc(kind sema.BuiltinType) sema.BuiltinType {
|
builtin_identity proc(kind sema.BuiltinType) sema.BuiltinType {
|
||||||
return match kind {
|
if (enummap.get(&builtin_identities, kind)) |identity| return identity
|
||||||
.i64, .c_long, .c_longlong: .int
|
return kind
|
||||||
.f64: .float
|
|
||||||
.uint, .c_ulong, .c_ulonglong: .u64
|
|
||||||
.c_char: .i8
|
|
||||||
.c_uchar: .u8
|
|
||||||
.c_short: .i16
|
|
||||||
.c_ushort: .u16
|
|
||||||
.c_int: .i32
|
|
||||||
.c_uint: .u32
|
|
||||||
else: kind
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
+21
-2
@@ -15,6 +15,25 @@ EnumMap proc($E, $V type) type {
|
|||||||
init proc(
|
init proc(
|
||||||
$E, $V type,
|
$E, $V type,
|
||||||
values meta.EnumFieldStruct(E, ?V, some!(null)),
|
values meta.EnumFieldStruct(E, ?V, some!(null)),
|
||||||
|
) EnumMap(E, V) {
|
||||||
|
return initialize(E, V, null, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
#! initializes absent value slots without making their keys present.
|
||||||
|
#! use fully initialized storage when a sparse map must be materialized at compile time.
|
||||||
|
init_filled proc(
|
||||||
|
$E, $V type,
|
||||||
|
storage_default V,
|
||||||
|
values meta.EnumFieldStruct(E, ?V, some!(null)),
|
||||||
|
) EnumMap(E, V) {
|
||||||
|
return initialize(E, V, some!(storage_default), values)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
initialize proc(
|
||||||
|
$E, $V type,
|
||||||
|
storage_default ?V,
|
||||||
|
values meta.EnumFieldStruct(E, ?V, some!(null)),
|
||||||
) EnumMap(E, V) {
|
) EnumMap(E, V) {
|
||||||
map EnumMap(E, V) := undefined
|
map EnumMap(E, V) := undefined
|
||||||
|
|
||||||
@@ -25,7 +44,7 @@ init proc(
|
|||||||
if field!(values, field.name) |value| {
|
if field!(values, field.name) |value| {
|
||||||
map.present[i] = true
|
map.present[i] = true
|
||||||
map.values[i] = value
|
map.values[i] = value
|
||||||
}
|
} else if (storage_default) |value| map.values[i] = value
|
||||||
}
|
}
|
||||||
else: compile_error!("EnumMap key must be an enum")
|
else: compile_error!("EnumMap key must be an enum")
|
||||||
}
|
}
|
||||||
@@ -38,7 +57,7 @@ get proc($E, $V type, map @EnumMap(E, V), key E) ?V {
|
|||||||
match typeinfo!(E) {
|
match typeinfo!(E) {
|
||||||
.enum |info|: inline for info.fields |field, i| {
|
.enum |info|: inline for info.fields |field, i| {
|
||||||
if key == field!(E, field.name) {
|
if key == field!(E, field.name) {
|
||||||
if (map.present[i]) return map.values[i]
|
if (map.present[i]) return some!(map.values[i])
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,22 @@ handles_sparse_enum_get test {
|
|||||||
try testing.expect_equal(null, eof)
|
try testing.expect_equal(null, eof)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
sparse_optional_values :: $init_filled(TestEnum, ?i16, null, {
|
||||||
|
ident = some!(null),
|
||||||
|
int = some!(some!(i16(42))),
|
||||||
|
})
|
||||||
|
|
||||||
|
filled_comptime_storage_preserves_absence_and_nested_optional_presence test {
|
||||||
|
absent :: get(&sparse_optional_values, TestEnum.eof)
|
||||||
|
present_null :: get(&sparse_optional_values, TestEnum.ident)
|
||||||
|
present_value :: get(&sparse_optional_values, TestEnum.int)
|
||||||
|
try testing.expect(absent == null)
|
||||||
|
try testing.expect(present_null != null)
|
||||||
|
try testing.expect(present_null? == null)
|
||||||
|
try testing.expect_equal(i16(42), present_value??)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
TestLeftError :: enum {
|
TestLeftError :: enum {
|
||||||
shared
|
shared
|
||||||
|
|||||||
@@ -9039,6 +9039,24 @@ def check_typeinfo_packages(temporary, executable):
|
|||||||
}
|
}
|
||||||
""", b"42\n", None,
|
""", b"42\n", None,
|
||||||
),
|
),
|
||||||
|
"real_enummap_filled_sparse_constants_preserve_optional_presence": (
|
||||||
|
"""
|
||||||
|
import "@std/meta"
|
||||||
|
import "@std/enummap"
|
||||||
|
Key :: enum(i16) { absent = -3, present_null = 8, present_value = 21 }
|
||||||
|
main proc() i16 {
|
||||||
|
values :: $(enummap.init_filled(Key, ?i16, null, {
|
||||||
|
present_null = some!(null),
|
||||||
|
present_value = some!(some!(i16(42))),
|
||||||
|
}))
|
||||||
|
empty :: $(enummap.init_filled(Key, i16, i16(99), {}))
|
||||||
|
if (enummap.get(&values, .absent) != null or enummap.get(&empty, .absent) != null) return -1
|
||||||
|
payload :: enummap.get(&values, .present_null)
|
||||||
|
if (payload == null or payload? != null) return -2
|
||||||
|
return enummap.get(&values, .present_value)??
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
"metadata_slices_cross_producers_without_losing_type_identity": (
|
"metadata_slices_cross_producers_without_losing_type_identity": (
|
||||||
"""
|
"""
|
||||||
import "@std/meta"
|
import "@std/meta"
|
||||||
|
|||||||
Reference in New Issue
Block a user