refactor: precompute compiler enum mappings

This commit is contained in:
2026-10-30 10:10:43 +01:00
parent d7e637dc3b
commit 30a98db379
13 changed files with 452 additions and 279 deletions
+2
View File
@@ -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.
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.
`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.
+18 -13
View File
@@ -1,5 +1,6 @@
import "@std/mem"
import "@std/arraylist"
import "@std/enummap"
import "@source/ast"
#! 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)
}
@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
assignment_operator_text proc(operator ast.BinaryOperator) []u8 {
return match operator {
.add: "+"
.subtract: "-"
.multiply: "*"
.divide: "/"
.bit_and: "&"
.bit_or: "|"
.bit_xor: "xor"
.shift_left: "<<"
.shift_right: ">>"
.shift_left_saturating: "<<|"
else: unreachable
}
return enummap.get(&assignment_operator_texts, operator)?
}
+50 -34
View File
@@ -1,6 +1,7 @@
import "@std/mem"
import "@std/arraylist"
import "@std/debug"
import "@std/enummap"
import "@source/diagnostics"
FunctionId :: distinct u32
@@ -33,44 +34,51 @@ aggregate proc(type Type) bool {
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_bits proc(type Type) usize {
return match type {
.i8, .u8: 8
.i16, .u16: 16
.i32, .u32: 32
.int, .isize, .usize, .u64: 64
else: 0
}
if (enummap.get(&integer_widths, type)) |width| return width
return 0
}
unsigned_integer proc(type Type) bool {
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 {
return match type {
.i8: 127
.i16: 32767
.i32: 2147483647
.int, .isize: u64(maxval!(i64))
.u8: 255
.u16: 65535
.u32: 4294967295
.u64, .usize: maxval!(u64)
else: unreachable
}
return enummap.get(&integer_maxima, type)?
}
@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 {
return match type {
.i8: -128
.i16: -32768
.i32: -2147483648
.int, .isize: minval!(i64)
.u8, .u16, .u32, .u64, .usize: 0
else: unreachable
}
return enummap.get(&integer_minima, type)?
}
ValueData :: union {
@@ -412,24 +420,32 @@ aligned_size proc(size, alignment usize) usize {
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 {
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8)
return if (type == .float or type == .string or type == .function 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 {
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
bits :: integer_bits(type)
if (bits != 0) return divtrunc!(bits, 8)
return match type {
.float, .function, .pointer: 8
.string: 16
.bool: 1
.void: 0
else: unreachable
}
return enummap.get(&scalar_sizes, type)?
}
#! loads a scalar or borrows an aggregate from byte cells without allocating.
+8 -4
View File
@@ -1,6 +1,7 @@
import "@std/mem"
import "@std/strmap"
import "@std/arraylist"
import "@std/enummap"
import "@source/diagnostics"
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
diagnostic_code proc(code ErrorCode) diagnostics.Code {
return match code {
.float_must_end_with_digit: .float_must_end_with_digit
.unterminated_string: .unterminated_string
}
return enummap.get(&diagnostic_codes, code)?
}
@hide:file
+63 -48
View File
@@ -1,7 +1,64 @@
import "@std/io"
import "@std/enummap"
import "@source/ir"
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
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
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: {
left :: try load(state, instruction.left)
right :: try load(state, instruction.right)
opcode []u8 :: match instruction.op {
.bit_and: "and"
.bit_or: "or"
.bit_xor: "xor"
else: unreachable
}
opcode :: enummap.get(&bitwise_opcodes, instruction.op)?
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)
@@ -324,13 +376,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
left :: try load(state, instruction.left)
right :: try load(state, instruction.right)
if (instruction.type == .float) {
opcode []u8 :: match instruction.op {
.add: "fadd"
.subtract: "fsub"
.multiply: "fmul"
.divide: "fdiv"
else: unreachable
}
opcode :: enummap.get(&float_opcodes, instruction.op)?
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
} else if (instruction.op == .divide) {
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"
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
} else {
opcode []u8 :: match instruction.op {
.add: "add"
.subtract: "sub"
.multiply: "mul"
else: unreachable
}
opcode :: enummap.get(&integer_opcodes, instruction.op)?
bits :: ir.integer_bits(instruction.type)
sign []u8 :: if (ir.unsigned_integer(instruction.type)) "u" else "s"
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)
operand_type :: state.values[usize(instruction.left)]
if (operand_type.kind == .float) {
predicate []u8 :: match instruction.op {
.equal: "oeq"
.not_equal: "une"
.less: "olt"
.less_equal: "ole"
.greater: "ogt"
.greater_equal: "oge"
else: unreachable
}
predicate :: enummap.get(&float_predicates, instruction.op)?
try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right})
} else {
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"
else prefix = "s"
}
predicate []u8 :: match instruction.op {
.equal: "eq"
.not_equal: "ne"
.less: "lt"
.less_equal: "le"
.greater: "gt"
.greater_equal: "ge"
else: unreachable
}
predicate :: enummap.get(&integer_predicates, instruction.op)?
try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate})
try write_type(output, state.module, operand_type)
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)
valid :: fresh(state)
failed :: fresh(state)
upper []u8 :: match 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
}
upper :: enummap.get(&conversion_upper_bounds, destination)?
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})
else try io.print(" %r{} = fcmp oge double %r{}, -{s}\n", output, {lower_valid, truncated, upper})
+33 -23
View File
@@ -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
abi_extension proc(kind ir.Type) []u8 {
return match kind {
.i8, .i16: "signext"
.u8, .u16, .bool: "zeroext"
else: ""
}
if (enummap.get(&abi_extensions, kind)) |extension| return extension
return ""
}
@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})
}
@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
memory_symbol proc(code diagnostics.Code) []u8 {
return match 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
}
return enummap.get(&memory_symbols, code)?
}
@hide:file
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
position :: location(state.line_starts, span)
symbol []u8 :: match code {
.integer_overflow: "overflow"
.index_out_of_bounds: "bounds"
.inactive_union_field: "inactive_union"
else: ""
}
symbol :: enummap.get(&trap_symbols, code)
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
length :: message.len
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)})
try io.print(", i64 {})\n unreachable\n", state.output, {length})
}
+65 -48
View File
@@ -1,6 +1,7 @@
import "@std/mem"
import "@std/arraylist"
import "@std/strconv"
import "@std/enummap"
import "@ffi/c"
import "@source/ast"
import "@source/diagnostics"
@@ -181,35 +182,43 @@ lower_module proc(
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
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 == .slice) 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)
return match 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
}
return enummap.get(&base_types, type.kind)?
}
@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
binary_op proc(operator ast.BinaryOperator) ir.Op {
return match 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
}
return enummap.get(&binary_ops, operator)?
}
@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)
if (state.terminated or trap_type(state, id)) return ir.no_value
op ir.Op :: match expression.operator {
.negate: .negate
.logical_not: .logical_not
.bit_not: .bit_not
.address_of, .dereference, .unwrap: unreachable
}
op :: enummap.get(&unary_ops, expression.operator)?
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
}
.binary: {
+97 -61
View File
@@ -1,5 +1,6 @@
import "@std/mem"
import "@std/arraylist"
import "@std/enummap"
import "@source/diagnostics"
import "@source/lexer"
@@ -937,24 +938,32 @@ parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.Nod
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
starts_type proc(kind lexer.TokenKind) bool {
return (
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
)
return enummap.get(&type_starters, kind) != null
}
@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
member_name proc(kind lexer.TokenKind) bool {
return match kind {
.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
}
return enummap.get(&member_names, kind) != null
}
@hide:file
@@ -2112,52 +2121,77 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
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
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
return match kind {
.minus, .bang, .tilde, .ampersand, .try: BindingPowerScalar(50)
else: null
}
return enummap.get(&prefix_binding_powers, kind)
}
@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
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
return match 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
}
return enummap.get(&infix_binding_powers, kind)
}
@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
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
return match 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
}
return enummap.get(&binary_operators, kind)?
}
@hide:file
@@ -2165,19 +2199,21 @@ is_assignment_operator proc(kind lexer.TokenKind) bool {
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
assignment_operator proc(kind lexer.TokenKind) ?ast.BinaryOperator {
return match 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
}
return enummap.get(&assignment_operators, kind)
}
+42 -34
View File
@@ -3,6 +3,7 @@ import "@std/mem"
import "@std/arraylist"
import "@std/hashmap"
import "@std/strconv"
import "@std/enummap"
import "@source/ast"
import "@source/diagnostics"
sema :: import "@source/sema"
@@ -1521,35 +1522,38 @@ numeric_kind proc(kind TypeKind) bool {
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
builtin_kind proc(type sema.BuiltinType) TypeKind {
return match 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
}
return enummap.get(&builtin_kinds, type)?
}
@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)
}
@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
integer_width proc(kind TypeKind) usize {
return match kind {
.i8, .u8: 8
.i16, .u16: 16
.i32, .u32: 32
.int, .isize, .u64, .usize: 64
else: 0
}
if (enummap.get(&integer_widths, kind)) |width| return width
return 0
}
@hide:file
+19 -12
View File
@@ -1,6 +1,7 @@
import "@std/mem"
import "@std/arraylist"
import "@std/hashmap"
import "@std/enummap"
import "@source/ast"
import "@source/diagnostics"
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)
}
@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
builtin_identity proc(kind sema.BuiltinType) sema.BuiltinType {
return match kind {
.i64, .c_long, .c_longlong: .int
.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
}
if (enummap.get(&builtin_identities, kind)) |identity| return identity
return kind
}
@hide:file
+21 -2
View File
@@ -15,6 +15,25 @@ EnumMap proc($E, $V type) type {
init proc(
$E, $V type,
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) {
map EnumMap(E, V) := undefined
@@ -25,7 +44,7 @@ init proc(
if field!(values, field.name) |value| {
map.present[i] = true
map.values[i] = value
}
} else if (storage_default) |value| map.values[i] = value
}
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) {
.enum |info|: inline for info.fields |field, i| {
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
}
}
+16
View File
@@ -25,6 +25,22 @@ handles_sparse_enum_get test {
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
TestLeftError :: enum {
shared
+18
View File
@@ -9039,6 +9039,24 @@ def check_typeinfo_packages(temporary, executable):
}
""", 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": (
"""
import "@std/meta"