disambiguate enum blocks and complete distinct type operations

This commit is contained in:
2026-08-01 23:57:35 +02:00
parent 91aa601464
commit b9526b5f06
34 changed files with 1128 additions and 1963359 deletions
-1
View File
@@ -1,3 +1,2 @@
/build/
/grammars/
.DS_Store
+21 -4
View File
@@ -29,7 +29,7 @@ roadmap and milestone history.
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic/bitwise expressions, and typed compile-time evaluation for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar casts through keywords or transparent aliases, such as `i32(x)`, `c_float(x)`, or `StringId(x)`
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `T`
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
@@ -38,13 +38,31 @@ roadmap and milestone history.
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
- narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange
- optionals with `null`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
- nominal distinct types with exact backing construction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
- nominal distinct types with exact backing construction and explicit scalar backing extraction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
#### distinct types
`Name :: distinct T` creates a nominal identity and reuses `T`'s runtime representation.
Construction accepts exactly one value of the immediate backing type. There is no implicit
conversion in either direction, and separate distinct declarations never mix. An explicit scalar
cast extracts exactly one layer: `u32(id)` works for `UserID :: distinct u32`, while nested
distinct values must be peeled one declared layer at a time.
Scalar-backed distinct values support the operations of their representation while preserving the
nominal result type: checked integer `+`, `-`, `*`, unary `-`, bitwise operators, shifts,
comparisons, and compound assignments; float arithmetic, unary `-`, comparisons, and compound
assignments; and boolean equality/inequality. Integer literals and float literals are contextual,
but typed backing values remain barred. Distinct integers also work as indices and slice bounds;
`minval!` / `maxval!` return the distinct type. Runtime and comptime behavior match.
`typeinfo!(Distinct).backing` reports the immediate declared backing. Standard formatting peels
distinct layers recursively, so all scalar format verbs behave like the final scalar backing.
#### native record constraint fields
A direct `int`, `uint`, `float`, or `range` field in a named native struct or union is a
@@ -240,7 +258,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
- `std/testing` supplies fallible `expect`, expected-first `expect_equal`, and exact compile-time `expect_type`; direct calls through
@@ -268,5 +286,4 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
- sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
- backed/C enum composition and must-consume fallible linting
- result-to-argument type-demand propagation through function call boundaries
- distinct-type backing operators and reverse explicit conversions
- string concatenation operator
+1
View File
@@ -228,6 +228,7 @@ Current prototype features:
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
- Scalar-backed nominal `distinct` types with same-identity runtime/comptime operators, explicit backing extraction casts, integer bounds/indexing, reflection, and recursive standard formatting
- Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
+23 -12
View File
@@ -139,10 +139,12 @@
8. distinct types (implemented; see below)
- nominal declarations preserve identity across packages and reuse the backing runtime representation
- construction uses `Type(value)` with exactly one value of the exact backing type
- no implicit conversion to or from the backing type
- backing-type operators and reverse explicit conversions remain deferred
- concrete runtime backing types are supported; unresolved, `int`, `void`, function, and opaque backings are rejected
- construction accepts exactly one value of the immediate backing type; no implicit conversion
crosses the nominal boundary or mixes separate distinct declarations
- explicit scalar casts extract one declared distinct layer at a time
- scalar-backed values support matching runtime/comptime arithmetic, bitwise, shift, comparison,
compound-assignment, bounds, indexing, reflection, and standard formatting behavior
- unresolved, `int`, `void`, function, and opaque backings remain invalid runtime declarations
9. allow pointer field access pass-through (implemented)
- having a pointer (`ptr`) to a struct, we should allow access through `ptr.field` as opposed to mandating `ptr^.field`
@@ -829,9 +831,6 @@
conflicting targets
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
35. syntax highlighting (tree-sitter) updates (implemented)
- pointer sigils are highlighted as operators
- type-factory calls in type positions and struct literals are highlighted as functions
36. transitive package namespaces (spike completed; no language change)
- imports remain file-local implementation details, including explicitly named imports such as
@@ -1062,16 +1061,28 @@ For-loop captures are immutable and scoped to the loop body. Sequence index capt
## A word on distinct types
Distinct types are considered distinct from their backing type. They do not implicitly coerce to their backing type.
Distinct declarations are nominal even when they share a backing type. Construction requires the
exact immediate backing, implicit conversion is forbidden in either direction, and an explicit
scalar cast extracts one layer:
```
# distinct type
```bro
UserID :: distinct u32
OuterID :: distinct UserID
# instantiate distinct type
my_id UserID :: UserID(42) # value must have the exact backing type
id UserID :: UserID(u32(42))
raw u32 :: u32(id)
outer OuterID :: OuterID(id)
inner UserID :: UserID(outer)
```
Scalar-backed distinct values retain their nominal type across the operations supported by the
backing scalar. Integer forms support checked arithmetic, bitwise operations, shifts, comparisons,
compound assignments, indexing, slicing, and `minval!` / `maxval!`; float forms support arithmetic
and comparisons; boolean forms support equality and inequality. Separate distinct identities and
typed backing operands never mix, though literals receive the distinct context. Runtime and
comptime rules are identical. Reflection reports the immediate backing, while standard formatting
recursively follows nested distinct backings to the final scalar.
## A word on enums
```
+388 -128
View File
@@ -942,13 +942,21 @@ type_builtin_value :: proc(checker: ^Checker, kind: Type_Builtin, value: types.T
case .Align_Of:
return i128(types.alignment_of(value, &checker.module.types, checker.target))
case .Min_Value:
if types.is_unsigned(value, checker.target) {
representation := value
if backing, ok := types.distinct_scalar_backing(value, &checker.module.types); ok {
representation = backing
}
if types.is_unsigned(representation, checker.target) {
return 0
}
return -(i128(1) << u32(types.bits(value, checker.target)-1))
return -(i128(1) << u32(types.bits(representation, checker.target)-1))
case .Max_Value:
bit_count := types.bits(value, checker.target)
sign_bit_count := 1 if types.is_signed(value, checker.target) else 0
representation := value
if backing, ok := types.distinct_scalar_backing(value, &checker.module.types); ok {
representation = backing
}
bit_count := types.bits(representation, checker.target)
sign_bit_count := 1 if types.is_signed(representation, checker.target) else 0
return (i128(1) << u32(bit_count-sign_bit_count))-1
case:
return 0
@@ -976,16 +984,22 @@ build_type_builtin :: proc(
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
}
if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(target) {
bound_representation := target
if backing, ok := types.distinct_scalar_backing(target, &checker.module.types); ok {
bound_representation = backing
}
if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(bound_representation) {
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
}
result_type := types.USIZE if kind == .Size_Of || kind == .Align_Of else target
_, distinct_ok := types.distinct_scalar_backing(result_type, &checker.module.types)
return build_constant_expr(
checker,
expr,
Constant{kind=.Value, value=type_builtin_value(checker, kind, target)},
result_type,
distinct_ok,
)
}
@@ -4363,7 +4377,9 @@ validate_meta_schema :: proc(checker: ^Checker) {
(tag == "array" && !types.equal(field.type, array_info)) ||
(tag == "record" && !types.equal(field.type, record_info)) ||
(tag == "enum" && !types.equal(field.type, enum_info)) ||
(tag != "array" && tag != "record" && tag != "enum" && !types.is_void(field.type)) {
(tag == "distinct" && !is_type_metatype_syntax(checker, field.type)) ||
(tag != "array" && tag != "record" && tag != "enum" && tag != "distinct" &&
!types.is_void(field.type)) {
valid = false
break
}
@@ -4704,6 +4720,43 @@ mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec
append(stack, id)
}
Numeric_Operation_Type :: struct {
result: types.Type,
representation: types.Type,
}
numeric_operation_type :: proc(checker: ^Checker, left, right: types.Type) -> (Numeric_Operation_Type, bool) {
if types.equal(left, right) {
if representation, ok := types.distinct_scalar_backing(left, &checker.module.types); ok {
return Numeric_Operation_Type{result=left, representation=representation}, true
}
}
result := types.widest(left, right)
if !types.is_concrete_scalar(result) {
return {}, false
}
return Numeric_Operation_Type{result=result, representation=result}, true
}
numeric_literal_accepts_type :: proc(
checker: ^Checker,
expr: ast.Expr_Id,
demand: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> bool {
representation := demand
if backing, ok := types.distinct_scalar_backing(demand, &checker.module.types); ok {
representation = backing
}
if constant := eval_integer_constant_in_context(checker, expr, pkg, file); constant.kind == .Value {
return types.is_float(representation, checker.target) ||
types.is_concrete_integer(representation) &&
fits_integer_type(constant.value, representation, checker.target)
}
return is_float_constant_expr(checker, expr) && types.is_float(representation, checker.target)
}
Infer_Frame :: struct {
expr: ast.Expr_Id,
expected: types.Type,
@@ -4715,6 +4768,8 @@ Infer_Frame :: struct {
mapping: []int,
args: []types.Type,
template: ast.Function_Id,
numeric_operation: bool,
reverse_operands: bool,
}
merge_inferred_test_error :: proc(checker: ^Checker, incoming: types.Type) {
@@ -4772,8 +4827,16 @@ infer_division_builtin :: proc(
right_hint := hint if types.is_valid(hint) else left
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, right_hint)
}
result := types.widest(left, right)
return result if types.is_concrete_scalar(result) && !types.is_bool(result) else types.INVALID
if left_const && types.is_distinct(right, &checker.module.types) &&
numeric_literal_accepts_type(checker, expr.args[0], right, pkg, file) {
left = right
}
if right_const && types.is_distinct(left, &checker.module.types) &&
numeric_literal_accepts_type(checker, expr.args[1], left, pkg, file) {
right = left
}
operation, ok := numeric_operation_type(checker, left, right)
return operation.result if ok && !types.is_bool(operation.representation) else types.INVALID
}
infer_compound_expr :: proc(
@@ -4796,28 +4859,38 @@ infer_compound_expr :: proc(
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
return types.BOOL
case .Bit_Not:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
return operand if types.is_concrete_integer(operand) else types.INVALID
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
representation := types.runtime_representation(operand, store)
return operand if types.is_concrete_integer(representation) else types.INVALID
case .Bit_And, .Bit_Or, .Bit_Xor:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
left, right := types.INVALID, types.INVALID
if left_const && !right_const && !types.is_valid(hint) {
if types.is_concrete_integer(expected) {
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, expected)
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, expected)
} else if left_const && !right_const {
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
} else {
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, hint if types.is_valid(hint) else left)
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
}
result := types.widest(left, right)
return result if types.is_concrete_integer(result) else types.INVALID
if left_const && types.is_distinct(right, store) &&
numeric_literal_accepts_type(checker, expr.left, right, pkg, file) {
left = right
}
if right_const && types.is_distinct(left, store) &&
numeric_literal_accepts_type(checker, expr.right, left, pkg, file) {
right = left
}
operation, ok := numeric_operation_type(checker, left, right)
return operation.result if ok && types.is_concrete_integer(operation.representation) else types.INVALID
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
representation := types.runtime_representation(left, store)
return left if types.is_concrete_integer(representation) && types.is_unsigned(right, checker.target) else types.INVALID
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
left_expr := checker.ast_module.exprs[expr.left]
right_expr := checker.ast_module.exprs[expr.right]
@@ -5135,6 +5208,16 @@ infer_expr :: proc(
continue
}
if constant.kind == .Value {
if frame.numeric_operation {
if representation, ok := types.distinct_scalar_backing(frame.expected, &checker.module.types);
ok && (types.is_float(representation, checker.target) ||
types.is_concrete_integer(representation) &&
fits_integer_type(constant.value, representation, checker.target)) {
last = frame.expected
_ = pop(&stack)
continue
}
}
last = constraint_integer_literal_type(frame.expected, constant.value)
if !types.is_valid(last) {
last = types.I64
@@ -5162,7 +5245,15 @@ infer_expr :: proc(
}
_ = pop(&stack)
case .Float:
last = types.F64
if frame.numeric_operation {
if representation, ok := types.distinct_scalar_backing(frame.expected, &checker.module.types);
ok && types.is_float(representation, checker.target) {
last = frame.expected
_ = pop(&stack)
continue
}
}
last = frame.expected if types.is_float(frame.expected, checker.target) else types.F64
_ = pop(&stack)
case .String, .Array, .Null, .Unreachable, .Undefined, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
@@ -5294,7 +5385,17 @@ infer_expr :: proc(
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Add, .Sub, .Mul, .Div:
stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
stack[frame_index].reverse_operands = left_const && !right_const
first := expr.right if stack[frame_index].reverse_operands else expr.left
first_expected := frame.expected if
types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) else types.INVALID
append(&stack, Infer_Frame{
expr=first,
expected=first_expected,
template=ast.INVALID_FUNCTION,
})
case .Call:
if expr.left != ast.INVALID_EXPR {
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
@@ -5567,38 +5668,54 @@ infer_expr :: proc(
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
append(&stack, Infer_Frame{expr=expr.right, template=ast.INVALID_FUNCTION})
second := expr.left if frame.reverse_operands else expr.right
second_expected := types.INVALID
numeric_operation := false
if is_numeric_constant_expr(checker, second) {
second_expected = last
if representation, ok := types.distinct_scalar_backing(last, &checker.module.types);
ok && !types.is_bool(representation) {
numeric_operation = true
}
}
append(&stack, Infer_Frame{
expr=second,
expected=second_expected,
template=ast.INVALID_FUNCTION,
numeric_operation=numeric_operation,
})
continue
}
if frame.stage == 2 {
right := last
if expr.kind == .Add && types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(right) {
last = frame.left
first, second := frame.left, last
left, right := first, second
if frame.reverse_operands {
left, right = second, first
}
if expr.kind == .Add && types.is_many_pointer(left, &checker.module.types) && types.is_concrete_integer(right) {
last = left
} else if operation, ok := numeric_operation_type(checker, left, right); ok {
last = operation.result
} else if is_numeric_constant_expr(checker, expr.right) &&
is_numeric_demand(frame.left, checker.target) &&
expr_accepts_numeric_demand(checker, expr.right, frame.left, locals, pkg, file) {
last = frame.left
is_numeric_demand(left, checker.target) &&
expr_accepts_numeric_demand(checker, expr.right, left, locals, pkg, file) {
last = left
} else if is_numeric_constant_expr(checker, expr.left) &&
is_numeric_demand(right, checker.target) &&
expr_accepts_numeric_demand(checker, expr.left, right, locals, pkg, file) {
last = right
} else if is_numeric_demand(frame.left, checker.target) &&
} else if is_numeric_demand(left, checker.target) &&
!numeric_operand_is_open(checker, expr.left, locals, pkg, file) &&
expr_accepts_numeric_demand(checker, expr.right, frame.left, locals, pkg, file) {
// Propagate only from an authoritative (fixed-type) left operand. A left
// operand that is still a provisional open constant carries only its
// smallest-signed default, which must not poison the sibling's family;
// two provisional operands are resolved together by the backward demand
// from the declaration/use.
_ = record_demand(checker, expr.right, frame.left, locals, local_types, pkg, file)
last = frame.left
expr_accepts_numeric_demand(checker, expr.right, left, locals, pkg, file) {
_ = record_demand(checker, expr.right, left, locals, local_types, pkg, file)
last = left
} else if is_numeric_demand(right, checker.target) &&
!numeric_operand_is_open(checker, expr.right, locals, pkg, file) &&
expr_accepts_numeric_demand(checker, expr.left, right, locals, pkg, file) {
_ = record_demand(checker, expr.left, right, locals, local_types, pkg, file)
last = right
} else {
last = types.widest(frame.left, right)
last = types.INVALID
}
_ = pop(&stack)
continue
@@ -7214,23 +7331,33 @@ build_constant_expr :: proc(
expr: ast.Expr,
constant: Constant,
expected: types.Type,
numeric_operation := false,
) -> hir.Expr_Id {
materialized := expected
nominal := types.INVALID
if numeric_operation {
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
ok && !types.is_bool(representation) {
materialized = representation
nominal = expected
}
}
recovery_type := types.I64
if types.is_concrete_integer(expected) {
if types.is_concrete_integer(materialized) {
recovery_type = expected
}
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
// float literal, mirroring build_float_expr's bit packing.
if constant.kind == .Value && types.is_float(expected, checker.target) {
if constant.kind == .Value && types.is_float(materialized, checker.target) {
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
bits := transmute(i64)fval
if types.bits(expected, checker.target) == 32 {
if types.bits(materialized, checker.target) == 32 {
bits = i64(transmute(u32)f32(fval))
}
return add_hir_expr(checker, hir.Expr{
kind = .Float,
span = expr.span,
type = expected,
type = nominal if types.is_valid(nominal) else materialized,
integer = bits,
target = hir.INVALID_REF,
left = hir.INVALID_EXPR,
@@ -7251,7 +7378,7 @@ build_constant_expr :: proc(
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Overflow ||
(!types.is_concrete_integer(expected) && expected != types.UINT && !fits_i64(constant.value)) {
(!types.is_concrete_integer(materialized) && materialized != types.UINT && !fits_i64(constant.value)) {
id := source.add(
checker.diagnostics,
expr.span,
@@ -7264,8 +7391,8 @@ build_constant_expr :: proc(
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
value = transmute(i64)u64(constant.value)
}
result_type := constraint_integer_literal_type(expected, constant.value)
if expected == types.UINT && !types.is_valid(result_type) {
result_type := constraint_integer_literal_type(materialized, constant.value)
if materialized == types.UINT && !types.is_valid(result_type) {
id := source.addf(
checker.diagnostics,
expr.span,
@@ -7274,8 +7401,8 @@ build_constant_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, types.U64)
}
if types.is_concrete_integer(expected) {
if !fits_integer_type(constant.value, expected, checker.target) {
if types.is_concrete_integer(materialized) {
if !fits_integer_type(constant.value, materialized, checker.target) {
id := source.addf(
checker.diagnostics,
expr.span,
@@ -7285,7 +7412,7 @@ build_constant_expr :: proc(
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
result_type = expected
result_type = nominal if types.is_valid(nominal) else materialized
}
return add_hir_expr(
checker,
@@ -7302,11 +7429,26 @@ build_constant_expr :: proc(
)
}
build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type) -> hir.Expr_Id {
build_float_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
expected: types.Type,
numeric_operation := false,
) -> hir.Expr_Id {
materialized := expected
result_type := types.F64
if types.is_float(expected, checker.target) {
result_type = expected
} else if types.is_valid(expected) {
if numeric_operation {
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
ok && types.is_float(representation, checker.target) {
materialized = representation
result_type = expected
}
}
if types.is_float(materialized, checker.target) {
if !types.is_valid(result_type) || !types.is_distinct(result_type, &checker.module.types) {
result_type = materialized
}
} else if types.is_valid(materialized) {
id := source.addf(
checker.diagnostics,
expr.span,
@@ -7317,7 +7459,7 @@ build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type
}
value := transmute(f64)expr.integer
bits := transmute(i64)value
if types.bits(result_type, checker.target) == 32 {
if types.bits(materialized, checker.target) == 32 {
bits = i64(transmute(u32)f32(value))
}
return add_hir_expr(checker, hir.Expr{
@@ -7346,6 +7488,8 @@ Build_Expr_Frame :: struct {
arg_types: []types.Type,
template: ast.Function_Id,
resolution: int,
numeric_operation: bool,
reverse_operands: bool,
}
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
@@ -7731,11 +7875,23 @@ build_scalar_cast :: proc(
) -> hir.Expr_Id {
store := &checker.module.types
actual := checker.module.exprs[value].type
if types.can_retype_distinct(actual, target, store) {
return add_hir_expr(checker, hir.Expr{
kind=.Retype,
span=span,
type=target,
left=value,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
actual_repr := types.runtime_representation(actual, store)
actual_item, actual_item_ok := types.node(store, actual)
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
_, distinct_scalar := types.distinct_scalar_backing(actual, store)
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum || distinct_scalar) &&
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
if !valid_target || !valid_actual {
id := source.addf(
@@ -7757,6 +7913,38 @@ build_scalar_cast :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
normalize_index_expr :: proc(checker: ^Checker, value: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
store := &checker.module.types
current := value
current_type := checker.module.exprs[current].type
representation, distinct_ok := types.distinct_scalar_backing(current_type, store)
if distinct_ok && types.is_concrete_integer(representation) {
for {
backing, ok := types.distinct_backing(current_type, store)
if !ok {
break
}
current = add_hir_expr(checker, hir.Expr{
kind=.Retype,
span=span,
type=backing,
left=current,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
current_type = backing
}
}
current_expr := &checker.module.exprs[current]
if current_expr.kind == .Integer &&
fits_integer_type(i128(current_expr.integer), types.USIZE, checker.target) {
current_expr.type = types.USIZE
return current
}
return coerce_expr(checker, current, types.USIZE, span)
}
build_function_value :: proc(
checker: ^Checker,
@@ -7836,11 +8024,12 @@ build_nested_expr :: proc(
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
numeric_operation := false,
) -> hir.Expr_Id {
outer := checker.build_stack
checker.build_stack = nil
checker.build_stack.allocator = checker.allocator
result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file)
result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file, numeric_operation)
delete(checker.build_stack)
checker.build_stack = outer
return result
@@ -7901,19 +8090,29 @@ build_division_builtin :: proc(
left, right := hir.INVALID_EXPR, hir.INVALID_EXPR
if left_const && !right_const && !types.is_valid(hint) {
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
right_type := checker.module.exprs[right].type
_, distinct_ok := types.distinct_scalar_backing(right_type, &checker.module.types)
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, right_type, pkg, file, distinct_ok)
} else {
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, hint, pkg, file)
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file)
_, distinct_ok := types.distinct_scalar_backing(right_hint, &checker.module.types)
right = build_nested_expr(
checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file,
distinct_ok && right_const,
)
}
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_scalar(result) || types.is_bool(result) {
operation, ok := numeric_operation_type(
checker,
checker.module.exprs[left].type,
checker.module.exprs[right].type,
)
if !ok || types.is_bool(operation.representation) {
id := source.add(checker.diagnostics, expr.span, "division builtins require compatible numeric operands")
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
left = coerce_expr(checker, left, operation.result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, operation.result, checker.module.exprs[right].span)
result_kind := hir.Expr_Kind.Div_Trunc
#partial switch kind {
case .Floor: result_kind = .Div_Floor
@@ -7924,7 +8123,7 @@ build_division_builtin :: proc(
case:
}
return add_hir_expr(checker, hir.Expr{
kind=result_kind, span=expr.span, type=result, left=left, right=right,
kind=result_kind, span=expr.span, type=operation.result, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
@@ -8303,8 +8502,8 @@ build_compound_expr :: proc(
})
case .Index:
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
index = coerce_expr(checker, index, types.USIZE, expr.span)
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
index = normalize_index_expr(checker, index, expr.span)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container, index); propagated {
return invalid
}
@@ -8347,8 +8546,8 @@ build_compound_expr :: proc(
bounds[1] = hir.INVALID_EXPR
for bound, index in expr.args {
if bound != ast.INVALID_EXPR {
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file)
bounds[index] = coerce_expr(checker, bounds[index], types.USIZE, checker.ast_module.exprs[bound].span)
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.INVALID, pkg, file)
bounds[index] = normalize_index_expr(checker, bounds[index], checker.ast_module.exprs[bound].span)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, bounds[index]); propagated {
delete(bounds, checker.allocator)
return invalid
@@ -8578,13 +8777,13 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bit_Not:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
return invalid
}
operand_type := checker.module.exprs[operand].type
if !types.is_concrete_integer(operand_type) {
representation := types.runtime_representation(operand_type, store)
if !types.is_concrete_integer(representation) {
id := source.add(checker.diagnostics, expr.span, "'~' requires a concrete integer operand")
return invalid_hir_expr(checker, expr.span, id, operand_type)
}
@@ -8593,47 +8792,59 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bit_And, .Bit_Or, .Bit_Xor:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
left, right: hir.Expr_Id
if left_const && !right_const && !types.is_valid(hint) {
if types.is_concrete_integer(expected) {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, expected, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, expected, pkg, file)
} else if left_const && !right_const {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
right_type := checker.module.exprs[right].type
_, distinct_ok := types.distinct_scalar_backing(right_type, store)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, right_type, pkg, file, distinct_ok)
} else {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, right_hint, pkg, file)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right_hint := checker.module.exprs[left].type
_, distinct_ok := types.distinct_scalar_backing(right_hint, store)
right = build_nested_expr(
checker, expr.right, locals, global_reads, calls, right_hint, pkg, file,
distinct_ok && right_const,
)
}
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
result_type := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_integer(result_type) {
operation, ok := numeric_operation_type(
checker,
checker.module.exprs[left].type,
checker.module.exprs[right].type,
)
if !ok || !types.is_concrete_integer(operation.representation) {
id := source.add(checker.diagnostics, expr.span, "bitwise operation requires compatible concrete integer operands")
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, result_type, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result_type, checker.module.exprs[right].span)
left = coerce_expr(checker, left, operation.result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, operation.result, checker.module.exprs[right].span)
kind := hir.Expr_Kind.Bit_And
#partial switch expr.kind {
case .Bit_Or: kind = .Bit_Or
case .Bit_Xor: kind = .Bit_Xor
}
return add_hir_expr(checker, hir.Expr{
kind=kind, span=expr.span, type=result_type, left=left, right=right,
kind=kind, span=expr.span, type=operation.result, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.U64, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
if !types.is_concrete_integer(left_type) {
left_representation := types.runtime_representation(left_type, store)
if !types.is_concrete_integer(left_representation) {
id := source.add(checker.diagnostics, checker.module.exprs[left].span, "shifted value must be a concrete integer")
return invalid_hir_expr(checker, expr.span, id, left_type)
}
@@ -8643,7 +8854,7 @@ build_compound_expr :: proc(
}
if constant := eval_integer_constant_in_context(checker, expr.right, pkg, file);
constant.kind == .Value && expr.kind != .Shift_Left_Saturating &&
constant.value >= i128(types.bits(left_type, checker.target)) {
constant.value >= i128(types.bits(left_representation, checker.target)) {
id := source.addf(
checker.diagnostics, checker.module.exprs[right].span,
"shift count %d exceeds %s width", constant.value, types.name(left_type),
@@ -8703,11 +8914,13 @@ build_compound_expr :: proc(
} else if right_numeric_const && !left_numeric_const {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
hint := checker.module.exprs[left].type
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
_, distinct_ok := types.distinct_scalar_backing(hint, store)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file, distinct_ok)
} else if left_numeric_const && !right_numeric_const {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
hint := checker.module.exprs[right].type
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
_, distinct_ok := types.distinct_scalar_backing(hint, store)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file, distinct_ok)
} else {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
@@ -8738,11 +8951,13 @@ build_compound_expr :: proc(
}
operand_type = types.BOOL
} else {
operand_type = types.widest(left_type, right_type)
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
operation, ok := numeric_operation_type(checker, left_type, right_type)
if !ok ||
types.is_bool(operation.representation) && expr.kind != .Eq && expr.kind != .Ne {
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
operand_type = operation.result
}
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
@@ -8978,17 +9193,21 @@ build_binary_arith :: proc(
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_scalar(result) {
operation, ok := numeric_operation_type(
checker,
checker.module.exprs[left].type,
checker.module.exprs[right].type,
)
if !ok || types.is_bool(operation.representation) {
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
return invalid_hir_expr(checker, span, id)
}
if op == .Div && !types.is_float(result, checker.target) {
if op == .Div && !types.is_float(operation.representation, checker.target) {
id := source.add(
checker.diagnostics, span,
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
)
return invalid_hir_expr(checker, span, id, result)
return invalid_hir_expr(checker, span, id, operation.result)
}
result_kind := hir.Expr_Kind.Add
#partial switch op {
@@ -8996,10 +9215,10 @@ build_binary_arith :: proc(
case .Mul: result_kind = .Mul
case .Div: result_kind = .Div
}
coerced_left := coerce_expr(checker, left, result, checker.module.exprs[left].span)
coerced_right := coerce_expr(checker, right, result, checker.module.exprs[right].span)
coerced_left := coerce_expr(checker, left, operation.result, checker.module.exprs[left].span)
coerced_right := coerce_expr(checker, right, operation.result, checker.module.exprs[right].span)
return add_hir_expr(checker, hir.Expr{
kind=result_kind, span=span, type=result, left=coerced_left, right=coerced_right,
kind=result_kind, span=span, type=operation.result, left=coerced_left, right=coerced_right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
@@ -9013,6 +9232,7 @@ build_expr :: proc(
expected := types.INVALID,
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
numeric_operation := false,
) -> hir.Expr_Id {
stack := checker.build_stack
checker.build_stack = nil
@@ -9030,7 +9250,12 @@ build_expr :: proc(
delete(stack)
}
}
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
append(&stack, Build_Expr_Frame{
expr=expr_id,
expected=expected,
template=ast.INVALID_FUNCTION,
numeric_operation=numeric_operation,
})
last := hir.INVALID_EXPR
for len(stack) > 0 {
@@ -9067,7 +9292,7 @@ build_expr :: proc(
constant = eval_constant(checker, frame.expr)
}
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
last = build_constant_expr(checker, expr, constant, frame.expected)
last = build_constant_expr(checker, expr, constant, frame.expected, frame.numeric_operation)
_ = pop(&stack)
continue
}
@@ -9093,7 +9318,7 @@ build_expr :: proc(
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
_ = pop(&stack)
case .Float:
last = build_float_expr(checker, expr, frame.expected)
last = build_float_expr(checker, expr, frame.expected, frame.numeric_operation)
_ = pop(&stack)
case .Name:
last = hir.INVALID_EXPR
@@ -9269,13 +9494,20 @@ build_expr :: proc(
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Add, .Sub, .Mul, .Div:
stack[frame_index].stage = 1
// Preserve assignment/return context for literal operands, e.g.
// assigning `i + 1` back into a `u32` local.
left_expected := types.INVALID
if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
left_expected = frame.expected
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
stack[frame_index].reverse_operands = left_const && !right_const
first := expr.right if stack[frame_index].reverse_operands else expr.left
first_expected := types.INVALID
if !stack[frame_index].reverse_operands &&
types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
first_expected = frame.expected
}
append(&stack, Build_Expr_Frame{expr=expr.left, expected=left_expected, template=ast.INVALID_FUNCTION})
append(&stack, Build_Expr_Frame{
expr=first,
expected=first_expected,
template=ast.INVALID_FUNCTION,
})
case .Call:
if expr.left != ast.INVALID_EXPR {
stack[frame_index].stage = 6
@@ -9481,7 +9713,7 @@ build_expr :: proc(
constructor_type := types.resolve_alias(named_type, &checker.module.types)
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
scalar_alias := named_ok && named_item.kind == .Alias &&
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
types.is_concrete_scalar(constructor_type)
if scalar_alias {
if len(expr.args) != 1 {
id := source.addf(
@@ -9681,7 +9913,8 @@ build_expr :: proc(
continue
}
operand_type := checker.module.exprs[operand].type
if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) {
representation := types.runtime_representation(operand_type, &checker.module.types)
if !types.is_signed(representation, checker.target) && !types.is_float(representation, checker.target) {
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float")
last = invalid_hir_expr(checker, expr.span, id)
} else {
@@ -9689,7 +9922,10 @@ build_expr :: proc(
kind=.Negate, span=expr.span, type=operand_type, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.is_signed(frame.expected, checker.target) || types.is_float(frame.expected, checker.target) {
expected_representation := types.runtime_representation(frame.expected, &checker.module.types)
if types.equal(operand_type, frame.expected) &&
(types.is_signed(expected_representation, checker.target) ||
types.is_float(expected_representation, checker.target)) {
last = coerce_expr(checker, last, frame.expected, expr.span)
}
}
@@ -9699,21 +9935,36 @@ build_expr :: proc(
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
right_expected := types.INVALID
if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) {
right_expected = types.USIZE
} else if eval_constant(checker, expr.right).kind == .Value {
// A constant RHS adopts the concrete LHS type before numeric
// compatibility is checked.
right_expected = checker.module.exprs[last].type
first_type := checker.module.exprs[last].type
second := expr.left if frame.reverse_operands else expr.right
second_expected := types.INVALID
numeric_operation := false
if types.is_many_pointer(first_type, &checker.module.types) {
second_expected = types.USIZE
} else if is_numeric_constant_expr(checker, second) {
second_expected = first_type
if representation, ok := types.distinct_scalar_backing(first_type, &checker.module.types);
ok && !types.is_bool(representation) {
numeric_operation = true
}
} else if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
right_expected = frame.expected
second_expected = frame.expected
}
append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION})
append(&stack, Build_Expr_Frame{
expr=second,
expected=second_expected,
template=ast.INVALID_FUNCTION,
numeric_operation=numeric_operation,
})
continue
}
if frame.stage == 2 {
last = build_binary_arith(checker, expr.kind, frame.left, last, expr.span)
first, second := frame.left, last
left, right := first, second
if frame.reverse_operands {
left, right = second, first
}
last = build_binary_arith(checker, expr.kind, left, right, expr.span)
_ = pop(&stack)
continue
}
@@ -10062,7 +10313,8 @@ build_expr :: proc(
if frame.stage == 8 {
distinct_item, ok := types.node(&checker.module.types, frame.target_type)
actual := checker.module.exprs[last].type
if !ok || distinct_item.kind != .Distinct || !types.equal(actual, distinct_item.child) {
if !ok || distinct_item.kind != .Distinct ||
!types.can_retype_distinct(actual, frame.target_type, &checker.module.types) {
id := source.addf(
checker.diagnostics,
expr.span,
@@ -10892,9 +11144,16 @@ build_block :: proc(
statement.assignment_op == .Shift_Left_Saturating {
rhs_expected = types.U64
}
rhs_numeric_operation := false
if statement.assignment_op != .Shift_Left &&
statement.assignment_op != .Shift_Right &&
statement.assignment_op != .Shift_Left_Saturating &&
is_numeric_constant_expr(checker, statement.expr) {
_, rhs_numeric_operation = types.distinct_scalar_backing(target_type, &checker.module.types)
}
value = build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
rhs_expected, ctx.pkg, ctx.file,
rhs_expected, ctx.pkg, ctx.file, rhs_numeric_operation,
)
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
append(&body, hir.stmt_id(len(checker.module.statements)))
@@ -10937,14 +11196,15 @@ build_block :: proc(
is_bitwise := statement.assignment_op == .Bit_And ||
statement.assignment_op == .Bit_Or ||
statement.assignment_op == .Bit_Xor
result_type := types.widest(target_type, rhs_type)
operation, compatible := numeric_operation_type(checker, target_type, rhs_type)
target_representation := types.runtime_representation(target_type, &checker.module.types)
if is_shift {
if !types.is_concrete_integer(target_type) || !types.is_unsigned(rhs_type, checker.target) {
if !types.is_concrete_integer(target_representation) || !types.is_unsigned(rhs_type, checker.target) {
id := source.add(checker.diagnostics, statement.span, "shift assignment requires an integer target and unsigned integer count")
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else if constant := eval_integer_constant_in_context(checker, statement.expr, ctx.pkg, ctx.file);
constant.kind == .Value && statement.assignment_op != .Shift_Left_Saturating &&
constant.value >= i128(types.bits(target_type, checker.target)) {
constant.value >= i128(types.bits(target_representation, checker.target)) {
id := source.addf(
checker.diagnostics, statement.span,
"shift count %d exceeds %s width", constant.value, types.name(target_type),
@@ -10952,21 +11212,21 @@ build_block :: proc(
value = invalid_hir_expr(checker, statement.span, id, target_type)
}
} else if is_bitwise {
if !types.is_concrete_integer(result_type) {
if !compatible || !types.is_concrete_integer(operation.representation) {
id := source.add(checker.diagnostics, statement.span, "bitwise assignment requires compatible concrete integer operands")
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else {
value = coerce_expr(checker, value, target_type, statement.span)
}
} else if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
} else if statement.assignment_op == .Div &&
compatible && types.is_concrete_integer(operation.representation) {
id := source.add(
checker.diagnostics,
statement.span,
"integer '/=' is not allowed; assign through an explicit division builtin",
)
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else if !types.is_concrete_scalar(result_type) ||
types.is_bool(result_type) {
} else if !compatible || types.is_bool(operation.representation) {
id := source.add(
checker.diagnostics,
statement.span,
+263 -93
View File
@@ -10,7 +10,6 @@ import "base:intrinsics"
import "core:fmt"
import "core:hash"
import "core:math"
import "core:mem"
import "core:strings"
COMPTIME_EVAL_QUOTA :: 100_000
@@ -79,6 +78,13 @@ current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
return value.type, true
}
if binding, ok := current_static_binding(checker, name);
ok && binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
value := checker.static_state.values[binding.value]
if value.kind == .Type {
return types.Type(value.index), true
}
}
return types.INVALID, false
}
@@ -1089,16 +1095,21 @@ ct_materialize_value :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
return build_constant_expr(checker, ast.Expr{span=span}, Constant{kind=.Value, value=value.integer}, value.type)
_, distinct_ok := types.distinct_scalar_backing(value.type, &checker.module.types)
return build_constant_expr(
checker, ast.Expr{span=span}, Constant{kind=.Value, value=value.integer},
value.type, distinct_ok,
)
case .Bool:
return add_hir_expr(checker, hir.Expr{
kind=.Bool, span=span, type=types.BOOL, integer=i64(value.integer),
kind=.Bool, span=span, type=value.type, integer=i64(value.integer),
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Float:
bits := transmute(i64)value.float
if types.bits(value.type, checker.target) == 32 {
representation := types.runtime_representation(value.type, &checker.module.types)
if types.bits(representation, checker.target) == 32 {
bits = i64(transmute(u32)f32(value.float))
}
return add_hir_expr(checker, hir.Expr{
@@ -1251,6 +1262,7 @@ ct_eval_expr :: proc(
expr_id: ast.Expr_Id,
expected := types.INVALID,
depth := 0,
numeric_operation := false,
) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if depth > 128 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
@@ -1263,6 +1275,22 @@ ct_eval_expr :: proc(
store := &checker.module.types
#partial switch expr.kind {
case .Integer:
if numeric_operation {
if representation, distinct_ok := types.distinct_scalar_backing(expected, store); distinct_ok {
if types.is_float(representation, checker.target) {
return ct_add_value(state, Ct_Value{
kind=.Float, type=expected, float=f64(expr.integer),
}), ct_flow(.Normal), true
}
value := i128(expr.integer)
if types.is_concrete_integer(representation) &&
fits_integer_type(value, representation, checker.target) {
return ct_add_value(state, Ct_Value{
kind=.Integer, type=expected, integer=value,
}), ct_flow(.Normal), true
}
}
}
value_type := expected if types.is_concrete_integer(expected) || types.is_enum(expected, store) else ct_default_integer_type(i128(expr.integer))
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value_type, integer=i128(expr.integer)})
if types.is_valid(expected) {
@@ -1272,6 +1300,14 @@ ct_eval_expr :: proc(
case .Bool:
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=i128(expr.integer)}), ct_flow(.Normal), true
case .Float:
if numeric_operation {
if representation, distinct_ok := types.distinct_scalar_backing(expected, store);
distinct_ok && types.is_float(representation, checker.target) {
return ct_add_value(state, Ct_Value{
kind=.Float, type=expected, float=transmute(f64)expr.integer,
}), ct_flow(.Normal), true
}
}
value_type := expected if types.is_float(expected, checker.target) else types.F64
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true
case .String:
@@ -1416,10 +1452,17 @@ ct_eval_expr :: proc(
}
return ct_eval_field_value(state, base_id, expr.name, expr.span)
case .Index:
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.INVALID, depth+1)
if !index_ok || index_flow.kind != .Normal {
return INVALID_CT_VALUE, index_flow, index_ok
}
index_type := state.values[index_id].type
index_representation := types.runtime_representation(index_type, store)
index_literal := is_numeric_constant_expr(checker, expr.right)
if !types.is_concrete_integer(index_representation) ||
!index_literal && !can_implicitly_convert_type(checker, index_representation, types.USIZE) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be coercible to usize")
}
index_value, index_is_int := ct_integer_value(state, index_id)
if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer")
@@ -1541,17 +1584,12 @@ ct_eval_expr :: proc(
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
}), ct_flow(.Normal), true
case .Negate, .Not, .Bit_Not:
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1, numeric_operation)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
return ct_eval_unary(state, expr.kind, value, expr.span)
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
if (expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor) &&
types.is_concrete_integer(expected) {
left_expected = expected
}
left_expr := checker.ast_module.exprs[expr.left]
right_expr := checker.ast_module.exprs[expr.right]
if left_expr.kind == .Null && right_expr.kind != .Null &&
@@ -1566,14 +1604,39 @@ ct_eval_expr :: proc(
}
return ct_eval_binary(state, expr.kind, left, right, expr.span)
}
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
left, right := INVALID_CT_VALUE, INVALID_CT_VALUE
flow := ct_flow(.Normal)
ok := false
if left_const && !right_const {
right, flow, ok = ct_eval_expr(state, expr.right, types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
_, distinct_ok := types.distinct_scalar_backing(state.values[right].type, store)
left, flow, ok = ct_eval_expr(
state, expr.left, state.values[right].type, depth+1, distinct_ok,
)
} else {
left_expected := types.INVALID
if types.is_concrete_integer(expected) ||
expr.kind == .Div && types.is_float(expected, checker.target) {
left_expected = expected
}
left, flow, ok = ct_eval_expr(state, expr.left, left_expected, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right_expected := state.values[left].type
_, distinct_ok := types.distinct_scalar_backing(right_expected, store)
right, flow, ok = ct_eval_expr(
state, expr.right, right_expected, depth+1, distinct_ok && right_const,
)
}
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1)
if !right_ok || right_flow.kind != .Normal {
return INVALID_CT_VALUE, right_flow, right_ok
}
return ct_eval_binary(state, expr.kind, left, right, expr.span)
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
left, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
@@ -1899,10 +1962,17 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
if bound == ast.INVALID_EXPR {
continue
}
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1)
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.INVALID, depth+1)
if !bound_ok || bound_flow.kind != .Normal {
return INVALID_CT_VALUE, bound_flow, bound_ok
}
bound_type := state.values[value].type
bound_representation := types.runtime_representation(bound_type, store)
bound_literal := is_numeric_constant_expr(checker, bound)
if !types.is_concrete_integer(bound_representation) ||
!bound_literal && !can_implicitly_convert_type(checker, bound_representation, types.USIZE) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be coercible to usize")
}
integer, integer_ok := ct_integer_value(state, value)
if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be non-negative integers")
@@ -2356,10 +2426,11 @@ ct_unwrap_optional :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span)
}
ct_normalize_integer :: proc(state: ^Ct_State, value: i128, type: types.Type) -> i128 {
bits := types.bits(type, state.checker.target)
representation := types.runtime_representation(type, &state.checker.module.types)
bits := types.bits(representation, state.checker.target)
mask := (i128(1) << u32(bits))-1
raw := value & mask
if types.is_signed(type, state.checker.target) {
if types.is_signed(representation, state.checker.target) {
sign := i128(1) << u32(bits-1)
if raw & sign != 0 {
return raw-(i128(1) << u32(bits))
@@ -2380,13 +2451,15 @@ ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if value.integer == 0 else 0}), ct_flow(.Normal), true
}
if op == .Bit_Not {
if value.kind != .Integer || !types.is_concrete_integer(value.type) {
representation := types.runtime_representation(value.type, &state.checker.module.types)
if value.kind != .Integer || !types.is_concrete_integer(representation) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'~' requires a concrete integer operand")
}
value.integer = ct_normalize_integer(state, ~value.integer, value.type)
return ct_add_value(state, value), ct_flow(.Normal), true
}
if value.kind == .Integer {
representation := types.runtime_representation(value.type, &state.checker.module.types)
if value.kind == .Integer && types.is_signed(representation, state.checker.target) {
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
if overflow {
state.error = .Overflow
@@ -2395,7 +2468,7 @@ ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span
value.integer = result
return ct_add_value(state, value), ct_flow(.Normal), true
}
if value.kind == .Float {
if value.kind == .Float && types.is_float(representation, state.checker.target) {
value.float = -value.float
return ct_add_value(state, value), ct_flow(.Normal), true
}
@@ -2409,10 +2482,11 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
}
left := state.values[left_id]
right := state.values[right_id]
store := &state.checker.module.types
is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge
if left.kind == .Null || right.kind == .Null {
if (op != .Eq && op != .Ne) ||
!types.is_optional(left.type, &state.checker.module.types) ||
!types.is_optional(left.type, store) ||
!types.equal(left.type, right.type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'null' only supports '==' and '!=' with an optional value")
}
@@ -2426,36 +2500,43 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
if left.kind != .Type || right.kind != .Type || (op != .Eq && op != .Ne) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "type values only support '==' and '!=' with another type")
}
ok := types.equal(types.Type(left.index), types.Type(right.index))
equal := types.equal(types.Type(left.index), types.Type(right.index))
if op == .Ne {
ok = !ok
equal = !equal
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
}
if left.kind == .Bool && right.kind == .Bool {
if op != .Eq && op != .Ne {
if left.kind == .Bool || right.kind == .Bool {
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
if left.kind != .Bool || right.kind != .Bool || !compatible ||
!types.is_bool(operation.representation) || (op != .Eq && op != .Ne) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='")
}
ok := left.integer == right.integer
equal := left.integer == right.integer
if op == .Ne {
ok = !ok
equal = !equal
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
}
if left.kind == .Float || right.kind == .Float {
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
distinct_operation := types.is_distinct(left.type, store) || types.is_distinct(right.type, store)
if distinct_operation && (!compatible || !types.is_float(operation.representation, state.checker.target)) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
lf := left.float if left.kind == .Float else f64(left.integer)
rf := right.float if right.kind == .Float else f64(right.integer)
if is_compare {
ok := false
result := false
#partial switch op {
case .Eq: ok = lf == rf
case .Ne: ok = lf != rf
case .Lt: ok = lf < rf
case .Le: ok = lf <= rf
case .Gt: ok = lf > rf
case .Ge: ok = lf >= rf
case .Eq: result = lf == rf
case .Ne: result = lf != rf
case .Lt: result = lf < rf
case .Le: result = lf <= rf
case .Gt: result = lf > rf
case .Ge: result = lf >= rf
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if result else 0}), ct_flow(.Normal), true
}
result := lf
#partial switch op {
@@ -2463,57 +2544,46 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
case .Sub: result = lf - rf
case .Mul: result = lf * rf
case .Div: result = lf / rf
case:
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
result_type := types.widest(left.type, right.type)
if !types.is_float(result_type, state.checker.target) {
result_type = types.F64
result_type := operation.result
if !distinct_operation {
result_type = types.widest(left.type, right.type)
if !types.is_float(result_type, state.checker.target) {
result_type = types.F64
}
}
if types.bits(result_type, state.checker.target) == 32 {
result = f64(f32(result))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
}
if ct_is_integer_like(left) && ct_is_integer_like(right) {
if is_compare {
ok := false
#partial switch op {
case .Eq: ok = left.integer == right.integer
case .Ne: ok = left.integer != right.integer
case .Lt: ok = left.integer < right.integer
case .Le: ok = left.integer <= right.integer
case .Gt: ok = left.integer > right.integer
case .Ge: ok = left.integer >= right.integer
if types.is_enum(left.type, store) || types.is_enum(right.type, store) {
if !types.equal(left.type, right.type) || (op != .Eq && op != .Ne) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "enum values only support '==' and '!=' with the same enum type")
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
}
if op == .Div {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Integer_Division, span,
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
)
}
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
result_type := types.widest(left.type, right.type)
if !types.is_concrete_integer(result_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
equal := left.integer == right.integer
if op == .Ne {
equal = !equal
}
value := left.integer & right.integer
#partial switch op {
case .Bit_Or: value = left.integer | right.integer
case .Bit_Xor: value = left.integer ~ right.integer
}
value = ct_normalize_integer(state, value, result_type)
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
}
if op == .Shift_Left || op == .Shift_Right || op == .Shift_Left_Saturating {
if !types.is_concrete_integer(left.type) || !types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
left_representation := types.runtime_representation(left.type, store)
if !types.is_concrete_integer(left_representation) ||
!types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift requires a concrete integer value and unsigned integer count")
}
bits := types.bits(left.type, state.checker.target)
bits := types.bits(left_representation, state.checker.target)
if right.integer >= i128(bits) {
if op != .Shift_Left_Saturating {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift count exceeds integer width")
}
endpoint := i128(0)
if left.integer != 0 {
if types.is_signed(left.type, state.checker.target) {
if types.is_signed(left_representation, state.checker.target) {
endpoint = -(i128(1) << u32(bits-1)) if left.integer < 0 else (i128(1) << u32(bits-1))-1
} else {
endpoint = (i128(1) << u32(bits))-1
@@ -2524,7 +2594,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
count := u32(right.integer)
if op == .Shift_Right {
value := left.integer >> count
if !types.is_signed(left.type, state.checker.target) {
if !types.is_signed(left_representation, state.checker.target) {
value = ct_normalize_integer(state, left.integer, left.type) >> count
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
@@ -2532,7 +2602,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
if op == .Shift_Left_Saturating {
factor := i128(1) << count
value := left.integer*factor
if types.is_signed(left.type, state.checker.target) {
if types.is_signed(left_representation, state.checker.target) {
minimum := -(i128(1) << u32(bits-1))
maximum := (i128(1) << u32(bits-1))-1
value = max(minimum, min(maximum, value))
@@ -2545,6 +2615,42 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
value := ct_normalize_integer(state, ct_normalize_integer(state, left.integer, left.type) << count, left.type)
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
}
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
distinct_operation := types.is_distinct(left.type, store) || types.is_distinct(right.type, store)
if distinct_operation && (!compatible || !types.is_concrete_integer(operation.representation)) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
if is_compare {
result := false
#partial switch op {
case .Eq: result = left.integer == right.integer
case .Ne: result = left.integer != right.integer
case .Lt: result = left.integer < right.integer
case .Le: result = left.integer <= right.integer
case .Gt: result = left.integer > right.integer
case .Ge: result = left.integer >= right.integer
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if result else 0}), ct_flow(.Normal), true
}
if op == .Div {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Integer_Division, span,
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
)
}
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
result_type := operation.result if distinct_operation else types.widest(left.type, right.type)
if !types.is_concrete_integer(types.runtime_representation(result_type, store)) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
}
value := left.integer & right.integer
#partial switch op {
case .Bit_Or: value = left.integer | right.integer
case .Bit_Xor: value = left.integer ~ right.integer
}
value = ct_normalize_integer(state, value, result_type)
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
}
value: i128
overflow := false
#partial switch op {
@@ -2559,9 +2665,12 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
state.error = .Overflow
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
result_type := types.widest(left.type, right.type)
if !types.is_concrete_integer(result_type) && !types.is_enum(result_type, &state.checker.module.types) {
result_type = ct_default_integer_type(value)
result_type := operation.result
if !distinct_operation {
result_type = types.widest(left.type, right.type)
if !types.is_concrete_integer(result_type) {
result_type = ct_default_integer_type(value)
}
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
}
@@ -2580,14 +2689,14 @@ ct_eval_division_builtin :: proc(
}
left := state.values[left_id]
right := state.values[right_id]
result_type := types.widest(left.type, right.type)
if !types.is_concrete_scalar(result_type) || types.is_bool(result_type) {
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
if !compatible || types.is_bool(operation.representation) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "division builtins require compatible numeric operands",
)
}
left_id, left_ok := ct_coerce_value(state, left_id, result_type, span)
right_id, right_ok := ct_coerce_value(state, right_id, result_type, span)
left_id, left_ok := ct_coerce_value(state, left_id, operation.result, span)
right_id, right_ok := ct_coerce_value(state, right_id, operation.result, span)
if !left_ok || !right_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
@@ -2614,10 +2723,10 @@ ct_eval_division_builtin :: proc(
result += right.float
}
}
if types.bits(result_type, state.checker.target) == 32 {
if types.bits(operation.representation, state.checker.target) == 32 {
result = f64(f32(result))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
return ct_add_value(state, Ct_Value{kind=.Float, type=operation.result, float=result}), ct_flow(.Normal), true
}
if left.kind != .Integer || right.kind != .Integer {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "division builtins require compatible numeric operands")
@@ -2626,8 +2735,8 @@ ct_eval_division_builtin :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
}
is_quotient := kind == .Trunc || kind == .Floor || kind == .Exact || kind == .Ceil
if is_quotient && types.is_signed(result_type, state.checker.target) {
minimum := -(i128(1) << u32(types.bits(result_type, state.checker.target)-1))
if is_quotient && types.is_signed(operation.representation, state.checker.target) {
minimum := -(i128(1) << u32(types.bits(operation.representation, state.checker.target)-1))
if left.integer == minimum && right.integer == -1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Overflow, span, "signed integer division overflow")
}
@@ -2656,7 +2765,7 @@ ct_eval_division_builtin :: proc(
}
case:
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true
return ct_add_value(state, Ct_Value{kind=.Integer, type=operation.result, integer=result}), ct_flow(.Normal), true
}
ct_eval_division_call :: proc(
@@ -2684,14 +2793,20 @@ ct_eval_division_call :: proc(
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
left, flow, ok = ct_eval_expr(state, expr.args[0], state.values[right].type, depth+1)
_, distinct_ok := types.distinct_scalar_backing(state.values[right].type, &checker.module.types)
left, flow, ok = ct_eval_expr(
state, expr.args[0], state.values[right].type, depth+1, distinct_ok,
)
} else {
left, flow, ok = ct_eval_expr(state, expr.args[0], hint, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right_hint := hint if types.is_valid(hint) else state.values[left].type
right, flow, ok = ct_eval_expr(state, expr.args[1], right_hint, depth+1)
_, distinct_ok := types.distinct_scalar_backing(right_hint, &checker.module.types)
right, flow, ok = ct_eval_expr(
state, expr.args[1], right_hint, depth+1, distinct_ok && right_const,
)
}
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
@@ -2864,6 +2979,10 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
value := state.values[id]
if types.can_retype_distinct(value.type, target, &state.checker.module.types) {
value.type = target
return ct_add_value(state, value), ct_flow(.Normal), true
}
if !types.is_concrete_scalar(target) || types.is_bool(target) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types")
}
@@ -3039,6 +3158,16 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
}), ct_flow(.Normal), true
}
if tag == "distinct" {
child_value := ct_add_value(state, Ct_Value{
kind=.Type, type=types.INVALID, index=u64(item.child),
})
payload_start := u32(len(state.children))
append(&state.children, child_value)
return ct_add_value(state, Ct_Value{
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
}), ct_flow(.Normal), true
}
if tag != "record" {
start := u32(len(state.children))
return ct_add_value(state, Ct_Value{
@@ -3522,7 +3651,11 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
if (builtin == .Size_Of || builtin == .Align_Of) && !valid_layout_type(checker, target) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
}
if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(target) {
bound_representation := target
if backing, ok := types.distinct_scalar_backing(target, &checker.module.types); ok {
bound_representation = backing
}
if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(bound_representation) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
}
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
@@ -3593,8 +3726,39 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
)
named_item, named_ok := types.node(&checker.module.types, named_type)
target := types.resolve_alias(named_type, &checker.module.types)
if named_ok && named_item.kind == .Alias &&
types.is_concrete_scalar(target) && !types.is_bool(target) {
target_item, target_ok := types.node(&checker.module.types, target)
if target_ok && target_item.kind == .Distinct {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state,
.Not_Comptime,
expr.span,
"distinct type '%s' expects 1 argument, got %d",
symbol_text(checker, expr.name),
len(expr.args),
)
}
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
actual := state.values[value].type
if !types.can_retype_distinct(actual, target, &checker.module.types) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state,
.Not_Comptime,
expr.span,
"distinct type '%s' requires an exact %s value, got %s",
symbol_text(checker, expr.name),
types.name(target_item.child),
types.name(actual),
)
}
result := state.values[value]
result.type = target
return ct_add_value(state, result), ct_flow(.Normal), true
}
if named_ok && named_item.kind == .Alias && types.is_concrete_scalar(target) {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state,
@@ -5027,11 +5191,17 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
}
} else {
value_expected := expected
numeric_operation := false
if statement.assignment_op == .Shift_Left || statement.assignment_op == .Shift_Right ||
statement.assignment_op == .Shift_Left_Saturating {
value_expected = types.U64
} else if statement.assignment_op != .Set &&
is_numeric_constant_expr(checker, statement.expr) {
_, numeric_operation = types.distinct_scalar_backing(expected, &checker.module.types)
}
value, flow, ok = ct_eval_expr(state, statement.expr, value_expected, depth+1)
value, flow, ok = ct_eval_expr(
state, statement.expr, value_expected, depth+1, numeric_operation,
)
}
if !ok || flow.kind != .Normal {
return flow, ok
+46 -28
View File
@@ -417,8 +417,9 @@ emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction:
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
emit_trap_call(emitter, message)
if types.is_runtime_value(instruction.type, &emitter.module.types) {
if !types.is_float(instruction.type, emitter.module.target) {
if !types.is_concrete_scalar(instruction.type) {
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !types.is_float(representation, emitter.module.target) {
if !types.is_concrete_scalar(representation) {
fmt.sbprintf(
&emitter.builder,
" %%v%d = freeze %s zeroinitializer\n",
@@ -583,8 +584,9 @@ emit_checked_arithmetic :: proc(
float_op: string,
overflow_message: string,
) {
representation := types.runtime_representation(instruction.type, &emitter.module.types)
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
if types.is_float(representation, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, float_op, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
@@ -592,7 +594,7 @@ emit_checked_arithmetic :: proc(
strings.write_string(&emitter.builder, "\n")
return
}
prefix := "u" if types.is_unsigned(instruction.type, emitter.module.target) else "s"
prefix := "u" if types.is_unsigned(representation, emitter.module.target) else "s"
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s%s.with.overflow.%s(%s ", type_name, prefix, mnemonic, type_name, type_name)
@@ -625,8 +627,9 @@ emit_division_zero_guard :: proc(
instruction_index: int,
instruction: ir.Instruction,
) {
representation := types.runtime_representation(instruction.type, &emitter.module.types)
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
if types.is_float(representation, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%divzero%d = fcmp oeq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0.000000e+00\n")
@@ -655,7 +658,8 @@ emit_division_overflow_guard :: proc(
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
representation := types.runtime_representation(instruction.type, &emitter.module.types)
min_value := -(i128(1) << u32(types.bits(representation, emitter.module.target)-1))
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n %%divminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
@@ -683,7 +687,8 @@ emit_float_division_builtin :: proc(
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
suffix := "f32" if types.bits(instruction.type, emitter.module.target) == 32 else "f64"
representation := types.runtime_representation(instruction.type, &emitter.module.types)
suffix := "f32" if types.bits(representation, emitter.module.target) == 32 else "f64"
if instruction.op == .Rem_Checked || instruction.op == .Mod_Checked {
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked else fmt.tprintf("%%rawrem%d", instruction_index)
@@ -740,7 +745,8 @@ emit_integer_remainder_builtin :: proc(
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
signed := types.is_signed(instruction.type, emitter.module.target)
representation := types.runtime_representation(instruction.type, &emitter.module.types)
signed := types.is_signed(representation, emitter.module.target)
raw_name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked || !signed else fmt.tprintf("%%rawrem%d", instruction_index)
if !signed {
fmt.sbprintf(&emitter.builder, " %s = urem %s ", raw_name, type_name)
@@ -749,7 +755,7 @@ emit_integer_remainder_builtin :: proc(
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
} else {
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
min_value := -(i128(1) << u32(types.bits(representation, emitter.module.target)-1))
fmt.sbprintf(&emitter.builder, " %%remminlo%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n %%remminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
@@ -782,7 +788,8 @@ emit_integer_quotient_builtin :: proc(
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
signed := types.is_signed(instruction.type, emitter.module.target)
representation := types.runtime_representation(instruction.type, &emitter.module.types)
signed := types.is_signed(representation, emitter.module.target)
operation := "sdiv" if signed else "udiv"
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Div_Trunc_Checked else fmt.tprintf("%%divq%d", instruction_index)
fmt.sbprintf(&emitter.builder, " %s = %s %s ", name, operation, type_name)
@@ -832,13 +839,14 @@ emit_division_builtin :: proc(
instruction: ir.Instruction,
) {
emit_division_zero_guard(emitter, instructions, instruction_index, instruction)
if types.is_float(instruction.type, emitter.module.target) {
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if types.is_float(representation, emitter.module.target) {
emit_float_division_builtin(emitter, instructions, instruction_index, instruction)
return
}
quotient := instruction.op == .Div_Trunc_Checked || instruction.op == .Div_Floor_Checked ||
instruction.op == .Div_Exact_Checked || instruction.op == .Div_Ceil_Checked
if quotient && types.is_signed(instruction.type, emitter.module.target) {
if quotient && types.is_signed(representation, emitter.module.target) {
emit_division_overflow_guard(emitter, instructions, instruction_index, instruction)
}
if quotient {
@@ -858,7 +866,8 @@ emit_shift :: proc(
if valid_instruction(instructions, instruction.b) {
count_type = instructions[instruction.b].type
}
if !types.is_concrete_integer(instruction.type) ||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !types.is_concrete_integer(representation) ||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!types.is_concrete_integer(count_type) || !types.is_unsigned(count_type, emitter.module.target) ||
!valid_value(instructions, instruction.b, count_type, &emitter.module.types) {
@@ -866,7 +875,7 @@ emit_shift :: proc(
return
}
type_name := llvm_type(instruction.type, &emitter.module.types)
bits := types.bits(instruction.type, emitter.module.target)
bits := types.bits(representation, emitter.module.target)
count_bits := types.bits(count_type, emitter.module.target)
if count_bits < 64 {
fmt.sbprintf(&emitter.builder, " %%shift_count64_%d = zext %s ", instruction_index, llvm_type(count_type, &emitter.module.types))
@@ -893,7 +902,7 @@ emit_shift :: proc(
}
operation := "shl"
if instruction.op == .Shift_Right {
operation = "ashr" if types.is_signed(instruction.type, emitter.module.target) else "lshr"
operation = "ashr" if types.is_signed(representation, emitter.module.target) else "lshr"
}
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
@@ -911,7 +920,7 @@ emit_shift :: proc(
if bits < 64 {
fmt.sbprintf(&emitter.builder, " %%shift_safe%d = trunc i64 %%shift_safe64_%d to %s\n", instruction_index, instruction_index, type_name)
}
signed := types.is_signed(instruction.type, emitter.module.target)
signed := types.is_signed(representation, emitter.module.target)
intrinsic := "sshl" if signed else "ushl"
fmt.sbprintf(&emitter.builder, " %%shift_saturated%d = call %s @llvm.%s.sat.%s(%s ", instruction_index, type_name, intrinsic, type_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
@@ -1868,8 +1877,8 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
case .Retype:
if !valid_instruction(instructions, instruction.a) ||
!types.can_construct_distinct(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid distinct type construction")
!types.can_retype_distinct(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid distinct retype")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
@@ -1891,7 +1900,8 @@ emit_instruction_stream :: proc(
from_repr := types.runtime_representation(from_type, &emitter.module.types)
from_item, from_item_ok := types.node(&emitter.module.types, from_type)
explicit_enum := from_item_ok && from_item.kind == .Enum && from_item.explicit_backing
valid_from := (types.is_concrete_scalar(from_type) || explicit_enum) &&
_, distinct_scalar := types.distinct_scalar_backing(from_type, &emitter.module.types)
valid_from := (types.is_concrete_scalar(from_type) || explicit_enum || distinct_scalar) &&
types.is_concrete_scalar(from_repr) && !types.is_bool(from_repr)
if !valid_from || !types.is_concrete_scalar(instruction.type) || types.is_bool(instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid scalar cast operand")
@@ -1973,12 +1983,15 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%decay_slice%d, i64 %d, 1\n", instruction_index, type_name, instruction_index, array.count)
}
case .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
(!types.is_signed(representation, emitter.module.target) &&
!types.is_float(representation, emitter.module.target)) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
if types.is_float(representation, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = fneg %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
@@ -2028,19 +2041,21 @@ emit_instruction_stream :: proc(
}
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow")
case .Div_Checked:
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
!types.is_float(instruction.type, emitter.module.target) {
!types.is_float(representation, emitter.module.target) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand")
continue
}
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "div", "fdiv", "")
case .Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked:
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
(!types.is_concrete_integer(instruction.type) &&
!types.is_float(instruction.type, emitter.module.target)) {
(!types.is_concrete_integer(representation) &&
!types.is_float(representation, emitter.module.target)) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division builtin operands")
continue
}
@@ -2062,7 +2077,8 @@ emit_instruction_stream :: proc(
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
case .Bit_Not:
if !types.is_concrete_integer(instruction.type) ||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !types.is_concrete_integer(representation) ||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid bitwise complement operand")
continue
@@ -2072,7 +2088,8 @@ emit_instruction_stream :: proc(
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", -1\n")
case .Bit_And, .Bit_Or, .Bit_Xor:
if !types.is_concrete_integer(instruction.type) ||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if !types.is_concrete_integer(representation) ||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid bitwise operands")
@@ -2367,10 +2384,11 @@ emit_instruction_stream :: proc(
}
predicate := ir.Compare_Predicate(instruction.integer)
type_name := llvm_type(operand_type, &emitter.module.types)
if types.is_float(operand_type, emitter.module.target) {
representation := types.runtime_representation(operand_type, &emitter.module.types)
if types.is_float(representation, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = fcmp %s %s ", instruction_index, float_predicate(predicate), type_name)
} else {
fmt.sbprintf(&emitter.builder, " %%v%d = icmp %s %s ", instruction_index, integer_predicate(predicate, types.is_signed(operand_type, emitter.module.target)), type_name)
fmt.sbprintf(&emitter.builder, " %%v%d = icmp %s %s ", instruction_index, integer_predicate(predicate, types.is_signed(representation, emitter.module.target)), type_name)
}
write_operand(&emitter.builder, instructions, instruction.a, operand_type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
+2 -1
View File
@@ -1035,7 +1035,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
}
payload := ast.INVALID_EXPR
end := member.span
if left_brace, ok := allow(parser, .Left_Brace); ok {
if !(parser.no_struct_literal && parser.delimiter_depth == 0) && current(parser).kind == .Left_Brace {
left_brace := advance(parser)
parser.delimiter_depth += 1
skip_newlines(parser)
if current(parser).kind == .Identifier && peek(parser).kind == .Equal {
+27 -3
View File
@@ -1106,9 +1106,33 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
return false
}
can_construct_distinct :: proc(from, to: Type, store: ^Store) -> bool {
item, ok := node(store, to)
return ok && item.kind == .Distinct && item.declared && equal(from, item.child)
distinct_backing :: proc(value: Type, store: ^Store) -> (Type, bool) {
item, ok := node(store, value)
if !ok || item.kind != .Distinct || !item.declared {
return INVALID, false
}
return item.child, true
}
distinct_scalar_backing :: proc(value: Type, store: ^Store) -> (Type, bool) {
_, ok := distinct_backing(value, store)
if !ok {
return INVALID, false
}
backing := runtime_representation(value, store)
return backing, is_concrete_scalar(backing)
}
can_retype_distinct :: proc(from, to: Type, store: ^Store) -> bool {
if backing, ok := distinct_backing(to, store); ok && equal(from, backing) {
return true
}
backing, ok := distinct_backing(from, store)
if !ok || !equal(to, backing) {
return false
}
_, scalar := distinct_scalar_backing(from, store)
return scalar
}
runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
+199 -20
View File
@@ -313,7 +313,9 @@ main func() i32 {
@(test)
typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
text := `main func() void {
text := `D :: distinct u8
main func() void {
_ = ~D(1)
_ = ~u8(0)
_ = (u8(240) & u8(204)) xor u8(15)
_ = u8(129) << 1
@@ -332,12 +334,17 @@ typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
for statement_id in hir_module.functions[0].body {
d_type := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "D")))
for statement_id, index in hir_module.functions[0].body {
statement := hir_module.statements[statement_id]
testing.expect(t, statement.expr != hir.INVALID_EXPR)
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
if index == 0 {
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Bit_Not)
testing.expect_value(t, hir_module.exprs[statement.expr].type, d_type)
} else {
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
}
}
}
@@ -353,7 +360,7 @@ main func() void {
_ = true & false
_ = 1.0 | 2.0
_ = ~p
_ = ~d
_ = d
_ = ~e
_ = u8(1) & i8(1)
_ = u8(1) << signed_count
@@ -410,6 +417,32 @@ main func() i32 {
testing.expect(t, state.exit_code != 0)
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
}
@(test)
runtime_distinct_ordinary_overshift_traps :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-distinct-bitwise-overshift"
main_path := "/tmp/brolang-test-distinct-bitwise-overshift/main.bro"
output := "/tmp/brolang-test-distinct-bitwise-overshift-output"
text := `D :: distinct u8
shift func(value D, count u8) D { return value << count }
main func() i32 {
_ = shift(D(1), 8)
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state, stdout, stderr, err := os2.process_exec(os2.Process_Desc{command=[]string{output}}, context.allocator)
defer delete(stdout)
defer delete(stderr)
testing.expect(t, err == nil)
testing.expect(t, state.exit_code != 0)
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
}
@(test)
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
@@ -1704,6 +1737,11 @@ integer_bound_builtins_compile_and_run :: proc(t: ^testing.T) {
main_path := "/tmp/brolang-test-integer-bounds/main.bro"
output := "/tmp/brolang-test-integer-bounds-output"
text := `MAX_U64 u64 :: maxval!(u64)
Signed :: distinct i16
Unsigned :: distinct u16
Inner :: distinct u8
Outer :: distinct Inner
maximum func($T type) T {
return maxval!(T)
@@ -1720,6 +1758,16 @@ main func() i32 {
if (maximum(u16) != 65535) return 8
if (minval!(c_int) != -2147483648) return 9
if (maxval!(c_ulong) != 18446744073709551615) return 10
signed_min Signed = minval!(Signed)
unsigned_max Unsigned = maxval!(Unsigned)
nested_max Outer = maxval!(Outer)
generic_min Signed = maximum(Signed)
generic_nested Outer = maximum(Outer)
if i16(signed_min) != -32768 { return 11 }
if u16(unsigned_max) != 65535 { return 12 }
if u8(nested_max) != 255 { return 13 }
if i16(generic_min) != 32767 { return 14 }
if u8(generic_nested) != 255 { return 15 }
return 0
}
`
@@ -1736,7 +1784,9 @@ main func() i32 {
@(test)
integer_bound_builtins_reject_invalid_targets :: proc(t: ^testing.T) {
text := `Named :: distinct u8
text := `BadFloat :: distinct f32
BadBool :: distinct bool
BadAggregate :: distinct [2]u8
Choice :: enum { one }
main func() void {
@@ -1747,7 +1797,9 @@ main func() void {
_ = maxval!(uint)
_ = maxval!(f32)
_ = maxval!(bool)
_ = maxval!(Named)
_ = maxval!(BadFloat)
_ = maxval!(BadBool)
_ = maxval!(BadAggregate)
_ = maxval!(Choice)
}
`
@@ -1773,7 +1825,7 @@ main func() void {
}
testing.expect_value(t, bad_arity, 2)
testing.expect(t, bad_type)
testing.expect_value(t, bad_target, 6)
testing.expect_value(t, bad_target, 8)
}
@(test)
@@ -3284,6 +3336,8 @@ milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
main_path := "/tmp/brolang-test-format-errors/main.bro"
text := `io :: import "@std/io"
process :: import "@std/process"
Aggregate :: distinct [2]u8
main func(init process.Init) void {
writer io.Writer :: io.stdout(init.io)
@@ -3299,6 +3353,7 @@ main func(init process.Init) void {
io.print(writer, "{e}", {1,}) catch |_| {}
io.print(writer, "{c}", {i16(65),}) catch |_| {}
io.print(writer, "}", {}) catch |_| {}
io.print(writer, "{}", {Aggregate([1, 2]),}) catch |_| {}
}
`
_ = os2.remove_all(directory)
@@ -3317,7 +3372,7 @@ main func(init process.Init) void {
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := [10]bool{}
found := [11]bool{}
for diagnostic in diagnostics.items {
message := diagnostic.message
found[0] = found[0] || strings.contains(message, "arguments must be a tuple")
@@ -3330,6 +3385,7 @@ main func(init process.Init) void {
found[7] = found[7] || strings.contains(message, "integer format requires an integer argument")
found[8] = found[8] || strings.contains(message, "float format requires a float argument")
found[9] = found[9] || strings.contains(message, "'{c}' requires an unsigned integer that fits in u8")
found[10] = found[10] || strings.contains(message, "io.print '{}' does not support this argument type")
}
testing.expect(t, loaded)
for present in found {
@@ -3402,7 +3458,7 @@ milestone_39_stable_values_and_richer_formatting_compile_and_run :: proc(t: ^tes
testing.expect_value(
t,
string(stdout),
"true -42 1.5 .running bro 2.5 1010 12 ff FF A 1.5000000000000000e+00 {} -9223372036854775808 0 inf nan 1.50000000e+00\n",
"true -42 1.5 .running bro 2.5 1010 12 ff FF A 1.5000000000000000e+00 {} -9223372036854775808 0 inf nan 1.50000000e+00\n-42 -42 1010 12 ff FF A 1.50000000e+00 7\n",
)
testing.expect_value(t, string(stderr), "debug=.idle 2a\n")
}
@@ -7927,6 +7983,28 @@ checked_addition_traps_on_overflow :: proc(t: ^testing.T) {
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
checked_distinct_addition_traps_on_backing_overflow :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-distinct-overflow"
main_path := "/tmp/brolang-test-distinct-overflow/main.bro"
output := "/tmp/brolang-test-distinct-overflow-output"
text := `D :: distinct i8
add func(left, right D) D { return left + right }
main func() i32 {
_ = add(D(127), D(1))
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
checked_runtime_negation_traps_for_every_signed_width :: proc(t: ^testing.T) {
@@ -10708,6 +10786,32 @@ parser_diagnoses_braceless_if_without_parens_or_call :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(diagnostics.items[0].message, "parenthesized"))
}
@(test)
parser_separates_contextual_enum_literal_from_if_block :: proc(t: ^testing.T) {
text := `Kind :: enum {
newline
other
}
main func() void {
kind Kind = .other
if kind != .newline {
}
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, module.statements[module.functions[0].body[1]].kind, ast.Stmt_Kind.If)
}
@(test)
parser_accepts_braceless_while_bodies :: proc(t: ^testing.T) {
text := `ready func() bool { return false }
@@ -13125,6 +13229,7 @@ OtherID :: distinct u32
PointID :: distinct Point
Bytes :: distinct [2]u8
WrappedID :: distinct UserID
Flag :: distinct bool
static_id UserID :: UserID(42)
take func(value UserID) UserID {
return value
@@ -13137,11 +13242,27 @@ main func() i32 {
point PointID :: PointID(Point { x = 1, y = 2 })
bytes Bytes :: Bytes([3, 4])
wrapped WrappedID :: WrappedID(id)
sum UserID = id + 1
matches bool = id == 7
raw u32 = u32(id)
wide usize = usize(id)
real f64 = f64(id)
inner UserID = UserID(wrapped)
terminal u32 = u32(wrapped)
flag_matches bool = Flag(true) == Flag(true)
_ = maybe
_ = pointer
_ = point
_ = bytes
_ = wrapped
_ = sum
_ = matches
_ = raw
_ = wide
_ = real
_ = inner
_ = terminal
_ = flag_matches
return 0
}
`
@@ -13177,17 +13298,33 @@ main func() i32 {
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i32 42"))
testing.expect(t, strings.contains(llvm_text, "select i1 true, i32"))
nominal_hir_add := false
for hir_expr in hir_module.exprs {
nominal_hir_add = nominal_hir_add || hir_expr.kind == .Add && hir_expr.type == user_id
}
testing.expect(t, nominal_hir_add)
retype_count := 0
nominal_ir_add := false
u32_cast, usize_cast, f64_cast := false, false, false
for function in ir_module.functions {
for instruction in function.instructions {
retype_count += 1 if instruction.op == .Retype else 0
nominal_ir_add = nominal_ir_add || instruction.op == .Add_Checked && instruction.type == user_id
if instruction.op == .Scalar_Cast {
u32_cast = u32_cast || instruction.type == types.U32
usize_cast = usize_cast || instruction.type == types.USIZE
f64_cast = f64_cast || instruction.type == types.F64
}
}
}
testing.expect_value(t, retype_count, 4)
testing.expect(t, nominal_ir_add)
testing.expect(t, u32_cast && usize_cast && f64_cast)
testing.expect(t, retype_count >= 5)
}
@(test)
distinct_types_reject_implicit_conversions_operators_and_invalid_backings :: proc(t: ^testing.T) {
distinct_types_reject_implicit_conversions_and_invalid_backings :: proc(t: ^testing.T) {
text := `Opaque :: opaque
UserID :: distinct u32
OtherID :: distinct u32
@@ -13207,9 +13344,14 @@ main func() void {
_ = UserID()
_ = UserID(1, 2)
left UserID :: UserID(5)
right UserID :: UserID(6)
_ = left + right
_ = left == right
raw_operand u32 :: u32(6)
other_operand OtherID :: OtherID(6)
_ = left + raw_operand
_ = raw_operand + left
_ = left + other_operand
_ = left == raw_operand
_ = raw_operand == left
_ = left == other_operand
}
`
source_file := source.Source{path="test.bro", text=text}
@@ -14616,12 +14758,12 @@ dependency_passes test {
defer delete(stderr)
output := string(stderr)
testing.expect_value(t, state.exit_code, 1)
testing.expect(t, strings.contains(output, "root.root_passes [ok]"))
testing.expect(t, strings.contains(output, "root.root_fails [failed]"))
testing.expect(t, strings.contains(output, "root.root_passes...[ok]"))
testing.expect(t, strings.contains(output, "root.root_fails...[failed]"))
testing.expect(t, strings.contains(output, "expected 42, found 41"))
testing.expect(t, strings.contains(output, "root.root_continues [ok]"))
testing.expect(t, strings.contains(output, "root.root_errors [failed]"))
testing.expect(t, strings.contains(output, "dependency.dependency_passes [ok]"))
testing.expect(t, strings.contains(output, "root.root_continues...[ok]"))
testing.expect(t, strings.contains(output, "root.root_errors...[failed]"))
testing.expect(t, strings.contains(output, "dependency.dependency_passes...[ok]"))
testing.expect(t, strings.contains(output, root_path))
testing.expect(t, strings.contains(output, "3 passed, 2 failed"))
}
@@ -15233,6 +15375,43 @@ main func() i32 {
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
distinct_reflection_reports_immediate_backing :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-distinct-reflection"
main_path := "/tmp/brolang-test-distinct-reflection/main.bro"
output := "/tmp/brolang-test-distinct-reflection-output"
text := `meta :: import "@std/meta"
Inner :: distinct u16
Outer :: distinct Inner
OuterAlias :: alias Outer
matches func($Distinct, $Backing type) bool {
match typeinfo!(Distinct) {
.distinct |backing|: return backing == Backing
else: return false
}
}
main func() i32 {
if !$(matches(Inner, u16)) { return 1 }
if !$(matches(Outer, Inner)) { return 2 }
if !$(matches(OuterAlias, Inner)) { return 3 }
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(
directory, output, nil, target.DEFAULT, cimport.Options{}, ".",
), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
static_string_map_infers_array_size_and_preserves_promoted_backing :: proc(t: ^testing.T) {
+72 -2
View File
@@ -9,8 +9,14 @@ LocalID :: distinct u32
PointID :: distinct Point
Bytes :: distinct [2]u8
WrappedID :: distinct LocalID
Signed :: distinct i32
Mask :: distinct u8
Real :: distinct f64
static_id LocalID :: LocalID(42)
static_expression LocalID :: (LocalID(3) + 5) * 2
take func(value LocalID) LocalID {
return value
@@ -26,12 +32,76 @@ main func() i32 {
wrapped WrappedID :: WrappedID(id)
remote ids.UserID :: ids.UserID(8)
remote_copy ids.UserID :: ids.make(9)
_ = static_id
if id + 1 != 8 or 1 + id != 8 { return 1 }
if id - 2 != 5 or 2 * id != 14 or id * 2 != 14 { return 2 }
if -Signed(5) != -5 { return 3 }
real Real :: Real(1.5)
if real + 0.5 != 2.0 or 0.5 + real != 2.0 or real - 0.5 != 1.0 or
real * 2.0 != 3.0 or real / 0.5 != 3.0 {
return 4
}
if !(id == 7) or !(7 == id) or !(id != 8) or !(8 != id) or
!(id < 8) or !(6 < id) or !(id <= 7) or !(7 <= id) or
!(id > 6) or !(8 > id) or !(id >= 7) or !(7 >= id) {
return 5
}
signed Signed :: Signed(-7)
if divtrunc!(signed, 3) != -2 or divfloor!(signed, 3) != -3 or
divexact!(Signed(8), 2) != 4 or divceil!(signed, 3) != -2 or
rem!(signed, 3) != -1 or mod!(signed, 3) != 2 {
return 6
}
mask Mask :: Mask(10)
if ~mask != 245 or (mask & 6) != 2 or (mask | 5) != 15 or (mask xor 3) != 9 {
return 7
}
if mask << u8(1) != 20 or mask >> u8(1) != 5 or Mask(128) <<| u8(1) != 255 {
return 8
}
arithmetic Signed = Signed(4)
arithmetic += 3
arithmetic -= 2
arithmetic *= 5
fraction Real = Real(3.0)
fraction /= 2.0
if arithmetic != 25 or fraction != 1.5 { return 9 }
bits Mask = Mask(3)
bits |= 8
bits xor= 2
bits &= 9
bits <<= u8(1)
bits >>= u8(1)
bits <<|= u8(5)
if bits != 255 { return 10 }
values [10]u8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
if values[LocalID(4)] != 4 { return 11 }
section []u8 = values[LocalID(2)..LocalID(5)]
if section.len != 3 or section[usize(0)] != 2 or section[usize(2)] != 4 { return 12 }
if u32(id) != 7 or usize(id) != 7 or f64(id) != 7.0 { return 13 }
extracted LocalID = LocalID(wrapped)
if extracted != id { return 14 }
minimum Signed = minval!(Signed)
maximum Mask = maxval!(Mask)
nested_max WrappedID = maxval!(WrappedID)
if i32(minimum) != minval!(i32) or u8(maximum) != 255 or u32(nested_max) != maxval!(u32) {
return 15
}
if static_expression != 16 { return 16 }
_ = maybe
_ = pointer
_ = point
_ = bytes
_ = wrapped
_ = remote
_ = remote_copy
return 0
+19
View File
@@ -24,6 +24,12 @@ Config :: struct {
maybe ?i32
payload Payload
}
Count :: distinct i32
Byte :: distinct u8
Ratio :: distinct f32
Inner :: distinct u32
Outer :: distinct Inner
first Config :: Config {
enabled = true,
@@ -105,6 +111,19 @@ main func(init process.Init) i32 {
}) catch |_| {
return 3
}
io.print(writer, "{} {d} {b} {o} {x} {X} {c} {e} {}\n", {
Count(i32(-42)),
Count(i32(-42)),
Byte(u8(10)),
Byte(u8(10)),
Byte(u8(255)),
Byte(u8(255)),
Byte(u8('A')),
Ratio(f32(1.5)),
Outer(Inner(u32(7))),
}) catch |_| {
return 4
}
debug.print("debug={} {x}\n", {State.idle, 42})
return 0
}
-13
View File
@@ -1,13 +0,0 @@
id = "brolang"
name = "Brolang"
version = "0.1.1"
schema_version = 1
authors = ["Brolang contributors"]
description = "Brolang language support"
repository = "ssh://git@gitea.hl-valdemar.dev:2222/hl-valdemar/brolang.git"
languages = ["languages/brolang"]
[grammars.brolang]
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
rev = "09571ffeb9b99216eb179bd452c0749640cf80e7"
path = "tree-sitter-brolang"
-14
View File
@@ -1,14 +0,0 @@
name = "Brolang"
grammar = "brolang"
path_suffixes = ["bro", "hon"]
line_comments = ["# "]
hard_tabs = true
tab_size = 4
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
]
-129
View File
@@ -1,129 +0,0 @@
(comment) @comment
[
(string)
(multiline_string)
] @string
(character) @string
(escape_sequence) @string.escape
[
(integer)
(float)
] @number
(boolean) @boolean
[
(null)
(unreachable)
(undefined)
] @constant.builtin
(builtin_type) @type.builtin
(named_type) @type
(named_type
(qualified_identifier
(identifier) @function .)
(argument_list))
(struct_literal
type: (qualified_identifier
(identifier) @function .)
(argument_list))
(type_declaration name: (identifier) @type)
(function_declaration name: (identifier) @function)
(test_declaration name: (identifier) @function)
(parameter name: (identifier) @variable.parameter)
(intrinsic_call_expression function: (identifier) @function.builtin)
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
(field_expression field: (integer) @property)
(field_initializer name: (identifier) @property)
(keyed_field_initializer name: (identifier) @property)
(record_field name: (identifier) @property)
(enum_member name: (identifier) @property)
(enum_literal name: (identifier) @property)
(import_declaration alias: (identifier) @variable)
(opaque_type) @keyword
[
"func"
"test"
"c_func"
"struct"
"c_struct"
"union"
"enum"
"distinct"
"alias"
"import"
"hide"
"return"
"try"
"catch"
"mut"
"orelse"
"and"
"or"
"if"
"while"
"for"
"expand"
"break"
"continue"
"defer"
"errdefer"
"yield"
"match"
"else"
] @keyword
[
"::"
"="
"+="
"-="
"*="
"/="
"=="
"!="
"<"
"<="
">"
">="
"+"
"-"
"*"
"/"
"!"
"&"
"@"
"?"
"^"
".."
"..="
"|"
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
","
"."
":"
";"
] @punctuation.delimiter
+9 -4
View File
@@ -142,17 +142,22 @@ is_version_command :: proc(arg: string) -> bool {
}
template_root_valid :: proc(root: string) -> bool {
std_build, std_error := filepath.join({root, "std", "build", "build.bro"})
if std_error != nil {
std_build_bro, bro_error := filepath.join({root, "std", "build", "build.bro"})
if bro_error != nil {
return false
}
defer delete(std_build)
defer delete(std_build_bro)
std_build_hon, hon_error := filepath.join({root, "std", "build", "build.hon"})
if hon_error != nil {
return false
}
defer delete(std_build_hon)
ffi_stdio, ffi_error := filepath.join({root, "ffi", "c", "stdio.bro"})
if ffi_error != nil {
return false
}
defer delete(ffi_stdio)
return os.exists(std_build) && os.exists(ffi_stdio)
return (os.exists(std_build_bro) || os.exists(std_build_hon)) && os.exists(ffi_stdio)
}
find_template_root :: proc(allocator := context.allocator) -> (string, bool) {
+40
View File
@@ -316,6 +316,21 @@ hide format_field_name func($T type, index usize) []u8 {
else: compile_error!("io.print arguments must be a tuple")
}
}
hide distinct_value func($Backing, $Distinct type, value Distinct) Backing {
return ptrcast!(Backing, &value)^
}
hide scalar_or_distinct_type func($T type) bool {
match typeinfo!(T) {
.bool: return true
.integer: return true
.float: return true
.distinct: return true
else: return false
}
}
hide write_integer func(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
match typeinfo!(T) {
@@ -324,6 +339,11 @@ hide write_integer func(output Writer, $T type, value T, base u64, uppercase boo
} else {
try write_integer_unsigned(output, u64(value), base, uppercase)
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_integer(output, distinct_value(backing, T, value), base, uppercase)
} else {
compile_error!("io.print integer format requires an integer argument")
}
else: compile_error!("io.print integer format requires an integer argument")
}
return
@@ -351,6 +371,11 @@ hide write_float func(output Writer, $T type, value T, scientific bool) void ! W
}
try write_all(output, buffer[0..usize(count)])
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_float(output, distinct_value(backing, T, value), scientific)
} else {
compile_error!("io.print float format requires a float argument")
}
else: compile_error!("io.print float format requires a float argument")
}
return
@@ -360,6 +385,11 @@ hide write_decimal func(output Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.integer: try write_integer(output, value, 10, false)
.float: try write_float(output, value, false)
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_decimal(output, distinct_value(backing, T, value))
} else {
compile_error!("io.print '{d}' requires an integer or float argument")
}
else: compile_error!("io.print '{d}' requires an integer or float argument")
}
return
@@ -374,6 +404,11 @@ hide write_character func(output Writer, $T type, value T) void ! WriteError {
buffer [1]u8 = [u8(value)]
try write_all(output, buffer[..])
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_character(output, distinct_value(backing, T, value))
} else {
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
return
@@ -401,6 +436,11 @@ hide write_default func(output Writer, $T type, value T) void ! WriteError {
}
return .write_failed
}
.distinct |backing|: if scalar_or_distinct_type(backing) {
try write_default(output, distinct_value(backing, T, value))
} else {
compile_error!("io.print '{}' does not support this argument type")
}
else: compile_error!("io.print '{}' does not support this argument type")
}
return
+1 -1
View File
@@ -40,7 +40,7 @@ TypeInfo :: union(enum) {
record RecordInfo
union void
fallible void
distinct void
distinct type
}
EnumFieldStruct func($E, $Field type, $default ?Field) type {
+17
View File
@@ -8,6 +8,10 @@ TestTokenKind :: enum(u8) {
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
TestArrayAlias :: alias [3]u16
TestInner :: distinct u16
TestOuter :: distinct TestInner
TestOuterAlias :: alias TestOuter
hide array_info_matches func($Array, $Child type, $len usize) bool {
match typeinfo!(Array) {
@@ -15,6 +19,13 @@ hide array_info_matches func($Array, $Child type, $len usize) bool {
else: return false
}
}
hide distinct_info_matches func($Distinct, $Backing type) bool {
match typeinfo!(Distinct) {
.distinct |backing|: return backing == Backing
else: return false
}
}
array_reflection_exposes_child_and_logical_length test {
try testing.expect($(array_info_matches([4]i32, i32, 4)))
@@ -23,6 +34,12 @@ array_reflection_exposes_child_and_logical_length test {
try testing.expect($(array_info_matches([2]mut i64, i64, 2)))
try testing.expect($(array_info_matches([2;0]u8, u8, 2)))
}
distinct_reflection_exposes_immediate_backing test {
try testing.expect($(distinct_info_matches(TestInner, u16)))
try testing.expect($(distinct_info_matches(TestOuter, TestInner)))
try testing.expect($(distinct_info_matches(TestOuterAlias, TestInner)))
}
enum_field_struct_defaults test {
names TestNames = {
-714
View File
@@ -1,714 +0,0 @@
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
const PREC = {
RANGE: 1,
FALLBACK: 2,
OR: 3,
AND: 4,
COMPARE: 5,
BITWISE: 6,
SHIFT: 7,
SUM: 8,
PRODUCT: 9,
PREFIX: 10,
POSTFIX: 11,
};
module.exports = grammar({
name: 'brolang',
word: $ => $.identifier,
extras: $ => [/[ \t\r]/, $.comment],
conflicts: $ => [
[$.expression, $.qualified_identifier],
[$.constant_declaration, $.variable_declaration, $.expression],
[$.constant_declaration, $.variable_declaration, $.expression, $.qualified_identifier],
[$.function_declaration],
[$.if_statement],
[$.enum_literal],
[$.array_type],
[$.array_type, $.expression],
[$.array_type, $.array_literal],
[$.expression_statement, $.parenthesized_expression],
[$.assignment_statement, $.expression_statement],
[$.labeled_block, $.expression],
[$.block, $.tuple_literal, $.anonymous_record_literal],
[$.field_initializer, $.expression],
[$.capture_list, $.expression],
[$.match_capture, $.expression],
[$.statement, $.labeled_block],
],
rules: {
source_file: $ => repeat(choice($._newline, $._top_level_declaration)),
_top_level_declaration: $ => choice(
$.import_declaration,
$.test_import_declaration,
$.function_declaration,
$.test_declaration,
$.type_declaration,
$.global_constant_declaration,
$.global_variable_declaration,
),
import_declaration: $ => seq(
optional(seq(field('alias', $.identifier), '::', repeat($._newline))),
'import',
repeat($._newline),
field('path', $.string),
),
test_import_declaration: $ => seq(
'test',
'import',
repeat($._newline),
field('path', $.string),
),
test_declaration: $ => seq(
field('name', $.identifier),
'test',
repeat($._newline),
field('body', $.block),
),
function_declaration: $ => seq(
optional('hide'),
field('name', $.identifier),
field('kind', choice('func', 'c_func')),
field('parameters', $.parameter_list),
repeat($._newline),
field('result', $.type),
optional(seq('!', field('error', $._error_type))),
optional(choice(
field('body', $.block),
seq(repeat1($._newline), field('body', $.block)),
)),
),
type_declaration: $ => prec(1, seq(
optional('hide'),
field('name', $.identifier),
'::',
repeat($._newline),
field('value', choice(
$.struct_type,
$.c_struct_type,
$.union_type,
$.enum_type,
$.opaque_type,
$.distinct_type,
$.alias_type,
)),
)),
global_constant_declaration: $ => seq(
optional('hide'),
field('name', $.identifier),
optional(field('type', $.type)),
'::',
repeat($._newline),
field('value', $._value),
),
global_variable_declaration: $ => seq(
optional('hide'),
field('name', $.identifier),
optional(field('type', $.type)),
'=',
repeat($._newline),
field('value', $._value),
),
struct_type: $ => seq('struct', repeat($._newline), $.record_body),
c_struct_type: $ => seq('c_struct', repeat($._newline), $.record_body),
opaque_type: _ => 'opaque',
distinct_type: $ => seq('distinct', field('type', $.type)),
alias_type: $ => seq('alias', field('type', $.type)),
union_type: $ => seq(
'union',
optional(seq('(', choice('enum', $.type), ')')),
repeat($._newline),
$.record_body,
),
enum_type: $ => seq(
'enum',
optional(seq('(', field('backing', $.type), ')')),
repeat($._newline),
$.enum_body,
),
record_body: $ => seq(
'{',
repeat(choice($._newline, seq($.record_field, optional(',')))),
'}',
),
record_field: $ => choice(
seq(
field('name', $.identifier),
field('type', choice($.type, $.struct_type)),
optional(seq('=', repeat($._newline), field('default', $.expression))),
),
field('type', choice($.type, $.struct_type)),
),
enum_body: $ => seq(
'{',
repeat(choice($._newline, seq($.enum_member, optional(',')))),
'}',
),
enum_member: $ => seq(
field('name', $.identifier),
optional(seq('=', optional('-'), field('value', $.integer))),
),
parameter_list: $ => seq(
'(',
commaSep($, choice($.parameter, '...')),
')',
),
parameter: $ => seq(
optional('$'),
field('name', choice($.identifier, $.sink)),
repeat(seq(',', repeat($._newline), optional('$'), field('name', choice($.identifier, $.sink)))),
field('type', $.type),
),
type: $ => prec.left(seq(
$._type_atom,
repeat(seq('|', $._type_atom)),
)),
_type_atom: $ => choice(
$.builtin_type,
$.intrinsic_type,
$.named_type,
$.optional_type,
$.pointer_type,
$.array_type,
$.function_type,
$.parenthesized_type,
),
parenthesized_type: $ => seq('(', repeat($._newline), $.type, repeat($._newline), ')'),
named_type: $ => prec.right(seq($.qualified_identifier, optional($.argument_list))),
intrinsic_type: $ => prec(PREC.POSTFIX, seq(
field('function', alias('struct_type!', $.identifier)),
field('arguments', $.argument_list),
)),
optional_type: $ => seq('?', $.type),
pointer_type: $ => seq(
choice('@', '*'),
optional('mut'),
$.type,
),
array_type: $ => seq(
'[',
repeat($._newline),
optional(choice(
seq('*', ';', field('sentinel', $._type_constant)),
seq(';', field('sentinel', $._type_constant)),
seq(
field('length', choice($.sink, $.expression)),
optional(seq(';', field('sentinel', $._type_constant))),
),
)),
repeat($._newline),
']',
optional('mut'),
field('element', $.type),
),
function_type: $ => prec.right(seq(
choice('func', 'c_func'),
$.parameter_list,
repeat($._newline),
field('result', $.type),
optional(seq('!', field('error', $._error_type))),
)),
_error_type: $ => choice($.type, $.enum_type, $.union_type),
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
builtin_type: _ => choice(
'void', 'noreturn', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
'isize', 'usize', 'f32', 'f64',
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
'c_int', 'c_uint', 'c_long', 'c_ulong', 'c_longlong',
'c_ulonglong', 'c_float', 'c_double', 'c_longdouble',
),
block: $ => seq(
'{',
repeat(choice($._newline, $.statement)),
'}',
),
statement: $ => choice(
$.constant_declaration,
$.variable_declaration,
$.assignment_statement,
$.return_statement,
$.yield_statement,
$.if_statement,
$.while_statement,
$.for_statement,
$.match_statement,
$.break_statement,
$.continue_statement,
$.defer_statement,
$.labeled_block,
$.block,
$.expression_statement,
),
constant_declaration: $ => seq(
field('name', choice($.identifier, $.sink)),
optional(field('type', $.type)),
'::',
repeat($._newline),
field('value', $._value),
),
variable_declaration: $ => seq(
field('name', choice($.identifier, $.sink)),
field('type', $.type),
'=',
repeat($._newline),
field('value', $._value),
),
assignment_statement: $ => seq(
field('left', $.expression),
field('operator', choice('=', '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|=')),
repeat($._newline),
field('right', $._value),
),
expression_statement: $ => $.expression,
return_statement: $ => prec.right(seq(
'return',
optional(field('value', $._value)),
)),
yield_statement: $ => seq(
'yield',
optional(seq(':', field('label', $.identifier))),
field('value', $._value),
),
break_statement: $ => prec.right(seq('break', optional(seq(':', field('label', $.identifier))))),
continue_statement: $ => prec.right(seq('continue', optional(seq(':', field('label', $.identifier))))),
defer_statement: $ => choice(
seq('defer', repeat($._newline), field('body', $.statement)),
seq(
'errdefer',
repeat($._newline),
optional(seq(field('capture', $.error_capture), repeat($._newline))),
field('body', $.statement),
),
),
error_capture: $ => seq('|', choice($.identifier, $.sink), '|'),
labeled_block: $ => seq(
field('label', $.identifier),
':',
repeat($._newline),
$.block,
),
if_statement: $ => seq(
'if',
repeat($._newline),
field('condition', $.expression),
optional($.capture_list),
repeat($._newline),
field('consequence', $._branch_body),
optional(seq(
repeat($._newline),
'else',
repeat($._newline),
field('alternative', $._branch_body),
)),
),
capture_list: $ => seq(
'|',
commaSep1($, choice($.identifier, $.sink)),
optional(seq(':', field('guard', $.expression))),
'|',
),
while_statement: $ => seq(
'while',
repeat($._newline),
field('condition', $.expression),
optional(seq(':', repeat($._newline), field('update', choice(
$.assignment_statement,
$.expression_statement,
seq('(', repeat($._newline), choice($.assignment_statement, $.expression_statement), repeat($._newline), ')'),
)))),
repeat($._newline),
optional(prec(1, seq(field('label', $.identifier), ':', repeat($._newline)))),
field('body', $._branch_body),
),
for_statement: $ => seq(
optional('expand'),
'for',
repeat($._newline),
field('iterable', $.expression),
repeat($._newline),
$._for_capture_bar,
optional('@'),
field('item', choice($.identifier, $.sink)),
optional(seq(',', field('index', choice($.identifier, $.sink)))),
$._for_capture_bar,
repeat($._newline),
optional(seq(field('label', $.identifier), ':', repeat($._newline))),
field('body', $.block),
),
_for_capture_bar: _ => token(prec(1, '|')),
match_statement: $ => seq(
'match',
repeat($._newline),
field('subject', $.expression),
repeat($._newline),
'{',
repeat(choice($._newline, $.match_arm)),
'}',
),
match_arm: $ => choice(seq(
field('pattern', choice('else', commaSep1($, $.expression))),
optional($.match_capture),
':',
repeat($._newline),
field('body', $._branch_body),
), seq(
'expand',
$.expand_match_capture,
':',
repeat($._newline),
field('body', $._branch_body),
)),
match_capture: $ => seq('|', optional('@'), field('name', choice($.identifier, $.sink)), '|'),
expand_match_capture: $ => seq(
'|',
optional('@'),
field('value', choice($.identifier, $.sink)),
optional(seq(',', field('tag', choice($.identifier, $.sink)))),
'|',
),
_branch_body: $ => $.statement,
_value: $ => choice(
$.labeled_block,
$.block,
$.if_statement,
$.while_statement,
$.for_statement,
$.match_statement,
$.expression,
),
expression: $ => choice(
$.binary_expression,
$.catch_expression,
$.unary_expression,
$.field_expression,
$.intrinsic_call_expression,
$.call_expression,
$.index_expression,
$.slice_expression,
$.postfix_expression,
$.struct_literal,
$.anonymous_record_literal,
$.tuple_literal,
$.comptime_block,
$.function_literal,
$.struct_type,
$.array_type,
$.enum_literal,
$.array_literal,
$.parenthesized_expression,
$.identifier,
$.sink,
$.builtin_type,
$.integer,
$.float,
$.string,
$.multiline_string,
$.character,
$.boolean,
$.null,
$.unreachable,
$.undefined,
),
binary_expression: $ => choice(
prec.left(PREC.RANGE, seq(field('left', $.expression), field('operator', choice('..', '..=')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.FALLBACK, seq(field('left', $.expression), field('operator', 'orelse'), repeat($._newline), field('right', $.expression))),
prec.left(PREC.OR, seq(field('left', $.expression), 'or', repeat($._newline), field('right', $.expression))),
prec.left(PREC.AND, seq(field('left', $.expression), 'and', repeat($._newline), field('right', $.expression))),
prec.left(PREC.COMPARE, seq(field('left', $.expression), field('operator', choice('==', '!=', '<', '<=', '>', '>=')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.BITWISE, seq(field('left', $.expression), field('operator', choice('&', 'xor', '|')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.SHIFT, seq(field('left', $.expression), field('operator', choice('<<', '>>', '<<|')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.SUM, seq(field('left', $.expression), field('operator', choice('+', '-')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.PRODUCT, seq(field('left', $.expression), field('operator', choice('*', '/')), repeat($._newline), field('right', $.expression))),
),
catch_expression: $ => prec.left(PREC.FALLBACK, seq(
field('value', $.expression),
'catch',
optional(field('capture', $.error_capture)),
repeat($._newline),
field('fallback', choice(
$.block,
$.if_statement,
$.while_statement,
$.for_statement,
$.match_statement,
$.expression,
)),
)),
unary_expression: $ => prec(PREC.PREFIX, seq(
field('operator', choice('-', '&', '!', '~', '$', 'try')),
repeat($._newline),
field('operand', $.expression),
)),
field_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'.',
field('field', choice($.identifier, $.integer)),
)),
intrinsic_call_expression: $ => prec(PREC.POSTFIX, seq(
field('function', $.identifier),
field('marker', '!'),
field('arguments', $.argument_list),
)),
call_expression: $ => prec.left(PREC.POSTFIX, seq(
field('function', $.expression),
field('arguments', $.argument_list),
)),
argument_list: $ => prec(PREC.POSTFIX, seq('(', commaSep($, $.expression), ')')),
index_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'[',
repeat($._newline),
field('index', $.expression),
repeat($._newline),
']',
)),
slice_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'[',
repeat($._newline),
optional(field('start', $.expression)),
'..',
optional(field('end', $.expression)),
repeat($._newline),
']',
)),
postfix_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
field('operator', choice('?', '^')),
)),
struct_literal: $ => prec(PREC.POSTFIX, seq(
field('type', $.qualified_identifier),
optional($.argument_list),
field('fields', $.initializer_list),
)),
initializer_list: $ => seq(
'{',
choice(
repeat($._newline),
seq(
repeat($._newline),
choice($.field_initializer, $.expression),
repeat(seq(repeat($._newline), ',', repeat($._newline), choice($.field_initializer, $.expression))),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
),
),
'}',
),
field_initializer: $ => seq(
field('name', $._field_name),
optional(seq('=', repeat($._newline), field('value', $.expression))),
),
anonymous_record_literal: $ => prec(2, seq(
'{',
repeat($._newline),
$.keyed_field_initializer,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.keyed_field_initializer)),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
'}',
)),
tuple_literal: $ => prec(1, choice(
seq('{', repeat($._newline), '}'),
seq(
'{', repeat($._newline), $.expression,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.expression)),
optional(seq(repeat($._newline), ',')),
repeat($._newline), '}',
),
)),
enum_literal: $ => seq(
'.',
field('name', $.identifier),
optional($.variant_payload),
),
variant_payload: $ => seq(
'{',
choice(
repeat($._newline),
seq(
repeat($._newline),
choice(
$.expression,
seq(
$.keyed_field_initializer,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.keyed_field_initializer)),
optional(seq(repeat($._newline), ',')),
),
),
repeat($._newline),
),
),
'}',
),
keyed_field_initializer: $ => seq(
field('name', $._field_name),
'=',
repeat($._newline),
field('value', $.expression),
),
array_literal: $ => seq('[', commaSep($, $.expression), ']'),
parenthesized_expression: $ => seq(
'(',
repeat($._newline),
$.expression,
repeat($._newline),
')',
),
comptime_block: $ => seq('$', repeat($._newline), $.block),
function_literal: $ => seq(
'func',
$.parameter_list,
repeat($._newline),
field('result', $.type),
optional(seq('!', field('error', $._error_type))),
repeat($._newline),
field('body', $.block),
),
qualified_identifier: $ => prec.right(seq(
field('qualifier', $.identifier),
optional(seq('.', field('name', $.identifier))),
)),
boolean: _ => choice('true', 'false'),
null: _ => 'null',
unreachable: _ => 'unreachable',
undefined: _ => 'undefined',
sink: _ => '_',
_field_name: $ => prec(2, choice(
$.identifier,
alias(choice(
'test', 'func', 'c_func', 'struct', 'c_struct', 'opaque', 'union', 'enum',
'distinct', 'alias', 'import', 'hide', 'return', 'try', 'catch', 'mut',
'null', 'unreachable', 'undefined', 'orelse', 'and', 'or', 'xor', 'if', 'while', 'for',
'expand', 'break', 'continue', 'defer', 'errdefer', 'yield', 'match', 'else',
'true', 'false',
'void', 'noreturn', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
'isize', 'usize', 'f32', 'f64',
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
'c_int', 'c_uint', 'c_long', 'c_ulong', 'c_longlong',
'c_ulonglong', 'c_float', 'c_double', 'c_longdouble',
), $.identifier),
)),
identifier: _ => /[A-Za-z_][A-Za-z0-9_]*/,
integer: _ => /[0-9]+/,
float: _ => token(prec(1, /[0-9]+\.[0-9]+/)),
string: $ => seq(
'"',
repeat(choice($.string_content, $.escape_sequence)),
'"',
),
string_content: _ => token.immediate(prec(1, /[^"\\\n]+/)),
escape_sequence: _ => token.immediate(/\\(?:\\|"|n|r|t|0)/),
multiline_string: _ => token(/`[^\n]*(?:\n[ \t]*`[^\n]*)*/),
character: _ => token(/'(?:[^'\\\n]|\\(?:\\|'|n|r|t|0))'/),
comment: _ => token(seq('#', /[^\n]*/)),
_newline: _ => /\n/,
},
});
function commaSep($, rule) {
return choice(
repeat($._newline),
seq(
repeat($._newline),
rule,
repeat(seq(repeat($._newline), ',', repeat($._newline), rule)),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
),
);
}
function commaSep1($, rule) {
return seq(
rule,
repeat(seq(repeat($._newline), ',', repeat($._newline), rule)),
);
}
-9
View File
@@ -1,9 +0,0 @@
{
"name": "tree-sitter-brolang",
"version": "0.1.0",
"private": true,
"scripts": {
"generate": "tree-sitter generate",
"test": "tree-sitter test"
}
}
-141
View File
@@ -1,141 +0,0 @@
(comment) @comment
[
(string)
(multiline_string)
] @string
(character) @string
(escape_sequence) @escape
[
(integer)
(float)
] @number
(boolean) @constant.builtin
[
(null)
(unreachable)
(undefined)
] @constant.builtin
(builtin_type) @type.builtin
(named_type) @type
(named_type
(qualified_identifier
(identifier) @function .)
(argument_list))
(struct_literal
type: (qualified_identifier
(identifier) @function .)
(argument_list))
(type_declaration name: (identifier) @type)
(function_declaration name: (identifier) @function)
(test_declaration name: (identifier) @function)
(parameter name: (identifier) @variable.parameter)
(intrinsic_call_expression function: (identifier) @function.builtin)
(intrinsic_type function: (identifier) @function.builtin)
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
(field_expression field: (integer) @property)
(field_initializer name: (identifier) @property)
(keyed_field_initializer name: (identifier) @property)
(record_field name: (identifier) @property)
(enum_member name: (identifier) @property)
(enum_literal name: (identifier) @property)
(import_declaration alias: (identifier) @module)
(opaque_type) @keyword
[
"func"
"test"
"c_func"
"struct"
"c_struct"
"union"
"enum"
"distinct"
"alias"
"import"
"hide"
"return"
"try"
"catch"
"mut"
"orelse"
"and"
"or"
"if"
"while"
"for"
"expand"
"break"
"continue"
"defer"
"errdefer"
"yield"
"match"
"else"
] @keyword
[
"::"
"="
"+="
"-="
"*="
"/="
"&="
"|="
"xor="
"<<="
">>="
"<<|="
"=="
"!="
"<"
"<="
">"
">="
"+"
"-"
"*"
"/"
"~"
"xor"
"<<"
">>"
"<<|"
"!"
"&"
"@"
"?"
"^"
".."
"..="
"|"
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
","
"."
":"
";"
] @punctuation.delimiter
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,54 +0,0 @@
#ifndef TREE_SITTER_ALLOC_H_
#define TREE_SITTER_ALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
// Allow clients to override allocation functions
#ifdef TREE_SITTER_REUSE_ALLOCATOR
extern void *(*ts_current_malloc)(size_t size);
extern void *(*ts_current_calloc)(size_t count, size_t size);
extern void *(*ts_current_realloc)(void *ptr, size_t size);
extern void (*ts_current_free)(void *ptr);
#ifndef ts_malloc
#define ts_malloc ts_current_malloc
#endif
#ifndef ts_calloc
#define ts_calloc ts_current_calloc
#endif
#ifndef ts_realloc
#define ts_realloc ts_current_realloc
#endif
#ifndef ts_free
#define ts_free ts_current_free
#endif
#else
#ifndef ts_malloc
#define ts_malloc malloc
#endif
#ifndef ts_calloc
#define ts_calloc calloc
#endif
#ifndef ts_realloc
#define ts_realloc realloc
#endif
#ifndef ts_free
#define ts_free free
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ALLOC_H_
-347
View File
@@ -1,347 +0,0 @@
#ifndef TREE_SITTER_ARRAY_H_
#define TREE_SITTER_ARRAY_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "./alloc.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#define Array(T) \
struct { \
T *contents; \
uint32_t size; \
uint32_t capacity; \
}
/// Initialize an array.
#define array_init(self) \
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
/// Create an empty array.
#define array_new() \
{ NULL, 0, 0 }
/// Get a pointer to the element at a given `index` in the array.
#define array_get(self, _index) \
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
/// Get a pointer to the first element in the array.
#define array_front(self) array_get(self, 0)
/// Get a pointer to the last element in the array.
#define array_back(self) array_get(self, (self)->size - 1)
/// Clear the array, setting its size to zero. Note that this does not free any
/// memory allocated for the array's contents.
#define array_clear(self) ((self)->size = 0)
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
/// less than the array's current capacity, this function has no effect.
#define array_reserve(self, new_capacity) \
((self)->contents = _array__reserve( \
(void *)(self)->contents, &(self)->capacity, \
array_elem_size(self), new_capacity) \
)
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) _array__delete((self), (void *)(self)->contents, sizeof(*self))
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
do { \
(self)->contents = _array__grow( \
(void *)(self)->contents, (self)->size, &(self)->capacity, \
1, array_elem_size(self) \
); \
(self)->contents[(self)->size++] = (element); \
} while(0)
/// Increase the array's size by `count` elements.
/// New elements are zero-initialized.
#define array_grow_by(self, count) \
do { \
if ((count) == 0) break; \
(self)->contents = _array__grow( \
(self)->contents, (self)->size, &(self)->capacity, \
count, array_elem_size(self) \
); \
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
(self)->size += (count); \
} while (0)
/// Append all elements from one array to the end of another.
#define array_push_all(self, other) \
array_extend((self), (other)->size, (other)->contents)
/// Append `count` elements to the end of the array, reading their values from the
/// `contents` pointer.
#define array_extend(self, count, other_contents) \
(self)->contents = _array__splice( \
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), (self)->size, 0, count, other_contents \
)
/// Remove `old_count` elements from the array starting at the given `index`. At
/// the same index, insert `new_count` new elements, reading their values from the
/// `new_contents` pointer.
#define array_splice(self, _index, old_count, new_count, new_contents) \
(self)->contents = _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, old_count, new_count, new_contents \
)
/// Insert one `element` into the array at the given `index`.
#define array_insert(self, _index, element) \
(self)->contents = _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, 0, 1, &(element) \
)
/// Remove one element from the array at the given `index`.
#define array_erase(self, _index) \
_array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index)
/// Pop the last element off the array, returning the element by value.
#define array_pop(self) ((self)->contents[--(self)->size])
/// Assign the contents of one array to another, reallocating if necessary.
#define array_assign(self, other) \
(self)->contents = _array__assign( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
)
/// Swap one array with another
#define array_swap(self, other) \
do { \
struct Swap swapped_contents = _array__swap( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(void *)(other)->contents, &(other)->size, &(other)->capacity \
); \
(self)->contents = swapped_contents.self_contents; \
(other)->contents = swapped_contents.other_contents; \
} while (0)
/// Get the size of the array contents
#define array_elem_size(self) (sizeof *(self)->contents)
/// Search a sorted array for a given `needle` value, using the given `compare`
/// callback to determine the order.
///
/// If an existing element is found to be equal to `needle`, then the `index`
/// out-parameter is set to the existing value's index, and the `exists`
/// out-parameter is set to true. Otherwise, `index` is set to an index where
/// `needle` should be inserted in order to preserve the sorting, and `exists`
/// is set to false.
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
/// Search a sorted array for a given `needle` value, using integer comparisons
/// of a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_with`.
#define array_search_sorted_by(self, field, needle, _index, _exists) \
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
/// Insert a given `value` into a sorted array, using the given `compare`
/// callback to determine the order.
#define array_insert_sorted_with(self, compare, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
/// Insert a given `value` into a sorted array, using integer comparisons of
/// a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_by`.
#define array_insert_sorted_by(self, field, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
// Private
// Pointers to individual `Array` fields (rather than the entire `Array` itself)
// are passed to the various `_array__*` functions below to address strict aliasing
// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`.
//
// The `Array` type itself was not altered as a solution in order to avoid breakage
// with existing consumers (in particular, parsers with external scanners).
/// This is not what you're looking for, see `array_delete`.
static inline void _array__delete(void *self, void *contents, size_t self_size) {
if (contents) ts_free(contents);
if (self) memset(self, 0, self_size);
}
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(void* self_contents, uint32_t *size,
size_t element_size, uint32_t index) {
assert(index < *size);
char *contents = (char *)self_contents;
memmove(contents + index * element_size, contents + (index + 1) * element_size,
(*size - index - 1) * element_size);
(*size)--;
}
/// This is not what you're looking for, see `array_reserve`.
static inline void *_array__reserve(void *contents, uint32_t *capacity,
size_t element_size, uint32_t new_capacity) {
void *new_contents = contents;
if (new_capacity > *capacity) {
if (contents) {
new_contents = ts_realloc(contents, new_capacity * element_size);
} else {
new_contents = ts_malloc(new_capacity * element_size);
}
*capacity = new_capacity;
}
return new_contents;
}
/// This is not what you're looking for, see `array_assign`.
static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity,
const void *other_contents, uint32_t other_size, size_t element_size) {
void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size);
*self_size = other_size;
memcpy(new_contents, other_contents, *self_size * element_size);
return new_contents;
}
struct Swap {
void *self_contents;
void *other_contents;
};
/// This is not what you're looking for, see `array_swap`.
// static inline void _array__swap(Array *self, Array *other) {
static inline struct Swap _array__swap(void *self_contents, uint32_t *self_size, uint32_t *self_capacity,
void *other_contents, uint32_t *other_size, uint32_t *other_capacity) {
void *new_self_contents = other_contents;
uint32_t new_self_size = *other_size;
uint32_t new_self_capacity = *other_capacity;
void *new_other_contents = self_contents;
*other_size = *self_size;
*other_capacity = *self_capacity;
*self_size = new_self_size;
*self_capacity = new_self_capacity;
struct Swap out = {
.self_contents = new_self_contents,
.other_contents = new_other_contents,
};
return out;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity,
uint32_t count, size_t element_size) {
void *new_contents = contents;
uint32_t new_size = size + count;
if (new_size > *capacity) {
uint32_t new_capacity = *capacity * 2;
if (new_capacity < 8) new_capacity = 8;
if (new_capacity < new_size) new_capacity = new_size;
new_contents = _array__reserve(contents, capacity, element_size, new_capacity);
}
return new_contents;
}
/// This is not what you're looking for, see `array_splice`.
static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity,
size_t element_size,
uint32_t index, uint32_t old_count,
uint32_t new_count, const void *elements) {
uint32_t new_size = *size + new_count - old_count;
uint32_t old_end = index + old_count;
uint32_t new_end = index + new_count;
assert(old_end <= *size);
void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size);
char *contents = (char *)new_contents;
if (*size > old_end) {
memmove(
contents + new_end * element_size,
contents + old_end * element_size,
(*size - old_end) * element_size
);
}
if (new_count > 0) {
if (elements) {
memcpy(
(contents + index * element_size),
elements,
new_count * element_size
);
} else {
memset(
(contents + index * element_size),
0,
new_count * element_size
);
}
}
*size += new_count - old_count;
return new_contents;
}
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
do { \
*(_index) = start; \
*(_exists) = false; \
uint32_t size = (self)->size - *(_index); \
if (size == 0) break; \
int comparison; \
while (size > 1) { \
uint32_t half_size = size / 2; \
uint32_t mid_index = *(_index) + half_size; \
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
if (comparison <= 0) *(_index) = mid_index; \
size -= half_size; \
} \
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
if (comparison == 0) *(_exists) = true; \
else if (comparison < 0) *(_index) += 1; \
} while (0)
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
/// parameter by reference in order to work with the generic sorting function above.
#define _compare_int(a, b) ((int)*(a) - (int)(b))
#ifdef _MSC_VER
#pragma warning(pop)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ARRAY_H_
@@ -1,286 +0,0 @@
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
typedef struct TSLanguageMetadata {
uint8_t major_version;
uint8_t minor_version;
uint8_t patch_version;
} TSLanguageMetadata;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
// Used to index the field and supertype maps.
typedef struct {
uint16_t index;
uint16_t length;
} TSMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
void (*log)(const TSLexer *, const char *, ...);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
uint16_t reserved_word_set_id;
} TSLexerMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
typedef struct {
int32_t start;
int32_t end;
} TSCharacterRange;
struct TSLanguage {
uint32_t abi_version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexerMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
const char *name;
const TSSymbol *reserved_words;
uint16_t max_reserved_word_set_size;
uint32_t supertype_count;
const TSSymbol *supertype_symbols;
const TSMapSlice *supertype_map_slices;
const TSSymbol *supertype_map_entries;
TSLanguageMetadata metadata;
};
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
uint32_t index = 0;
uint32_t size = len - index;
while (size > 1) {
uint32_t half_size = size / 2;
uint32_t mid_index = index + half_size;
const TSCharacterRange *range = &ranges[mid_index];
if (lookahead >= range->start && lookahead <= range->end) {
return true;
} else if (lookahead > range->end) {
index = mid_index;
}
size -= half_size;
}
const TSCharacterRange *range = &ranges[index];
return (lookahead >= range->start && lookahead <= range->end);
}
/*
* Lexer Macros
*/
#ifdef _MSC_VER
#define UNUSED __pragma(warning(suppress : 4101))
#else
#define UNUSED __attribute__((unused))
#endif
#define START_LEXER() \
bool result = false; \
bool skip = false; \
UNUSED \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define ADVANCE_MAP(...) \
{ \
static const uint16_t map[] = { __VA_ARGS__ }; \
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
if (map[i] == lookahead) { \
state = map[i + 1]; \
goto next_state; \
} \
} \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value) \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value), \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_name, children, precedence, prod_id) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_name, \
.child_count = children, \
.dynamic_precedence = precedence, \
.production_id = prod_id \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_
-110
View File
@@ -1,110 +0,0 @@
==================
Catch value sources
==================
recover func() void {
a :: fail() catch |err| handle(err)
b :: fail() catch |err| match err {
.bad: 1
else: 2
}
c :: fail() catch { yield 3 }
d :: fail() catch if true { yield 4 } else { yield 5 }
}
---
(source_file
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(constant_declaration
(identifier)
(expression
(catch_expression
(expression
(call_expression
(expression
(identifier))
(argument_list)))
(error_capture
(identifier))
(expression
(call_expression
(expression
(identifier))
(argument_list
(expression
(identifier)))))))))
(statement
(constant_declaration
(identifier)
(expression
(catch_expression
(expression
(call_expression
(expression
(identifier))
(argument_list)))
(error_capture
(identifier))
(match_statement
(expression
(identifier))
(match_arm
(expression
(enum_literal
(identifier)))
(statement
(expression_statement
(expression
(integer)))))
(match_arm
(statement
(expression_statement
(expression
(integer))))))))))
(statement
(constant_declaration
(identifier)
(expression
(catch_expression
(expression
(call_expression
(expression
(identifier))
(argument_list)))
(block
(statement
(yield_statement
(expression
(integer)))))))))
(statement
(constant_declaration
(identifier)
(expression
(catch_expression
(expression
(call_expression
(expression
(identifier))
(argument_list)))
(if_statement
(expression
(boolean))
(statement
(block
(statement
(yield_statement
(expression
(integer))))))
(statement
(block
(statement
(yield_statement
(expression
(integer))))))))))))))
-842
View File
@@ -1,842 +0,0 @@
==================
Core syntax
==================
io :: import "@std/io"
hide private_value :: 1
hide private_counter i32 = 0
hide Private :: opaque
hide private_sum func(a, b i32) i32 {
return a + b
}
Status :: enum {
ok
bad
}
Pair :: struct {
left i32
right i32
}
sum func(a, b i32) i32 ! Status {
result :: a + b
if result > 0 {
return result
} else {
return .bad
}
}
---
(source_file
(import_declaration
(identifier)
(string
(string_content)))
(global_constant_declaration
(identifier)
(expression
(integer)))
(global_variable_declaration
(identifier)
(type
(builtin_type))
(expression
(integer)))
(type_declaration
(identifier)
(opaque_type))
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(identifier)
(type
(builtin_type))))
(type
(builtin_type))
(block
(statement
(return_statement
(expression
(binary_expression
(expression
(identifier))
(expression
(identifier))))))))
(type_declaration
(identifier)
(enum_type
(enum_body
(enum_member
(identifier))
(enum_member
(identifier)))))
(type_declaration
(identifier)
(struct_type
(record_body
(record_field
(identifier)
(type
(builtin_type)))
(record_field
(identifier)
(type
(builtin_type))))))
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(identifier)
(type
(builtin_type))))
(type
(builtin_type))
(type
(named_type
(qualified_identifier
(identifier))))
(block
(statement
(constant_declaration
(identifier)
(expression
(binary_expression
(expression
(identifier))
(expression
(identifier))))))
(statement
(if_statement
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))
(statement
(block
(statement
(return_statement
(expression
(identifier))))))
(statement
(block
(statement
(return_statement
(expression
(enum_literal
(identifier))))))))))))
==================
Anonymous keyed records
==================
inferred :: {
x = 1,
int = "bro",
}
empty :: {}
pair :: {1, 2}
---
(source_file
(global_constant_declaration
(identifier)
(expression
(anonymous_record_literal
(keyed_field_initializer
(identifier)
(expression
(integer)))
(keyed_field_initializer
(identifier)
(expression
(string
(string_content)))))))
(global_constant_declaration
(identifier)
(expression
(tuple_literal)))
(global_constant_declaration
(identifier)
(expression
(tuple_literal
(expression
(integer))
(expression
(integer))))))
==================
Intrinsic calls
==================
main func() void {
_ = sizeof ! (i32)
}
---
(source_file
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(assignment_statement
(expression
(sink))
(expression
(intrinsic_call_expression
(identifier)
(argument_list
(expression
(builtin_type))))))))))
==================
Intrinsic type construction
==================
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {null})
---
(source_file
(type_declaration
(identifier)
(alias_type
(type
(intrinsic_type
(identifier)
(argument_list
(expression
(enum_literal
(identifier)))
(expression
(tuple_literal
(expression
(string
(string_content)))))
(expression
(tuple_literal
(expression
(builtin_type))))
(expression
(tuple_literal
(expression
(null))))))))))
==================
Errdefer
==================
Failure :: enum { bad }
work func() i32 ! Failure {
errdefer cleanup()
errdefer |err| {
_ = err
}
return 1
}
---
(source_file
(type_declaration
(identifier)
(enum_type
(enum_body
(enum_member
(identifier)))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(type
(named_type
(qualified_identifier
(identifier))))
(block
(statement
(defer_statement
(statement
(expression_statement
(expression
(call_expression
(expression
(identifier))
(argument_list)))))))
(statement
(defer_statement
(error_capture
(identifier))
(statement
(block
(statement
(assignment_statement
(expression
(sink))
(expression
(identifier))))))))
(statement
(return_statement
(expression
(integer)))))))
==================
Bare return and value yield
==================
done func() void {
return
}
inline func() void { return }
choose func() i32 {
result :: { yield 1 }
return result
}
---
(source_file
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(return_statement))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(return_statement))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(constant_declaration
(identifier)
(block
(statement
(yield_statement
(expression
(integer)))))))
(statement
(return_statement
(expression
(identifier)))))))
==================
Tuples and expand for
==================
Pair :: struct { i32, []u8 }
main func() void {
pair Pair = Pair {42, "bro"}
singleton :: {1,}
empty :: {}
_ = pair.0
expand for singleton |value| {
_ = value
}
}
---
(source_file
(type_declaration
(identifier)
(struct_type
(record_body
(record_field
(type
(builtin_type)))
(record_field
(type
(array_type
(type
(builtin_type))))))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(variable_declaration
(identifier)
(type
(named_type
(qualified_identifier
(identifier))))
(expression
(struct_literal
(qualified_identifier
(identifier))
(initializer_list
(expression
(integer))
(expression
(string
(string_content))))))))
(statement
(constant_declaration
(identifier)
(expression
(tuple_literal
(expression
(integer))))))
(statement
(constant_declaration
(identifier)
(expression
(tuple_literal))))
(statement
(assignment_statement
(expression
(sink))
(expression
(field_expression
(expression
(identifier))
(integer)))))
(statement
(for_statement
(expression
(identifier))
(identifier)
(block
(statement
(assignment_statement
(expression
(sink))
(expression
(identifier))))))))))
==================
Native tests
==================
math :: import "../math"
test import "../math"
addition test {}
---
(source_file
(import_declaration
(identifier)
(string
(string_content)))
(test_import_declaration
(string
(string_content)))
(test_declaration
(identifier)
(block)))
==================
Expanded match arms
==================
Kind :: enum { one, two }
Value :: union(enum) { number i32, empty void }
visit func(kind Kind, value Value) void {
match kind {
.one: {}
expand |tag|: _ = tag
}
match value {
expand |@payload, tag|: {
_ = payload
_ = tag
}
}
}
---
(source_file
(type_declaration
(identifier)
(enum_type
(enum_body
(enum_member
(identifier))
(enum_member
(identifier)))))
(type_declaration
(identifier)
(union_type
(record_body
(record_field
(identifier)
(type
(builtin_type)))
(record_field
(identifier)
(type
(builtin_type))))))
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(type
(named_type
(qualified_identifier
(identifier)))))
(parameter
(identifier)
(type
(named_type
(qualified_identifier
(identifier))))))
(type
(builtin_type))
(block
(statement
(match_statement
(expression
(identifier))
(match_arm
(expression
(enum_literal
(identifier)))
(statement
(expression_statement
(expression
(tuple_literal)))))
(match_arm
(expand_match_capture
(identifier))
(statement
(assignment_statement
(expression
(sink))
(expression
(identifier)))))))
(statement
(match_statement
(expression
(identifier))
(match_arm
(expand_match_capture
(identifier)
(identifier))
(statement
(block
(statement
(assignment_statement
(expression
(sink))
(expression
(identifier))))
(statement
(assignment_statement
(expression
(sink))
(expression
(identifier))))))))))))
==================
Unsigned constraint
==================
value uint :: 255
---
(source_file
(global_constant_declaration
(identifier)
(type
(builtin_type))
(expression
(integer))))
==================
Bitwise operations
==================
ops func(value u8, count u8) u8 {
value &= ~u8(1)
value |= 2
value xor= 3
value <<= count
value >>= count
value <<|= count
if (value | 1) |captured| { return captured }
return (value & 15) xor (value << 1) | (value >> 1) | (value <<| 8)
}
---
(source_file
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(type
(builtin_type)))
(parameter
(identifier)
(type
(builtin_type))))
(type
(builtin_type))
(block
(statement
(assignment_statement
(expression
(identifier))
(expression
(unary_expression
(expression
(call_expression
(expression
(builtin_type))
(argument_list
(expression
(integer)))))))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(integer))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(integer))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(identifier))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(identifier))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(identifier))))
(statement
(if_statement
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))
(capture_list
(identifier))
(statement
(block
(statement
(return_statement
(expression
(identifier))))))))
(statement
(return_statement
(expression
(binary_expression
(expression
(binary_expression
(expression
(binary_expression
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))))
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))))
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer)))))))))))))
==================
Struct field defaults and grouped types
==================
Config :: struct {
capacity usize = 8
}
work func() void ! (Config | errors.Full) {}
clear func($K, $V type) void {
for 0..entries.len |i| {
entries[i].hash = 0
}
}
---
(source_file
(type_declaration
(identifier)
(struct_type
(record_body
(record_field
(identifier)
(type
(builtin_type))
(expression
(integer))))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(type
(parenthesized_type
(type
(named_type
(qualified_identifier
(identifier)))
(named_type
(qualified_identifier
(identifier)
(identifier))))))
(block))
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(identifier)
(type
(builtin_type))))
(type
(builtin_type))
(block
(statement
(for_statement
(expression
(binary_expression
(expression
(integer))
(expression
(field_expression
(expression
(identifier))
(identifier)))))
(identifier)
(block
(statement
(assignment_statement
(expression
(field_expression
(expression
(index_expression
(expression
(identifier))
(expression
(identifier))))
(identifier)))
(expression
(integer))))))))))
==================
Sink for captures and braceless while
==================
main func() void {
for [1] |_, _| {}
while (true) _ = 1
}
---
(source_file
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(for_statement
(expression
(array_literal
(expression
(integer))))
(sink)
(sink)
(block)))
(statement
(while_statement
(expression
(parenthesized_expression
(expression
(boolean))))
(statement
(assignment_statement
(expression
(sink))
(expression
(integer)))))))))
@@ -1,14 +0,0 @@
value u8 = 1
value xor= 2
# ^^^^ operator
value <<|= 3
# ^^^^ operator
masked :: ~value & 15
# ^ operator
# ^ operator
shifted :: value <<| 8
# ^^^ operator
@@ -1,16 +0,0 @@
hide helper func() void {}
# <- keyword
value uint :: 1
# ^^^^ type.builtin
main func() void {
_ = sizeof!(i32)
# ^^^^^^ function.builtin
# ^ operator
_ = sizeof(i32)
# ^^^^^^ function
_ = constcast!(memory)
# ^^^^^^^^^ function.builtin
# ^ operator
}
@@ -1,30 +0,0 @@
Config :: struct {
# ^^^^^^ type
capacity usize = 8
# ^^^^^^^^ property
# ^ operator
}
work func() void ! (Config | errors.Full) {}
# ^^^^ keyword
# ^ operator
# ^^^^^^ type
# ^ operator
clear func($K, $V type) void {
# ^ variable.parameter
# ^ variable.parameter
# ^^^^ type.builtin
for 0..entries.len |i| {}
# ^^^ keyword
# ^ operator
}
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {null})
# ^^^^^^^^^^^^ function.builtin
fail func() noreturn {
# ^^^^^^^^ type.builtin
unreachable
# ^^^^^^^^^^^ constant.builtin
}
-27
View File
@@ -1,27 +0,0 @@
{
"grammars": [
{
"name": "brolang",
"camelcase": "Brolang",
"scope": "source.bro",
"path": ".",
"file-types": ["bro", "hon"],
"highlights": "queries/highlights.scm"
}
],
"metadata": {
"version": "0.1.0",
"description": "Tree-sitter grammar for Brolang",
"authors": [{"name": "Brolang contributors"}]
},
"bindings": {
"c": false,
"go": false,
"java": false,
"node": false,
"python": false,
"rust": false,
"swift": false,
"zig": false
}
}