diff --git a/README.md b/README.md index 27108a6..28d4981 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/source/ast/renderer/renderer.hon b/source/ast/renderer/renderer.hon index d5f2322..25366c4 100644 --- a/source/ast/renderer/renderer.hon +++ b/source/ast/renderer/renderer.hon @@ -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)? } diff --git a/source/ir/ir.hon b/source/ir/ir.hon index 4b90188..0579395 100644 --- a/source/ir/ir.hon +++ b/source/ir/ir.hon @@ -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. diff --git a/source/lexer/scan.hon b/source/lexer/scan.hon index 6e2b24d..a51c4c8 100644 --- a/source/lexer/scan.hon +++ b/source/lexer/scan.hon @@ -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 diff --git a/source/llvm/instructions.hon b/source/llvm/instructions.hon index 1aaded9..2904180 100644 --- a/source/llvm/instructions.hon +++ b/source/llvm/instructions.hon @@ -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}) diff --git a/source/llvm/llvm.hon b/source/llvm/llvm.hon index 584202f..956e9d7 100644 --- a/source/llvm/llvm.hon +++ b/source/llvm/llvm.hon @@ -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}) } diff --git a/source/lower/lower.hon b/source/lower/lower.hon index 0ae7b0f..7b8904b 100644 --- a/source/lower/lower.hon +++ b/source/lower/lower.hon @@ -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: { diff --git a/source/parser/parse.hon b/source/parser/parse.hon index 1a43540..4656172 100644 --- a/source/parser/parse.hon +++ b/source/parser/parse.hon @@ -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) } diff --git a/source/sema/check/check.hon b/source/sema/check/check.hon index 15bbd94..ef032d4 100644 --- a/source/sema/check/check.hon +++ b/source/sema/check/check.hon @@ -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 diff --git a/source/sema/specialize/specialize.hon b/source/sema/specialize/specialize.hon index edf3549..22fa86d 100644 --- a/source/sema/specialize/specialize.hon +++ b/source/sema/specialize/specialize.hon @@ -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 diff --git a/std/enummap/enummap.hon b/std/enummap/enummap.hon index a13f1a2..e696297 100644 --- a/std/enummap/enummap.hon +++ b/std/enummap/enummap.hon @@ -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 } } diff --git a/std/enummap/enummap.test.hon b/std/enummap/enummap.test.hon index 3ffed42..44715d0 100644 --- a/std/enummap/enummap.test.hon +++ b/std/enummap/enummap.test.hon @@ -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 diff --git a/tests/native.py b/tests/native.py index 8d54187..5de1e3e 100644 --- a/tests/native.py +++ b/tests/native.py @@ -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"