rename intrinsics
This commit is contained in:
+26
-22
@@ -25,11 +25,11 @@ 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, and compile-time folding for numeric constant expressions
|
||||
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
|
||||
- compile-time `min_value(T)` and `max_value(T)` bounds for concrete native and C integer scalar types
|
||||
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
|
||||
- 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
|
||||
- `ptr_cast(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
|
||||
- `ptrcast!(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
|
||||
- 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 `none`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
|
||||
@@ -98,47 +98,51 @@ fields. `_` is not a keyword member name.
|
||||
|
||||
#### division
|
||||
|
||||
Compiler intrinsics use direct unqualified `name!(...)` syntax. The `!` marks the call as an
|
||||
intrinsic; it is not part of the identifier. Bare and qualified calls without `!` resolve as
|
||||
ordinary user functions, while qualified bang calls are rejected.
|
||||
|
||||
`/` and `/=` accept only floating-point operands. Integer division must state its rounding and
|
||||
remainder convention with one of these unqualified builtins:
|
||||
remainder convention with one of these intrinsics:
|
||||
|
||||
| Builtin | Result |
|
||||
| --- | --- |
|
||||
| `div_trunc(a, b)` | quotient rounded toward zero |
|
||||
| `div_floor(a, b)` | quotient rounded toward negative infinity |
|
||||
| `div_exact(a, b)` | truncated quotient; traps unless it divides exactly |
|
||||
| `div_ceil(a, b)` | quotient rounded toward positive infinity |
|
||||
| `rem(a, b)` | remainder paired with `div_trunc`; sign follows `a` |
|
||||
| `mod(a, b)` | modulus paired with `div_floor`; sign follows `b` |
|
||||
| `divtrunc!(a, b)` | quotient rounded toward zero |
|
||||
| `divfloor!(a, b)` | quotient rounded toward negative infinity |
|
||||
| `divexact!(a, b)` | truncated quotient; traps unless it divides exactly |
|
||||
| `divceil!(a, b)` | quotient rounded toward positive infinity |
|
||||
| `rem!(a, b)` | remainder paired with `divtrunc!`; sign follows `a` |
|
||||
| `mod!(a, b)` | modulus paired with `divfloor!`; sign follows `b` |
|
||||
|
||||
The operands may be compatible concrete integer or float scalars. Existing literal coercion and
|
||||
numeric widening rules apply, the result has the common operand type, and float quotients are
|
||||
integral-valued floats. These identities hold when representable:
|
||||
|
||||
```bro
|
||||
div_trunc(a, b) * b + rem(a, b) == a
|
||||
div_floor(a, b) * b + mod(a, b) == a
|
||||
divtrunc!(a, b) * b + rem!(a, b) == a
|
||||
divfloor!(a, b) * b + mod!(a, b) == a
|
||||
```
|
||||
|
||||
Negative operands distinguish the operations:
|
||||
|
||||
```bro
|
||||
div_trunc(-5, 3) == -1
|
||||
div_floor(-5, 3) == -2
|
||||
div_ceil(-5, 3) == -1
|
||||
rem(-5, 3) == -2
|
||||
mod(-5, 3) == 1
|
||||
mod(5, -3) == -1
|
||||
divtrunc!(-5, 3) == -1
|
||||
divfloor!(-5, 3) == -2
|
||||
divceil!(-5, 3) == -1
|
||||
rem!(-5, 3) == -2
|
||||
mod!(-5, 3) == 1
|
||||
mod!(5, -3) == -1
|
||||
```
|
||||
|
||||
All six builtins diagnose a zero denominator at comptime and trap at runtime, including float
|
||||
zero. Quotient operations also trap for signed `min_value(T), -1`; `rem` and `mod` return zero for
|
||||
that pair. `div_exact` traps when `div_trunc(a, b) * b == a` is false in the operand type, so float
|
||||
zero. Quotient operations also trap for signed `minval!(T), -1`; `rem!` and `mod!` return zero for
|
||||
that pair. `divexact!` traps when `divtrunc!(a, b) * b == a` is false in the operand type, so float
|
||||
exactness follows floating-point equality. Other float NaN and infinity behavior follows the
|
||||
underlying IEEE operations. Ordinary float `/` remains unchecked and therefore preserves IEEE
|
||||
infinity/NaN behavior.
|
||||
|
||||
The six spellings are reserved only as direct unqualified calls. A qualified call such as
|
||||
`math.div_floor(a, b)` resolves to an ordinary package function.
|
||||
Only the six bang calls are intrinsic. Bare calls such as `divfloor(a, b)` and qualified calls such
|
||||
as `math.divfloor(a, b)` resolve to ordinary functions.
|
||||
|
||||
### functions, C interop, and linking
|
||||
|
||||
@@ -188,7 +192,7 @@ The six spellings are reserved only as direct unqualified calls. A qualified cal
|
||||
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
|
||||
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
|
||||
- recursive type factories, type reflection, and type-producing unions/enums
|
||||
- broader Zig-style pointer/result casts beyond V1 `ptr_cast(T, ptr)`
|
||||
- broader Zig-style pointer/result casts beyond V1 `ptrcast!(T, ptr)`
|
||||
- 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
|
||||
|
||||
@@ -183,7 +183,7 @@ Current prototype features:
|
||||
- `#` comments
|
||||
- Immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
|
||||
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
|
||||
- Target-dependent atomic `c_*` primitive types, `c_func`, complete `c_struct`, `opaque`, `anyopaque`, and V1 `ptr_cast(T, ptr)`
|
||||
- Target-dependent atomic `c_*` primitive types, `c_func`, complete `c_struct`, `opaque`, `anyopaque`, and V1 `ptrcast!(T, ptr)`
|
||||
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
|
||||
- String literals as immutable pointers to static zero-terminated byte arrays
|
||||
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
|
||||
@@ -203,7 +203,7 @@ Current prototype features:
|
||||
- Bodyless manual and imported C variadic declarations with default argument promotions
|
||||
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||
- Checked signed addition and unary negation
|
||||
- Float-only `/` plus explicit `div_trunc`, `div_floor`, `div_exact`, `div_ceil`, `rem`, and `mod` scalar builtins
|
||||
- Float-only `/` plus explicit `divtrunc!`, `divfloor!`, `divexact!`, `divceil!`, `rem!`, and `mod!` scalar intrinsics
|
||||
- Static, eager runtime, mutable runtime, and deferred problematic globals
|
||||
- Runtime diagnostics followed by `llvm.trap`
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
- `Name :: opaque` is the incomplete nominal record spelling; bodyless `c_struct` is invalid
|
||||
- `anyopaque` is the erased object type used behind pointers for C `void*` and allocator contexts
|
||||
- C `void` function results remain `void`; C `void*` / `const void*` import and render as `?*mut anyopaque` / `?*anyopaque`
|
||||
- `ptr_cast(T, ptr)` preserves pointer shape and only changes the child type in v1
|
||||
- `ptrcast!(T, ptr)` preserves pointer shape and only changes the child type in v1
|
||||
- future direction: generalize toward Zig-style arbitrary pointer-result casts once casts have a broader result-type story
|
||||
|
||||
12. `undefined` as inspired by zig (implemented):
|
||||
@@ -464,7 +464,7 @@
|
||||
active/field `integer`) is sufficient. **no HIR/IR/lowering change** (like 18/19/20); the delta
|
||||
is parser + types layout + one checker validation + three LLVM emit sites
|
||||
- runtime layout `{tag, payload-carrier}`: tag at offset 0, payload carrier at
|
||||
`payload_offset = round_up(sizeof(tag), payload_align)` (`types.union_payload_offset`, shared by
|
||||
`payload_offset = round_up(sizeof!(tag), payload_align)` (`types.union_payload_offset`, shared by
|
||||
`size` and the emitter). field access uses byte-offset GEPs, so offsets stay self-consistent
|
||||
- construction `T{ variant = value }` reuses the union-literal path and additionally stores the
|
||||
derived tag; payload read `x.variant` reuses field access, reading at the payload offset
|
||||
@@ -794,19 +794,19 @@
|
||||
32. explicit division family (implemented)
|
||||
- `/` and `/=` are float-only; every integer use is rejected with guidance toward explicit
|
||||
division, including literals, comptime execution, array counts, and compound assignment
|
||||
- direct unqualified calls reserve `div_trunc`, `div_floor`, `div_exact`, `div_ceil`, `rem`, and
|
||||
`mod`; qualified names remain ordinary package functions
|
||||
- direct bang calls use `divtrunc!`, `divfloor!`, `divexact!`, `divceil!`, `rem!`, and `mod!`;
|
||||
bare and qualified names remain ordinary functions
|
||||
- the builtins accept compatible concrete integer or float scalars, reuse existing literal and
|
||||
widening rules, and return the common operand type (integral-valued floats for quotients)
|
||||
- all builtins diagnose zero denominators at comptime and trap at runtime; quotient operations
|
||||
also trap on signed `min_value(T) / -1`, while `rem` and `mod` return zero for that pair
|
||||
- `div_exact` checks the reconstructed dividend in the operand type; `rem` pairs with truncation
|
||||
and follows the numerator sign, while `mod` pairs with floor and follows the denominator sign
|
||||
also trap on signed `minval!(T) / -1`, while `rem!` and `mod!` return zero for that pair
|
||||
- `divexact!` checks the reconstructed dividend in the operand type; `rem!` pairs with truncation
|
||||
and follows the numerator sign, while `mod!` pairs with floor and follows the denominator sign
|
||||
- HIR/IR use compact semantic enum tags; integer floor, ceil, and exact lowering reconstructs the
|
||||
remainder from one quotient so each produces only one hardware-division candidate
|
||||
- float lowering uses the typed LLVM trunc/floor/ceil intrinsics, `frem`, and ordered equality;
|
||||
ordinary float `/` remains the unchecked IEEE infinity/NaN escape hatch
|
||||
- migrated `std/mem`, `std/arraylist`, and the compound-assignment example to `div_trunc`
|
||||
- migrated `std/mem`, `std/arraylist`, and the compound-assignment example to `divtrunc!`
|
||||
|
||||
33. explicit I/O provider (implemented)
|
||||
- `main` may take one canonical `@std/io Io`; parameterless entry points remain valid
|
||||
@@ -834,7 +834,8 @@
|
||||
- add `debug.print` function making use of `std/io` to print values to the console
|
||||
- this may either require native variadic arguments or a tuple value to like zig's approach (consider pros and cons)
|
||||
|
||||
38. consider renaming `ptr_cast` to `ptrcast`
|
||||
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
|
||||
user functions (implemented)
|
||||
|
||||
## A word on unchecked casts
|
||||
|
||||
@@ -844,7 +845,7 @@ For casts that bypass safety checks, Honey provides builtin functions:
|
||||
| -- | -- | -- |
|
||||
| `truncate(x, T)` | Keep low bits, discard rest | Never |
|
||||
| `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) |
|
||||
| `ptrcast(p, T)` | Change pointer type | Gaining mutability (compile error) |
|
||||
| `ptrcast!(p, T)` | Change pointer type | Gaining mutability (compile error) |
|
||||
|
||||
```honey
|
||||
# truncation
|
||||
@@ -859,10 +860,10 @@ bits := bitcast(f, u32) # IEEE 754 representation
|
||||
|
||||
# pointer casts (element type, many ↔ single, pointer ↔ usize)
|
||||
buf: *u8 = get_buffer()
|
||||
ints := ptrcast(buf, *u32) # element type change
|
||||
single := ptrcast(buf, @u8) # many → single (restricting)
|
||||
addr := ptrcast(buf, usize) # pointer to integer
|
||||
ptr := ptrcast(addr, @u8) # integer to pointer
|
||||
ints := ptrcast!(buf, *u32) # element type change
|
||||
single := ptrcast!(buf, @u8) # many → single (restricting)
|
||||
addr := ptrcast!(buf, usize) # pointer to integer
|
||||
ptr := ptrcast!(addr, @u8) # integer to pointer
|
||||
```
|
||||
|
||||
## A word on multi-unwrap
|
||||
|
||||
@@ -120,6 +120,7 @@ Expr :: struct {
|
||||
body: []Stmt_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
parenthesized: bool,
|
||||
intrinsic: bool,
|
||||
kind: Expr_Kind,
|
||||
}
|
||||
|
||||
|
||||
@@ -401,10 +401,11 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string {
|
||||
return strings.to_string(builder)
|
||||
}
|
||||
|
||||
is_ptr_cast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
|
||||
return expr.left == ast.INVALID_EXPR &&
|
||||
is_ptrcast_call :: proc(checker: ^Checker, expr: ast.Expr) -> bool {
|
||||
return expr.intrinsic &&
|
||||
expr.left == ast.INVALID_EXPR &&
|
||||
!symbol.is_valid(expr.qualifier) &&
|
||||
symbol_text(checker, expr.name) == "ptr_cast"
|
||||
symbol_text(checker, expr.name) == "ptrcast"
|
||||
}
|
||||
|
||||
Type_Builtin :: enum u8 {
|
||||
@@ -426,14 +427,14 @@ Division_Builtin :: enum u8 {
|
||||
}
|
||||
|
||||
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
|
||||
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
}
|
||||
switch symbol_text(checker, expr.name) {
|
||||
case "div_trunc": return .Trunc
|
||||
case "div_floor": return .Floor
|
||||
case "div_exact": return .Exact
|
||||
case "div_ceil": return .Ceil
|
||||
case "divtrunc": return .Trunc
|
||||
case "divfloor": return .Floor
|
||||
case "divexact": return .Exact
|
||||
case "divceil": return .Ceil
|
||||
case "rem": return .Rem
|
||||
case "mod": return .Mod
|
||||
}
|
||||
@@ -441,26 +442,26 @@ division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Bui
|
||||
}
|
||||
|
||||
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
|
||||
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
}
|
||||
name := symbol_text(checker, expr.name)
|
||||
if name == "size_of" {
|
||||
if name == "sizeof" {
|
||||
return .Size_Of
|
||||
}
|
||||
if name == "align_of" {
|
||||
if name == "alignof" {
|
||||
return .Align_Of
|
||||
}
|
||||
if name == "min_value" {
|
||||
if name == "minval" {
|
||||
return .Min_Value
|
||||
}
|
||||
if name == "max_value" {
|
||||
if name == "maxval" {
|
||||
return .Max_Value
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
valid_ptr_cast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
valid_ptrcast_child :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||
return types.is_valid(value) &&
|
||||
!types.is_void(value) &&
|
||||
!types.is_anyopaque(value) &&
|
||||
@@ -501,7 +502,7 @@ build_type_builtin :: proc(
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "%s expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
id := source.addf(checker.diagnostics, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
||||
}
|
||||
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
@@ -640,7 +641,7 @@ type_from_syntax :: proc(
|
||||
}
|
||||
} else {
|
||||
if constant.kind == .Integer_Division {
|
||||
source.add(checker.diagnostics, span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
|
||||
source.add(checker.diagnostics, span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!")
|
||||
return types.INVALID
|
||||
}
|
||||
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
|
||||
@@ -3365,7 +3366,7 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptr_cast_call(checker, expr) {
|
||||
if is_ptrcast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
@@ -3374,13 +3375,18 @@ infer_expr :: proc(
|
||||
child, child_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
operand := infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
||||
result := types.INVALID
|
||||
if child_ok && valid_ptr_cast_child(checker, child) {
|
||||
if child_ok && valid_ptrcast_child(checker, child) {
|
||||
result, _ = types.replace_pointer_child(&checker.module.types, operand, child)
|
||||
}
|
||||
last = result
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if expr.intrinsic {
|
||||
last = types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if callee_type, handled := infer_qualified_value_field_type(checker, expr, locals, pkg, file); handled {
|
||||
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
||||
if !ok {
|
||||
@@ -4957,7 +4963,7 @@ build_constant_expr :: proc(
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||
}
|
||||
if constant.kind == .Integer_Division {
|
||||
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
|
||||
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!")
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||
}
|
||||
if constant.kind == .Overflow ||
|
||||
@@ -5447,7 +5453,7 @@ build_division_builtin :: proc(
|
||||
) -> hir.Expr_Id {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(
|
||||
checker.diagnostics, expr.span, "%s expects 2 arguments, got %d",
|
||||
checker.diagnostics, expr.span, "%s! expects 2 arguments, got %d",
|
||||
symbol_text(checker, expr.name), len(expr.args),
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
@@ -6223,7 +6229,7 @@ build_binary_arith :: proc(
|
||||
if op == .Div && !types.is_float(result, checker.target) {
|
||||
id := source.add(
|
||||
checker.diagnostics, span,
|
||||
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
|
||||
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
|
||||
)
|
||||
return invalid_hir_expr(checker, span, id, result)
|
||||
}
|
||||
@@ -6463,22 +6469,22 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptr_cast_call(checker, expr) {
|
||||
if is_ptrcast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptr_cast expects 2 arguments, got %d", len(expr.args))
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptrcast! expects 2 arguments, got %d", len(expr.args))
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
target, target_ok := resolve_type_argument(checker, expr.args[0], pkg, file)
|
||||
if !target_ok {
|
||||
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptr_cast target must be a type")
|
||||
id := source.add(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a type")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if !valid_ptr_cast_child(checker, target) {
|
||||
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptr_cast target must be a sized runtime object type, got %s", type_label(checker, target))
|
||||
if !valid_ptrcast_child(checker, target) {
|
||||
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "ptrcast! target must be a sized runtime object type, got %s", type_label(checker, target))
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
@@ -6488,6 +6494,17 @@ build_expr :: proc(
|
||||
append(&stack, Build_Expr_Frame{expr=expr.args[1], expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||
continue
|
||||
}
|
||||
if expr.intrinsic {
|
||||
id := source.INVALID_DIAGNOSTIC
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
id = source.add(checker.diagnostics, expr.span, "intrinsic calls must be unqualified")
|
||||
} else {
|
||||
id = source.addf(checker.diagnostics, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name))
|
||||
}
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if callee, handled, ok := build_qualified_value_field(checker, expr, locals, global_reads, pkg, file); handled {
|
||||
if !ok {
|
||||
last = callee
|
||||
@@ -6996,7 +7013,7 @@ build_expr :: proc(
|
||||
if frame.stage == 9 {
|
||||
result, ok := types.replace_pointer_child(&checker.module.types, checker.module.exprs[last].type, frame.target_type)
|
||||
if !ok {
|
||||
id := source.add(checker.diagnostics, expr.span, "ptr_cast operand must be a pointer or optional pointer")
|
||||
id := source.add(checker.diagnostics, expr.span, "ptrcast! operand must be a pointer or optional pointer")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else {
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
|
||||
@@ -1862,7 +1862,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
if op == .Div {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||
state, .Integer_Division, span,
|
||||
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
|
||||
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
|
||||
)
|
||||
}
|
||||
value: i128
|
||||
@@ -2059,7 +2059,7 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
}
|
||||
if builtin := type_builtin_call(checker, expr); builtin != .None {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
|
||||
}
|
||||
target, target_ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
|
||||
if !target_ok {
|
||||
@@ -2078,6 +2078,12 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
if builtin := division_builtin_call(checker, expr); builtin != .None {
|
||||
return ct_eval_division_call(state, expr, builtin, expected, depth+1)
|
||||
}
|
||||
if expr.intrinsic {
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "intrinsic calls must be unqualified")
|
||||
}
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown intrinsic '%s!'", symbol_text(checker, expr.name))
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
||||
if !available {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
|
||||
|
||||
@@ -399,7 +399,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
!symbol.is_valid(qualifier) && file_hidden_name(parser, name),
|
||||
)
|
||||
if current(parser).kind == .Left_Paren {
|
||||
call := parse_call(parser, qualifier, first, name, 0)
|
||||
call := parse_call(parser, qualifier, first, name, 0, false)
|
||||
return types.intern(&parser.module.type_store, types.Node{
|
||||
kind=.Type_Call,
|
||||
count_expr=u32(call),
|
||||
@@ -490,7 +490,7 @@ parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.
|
||||
return args[:], right_paren
|
||||
}
|
||||
|
||||
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id {
|
||||
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int, intrinsic: bool) -> ast.Expr_Id {
|
||||
if nesting >= MAX_EXPRESSION_NESTING {
|
||||
span := skip_parenthesized(parser)
|
||||
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
|
||||
@@ -502,6 +502,7 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
|
||||
qualifier=qualifier,
|
||||
name=name.symbol,
|
||||
args=args[:],
|
||||
intrinsic=intrinsic,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
@@ -885,9 +886,10 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
qualifier = first.symbol
|
||||
name = member
|
||||
}
|
||||
_, intrinsic := allow(parser, .Bang)
|
||||
if current(parser).kind == .Left_Paren {
|
||||
call := parse_call(parser, qualifier, first, name, nesting)
|
||||
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
||||
call := parse_call(parser, qualifier, first, name, nesting, intrinsic)
|
||||
if !intrinsic && current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
||||
left_brace := advance(parser)
|
||||
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
|
||||
return add_expr(parser, ast.Expr{
|
||||
@@ -901,6 +903,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
}
|
||||
return call
|
||||
}
|
||||
if intrinsic {
|
||||
return invalid_expr(parser, previous(parser).span, "expected '(' after intrinsic name")
|
||||
}
|
||||
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
||||
return parse_struct_literal(parser, qualifier, first, name, nesting)
|
||||
}
|
||||
@@ -1156,6 +1161,21 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
}
|
||||
continue
|
||||
}
|
||||
if current(parser).kind == .Bang {
|
||||
marker := advance(parser)
|
||||
if current(parser).kind != .Left_Paren {
|
||||
left = invalid_expr(parser, marker.span, "expected '(' after '!'")
|
||||
continue
|
||||
}
|
||||
left_expr := parser.module.exprs[left]
|
||||
call_span := skip_parenthesized(parser)
|
||||
left = invalid_expr(
|
||||
parser,
|
||||
span_from(left_expr.span, call_span),
|
||||
"intrinsic calls require a direct name",
|
||||
)
|
||||
continue
|
||||
}
|
||||
if current(parser).kind == .Left_Paren {
|
||||
if nesting >= MAX_EXPRESSION_NESTING {
|
||||
span := skip_parenthesized(parser)
|
||||
|
||||
+264
-106
@@ -86,6 +86,32 @@ main func() void { _ = value }
|
||||
testing.expect_value(t, module.statements[module.functions[0].body[0]].name, sink_symbol)
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_marks_only_bang_calls_as_intrinsic :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
_ = sizeof ! (i32)
|
||||
_ = sizeof(i32)
|
||||
}
|
||||
`
|
||||
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)
|
||||
|
||||
marked := module.exprs[module.statements[module.functions[0].body[0]].expr]
|
||||
ordinary := module.exprs[module.statements[module.functions[0].body[1]].expr]
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, marked.kind, ast.Expr_Kind.Call)
|
||||
testing.expect_value(t, ordinary.kind, ast.Expr_Kind.Call)
|
||||
testing.expect(t, marked.intrinsic)
|
||||
testing.expect(t, !ordinary.intrinsic)
|
||||
}
|
||||
|
||||
@(test)
|
||||
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, size_of(source.Span), 12)
|
||||
@@ -1372,14 +1398,14 @@ Color :: enum {
|
||||
blue
|
||||
}
|
||||
UserID :: distinct u32
|
||||
SIZE_GLOBAL :: size_of(i32)
|
||||
SIZE_GLOBAL :: sizeof!(i32)
|
||||
|
||||
needs_usize func(value usize) usize {
|
||||
return value
|
||||
}
|
||||
|
||||
buffer func($T type) [size_of(T)]u8 {
|
||||
data [size_of(T)]u8 = undefined
|
||||
buffer func($T type) [sizeof!(T)]u8 {
|
||||
data [sizeof!(T)]u8 = undefined
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -1387,18 +1413,18 @@ main func() i32 {
|
||||
bytes [_]u8 :: buffer(i32)
|
||||
if (needs_usize(SIZE_GLOBAL) != 4) return 1
|
||||
if (bytes.len != 4) return 2
|
||||
if (size_of([3]u8) != 3) return 3
|
||||
if (size_of([]u8) != 16) return 4
|
||||
if (align_of([]u8) != 8) return 5
|
||||
if (size_of(*anyopaque) != 8) return 6
|
||||
if (size_of(?*i32) != 8) return 7
|
||||
if (size_of(*Opaque) != 8) return 8
|
||||
if (size_of(Color) != 2) return 9
|
||||
if (align_of(Color) != 2) return 10
|
||||
if (size_of(Point) != 8) return 11
|
||||
if (align_of(Point) != 4) return 12
|
||||
if (size_of(UserID) != 4) return 13
|
||||
if (align_of(UserID) != 4) return 14
|
||||
if (sizeof!([3]u8) != 3) return 3
|
||||
if (sizeof!([]u8) != 16) return 4
|
||||
if (alignof!([]u8) != 8) return 5
|
||||
if (sizeof!(*anyopaque) != 8) return 6
|
||||
if (sizeof!(?*i32) != 8) return 7
|
||||
if (sizeof!(*Opaque) != 8) return 8
|
||||
if (sizeof!(Color) != 2) return 9
|
||||
if (alignof!(Color) != 2) return 10
|
||||
if (sizeof!(Point) != 8) return 11
|
||||
if (alignof!(Point) != 4) return 12
|
||||
if (sizeof!(UserID) != 4) return 13
|
||||
if (alignof!(UserID) != 4) return 14
|
||||
return 0
|
||||
}
|
||||
`
|
||||
@@ -1418,23 +1444,23 @@ integer_bound_builtins_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-integer-bounds"
|
||||
main_path := "/tmp/brolang-test-integer-bounds/main.bro"
|
||||
output := "/tmp/brolang-test-integer-bounds-output"
|
||||
text := `MAX_U64 u64 :: max_value(u64)
|
||||
text := `MAX_U64 u64 :: maxval!(u64)
|
||||
|
||||
maximum func($T type) T {
|
||||
return max_value(T)
|
||||
return maxval!(T)
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
if (min_value(i8) != -128) return 1
|
||||
if (max_value(i8) != 127) return 2
|
||||
if (min_value(u8) != 0) return 3
|
||||
if (max_value(u8) != 255) return 4
|
||||
if (min_value(isize) != -9223372036854775808) return 5
|
||||
if (max_value(usize) != 18446744073709551615) return 6
|
||||
if (minval!(i8) != -128) return 1
|
||||
if (maxval!(i8) != 127) return 2
|
||||
if (minval!(u8) != 0) return 3
|
||||
if (maxval!(u8) != 255) return 4
|
||||
if (minval!(isize) != -9223372036854775808) return 5
|
||||
if (maxval!(usize) != 18446744073709551615) return 6
|
||||
if (MAX_U64 != 18446744073709551615) return 7
|
||||
if (maximum(u16) != 65535) return 8
|
||||
if (min_value(c_int) != -2147483648) return 9
|
||||
if (max_value(c_ulong) != 18446744073709551615) return 10
|
||||
if (minval!(c_int) != -2147483648) return 9
|
||||
if (maxval!(c_ulong) != 18446744073709551615) return 10
|
||||
return 0
|
||||
}
|
||||
`
|
||||
@@ -1455,14 +1481,14 @@ integer_bound_builtins_reject_invalid_targets :: proc(t: ^testing.T) {
|
||||
Choice :: enum { one }
|
||||
|
||||
main func() void {
|
||||
_ = min_value()
|
||||
_ = max_value(u8, u16)
|
||||
_ = min_value(1)
|
||||
_ = max_value(int)
|
||||
_ = max_value(f32)
|
||||
_ = max_value(bool)
|
||||
_ = max_value(Named)
|
||||
_ = max_value(Choice)
|
||||
_ = minval!()
|
||||
_ = maxval!(u8, u16)
|
||||
_ = minval!(1)
|
||||
_ = maxval!(int)
|
||||
_ = maxval!(f32)
|
||||
_ = maxval!(bool)
|
||||
_ = maxval!(Named)
|
||||
_ = maxval!(Choice)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -1496,11 +1522,11 @@ layout_builtins_reject_unsized_targets :: proc(t: ^testing.T) {
|
||||
Fn :: alias func() void
|
||||
|
||||
main func() void {
|
||||
_ = size_of(void)
|
||||
_ = align_of(anyopaque)
|
||||
_ = size_of(Fn)
|
||||
_ = size_of(Opaque)
|
||||
_ = align_of(1)
|
||||
_ = sizeof!(void)
|
||||
_ = alignof!(anyopaque)
|
||||
_ = sizeof!(Fn)
|
||||
_ = sizeof!(Opaque)
|
||||
_ = alignof!(1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -1803,14 +1829,14 @@ main func() void {
|
||||
}
|
||||
|
||||
@(test)
|
||||
opaque_anyopaque_and_ptr_cast_compile_and_lower :: proc(t: ^testing.T) {
|
||||
opaque_anyopaque_and_ptrcast_compile_and_lower :: proc(t: ^testing.T) {
|
||||
text := `Handle :: opaque
|
||||
take func(_ ?*mut anyopaque) void {}
|
||||
use_handle func(_ ?@mut Handle) void {}
|
||||
main func() void {
|
||||
values [2]mut u8 = [1, 2]
|
||||
raw ?*mut anyopaque = (&values).ptr
|
||||
bytes ?*mut u8 = ptr_cast(u8, raw)
|
||||
bytes ?*mut u8 = ptrcast!(u8, raw)
|
||||
take(bytes)
|
||||
if bytes |p| {
|
||||
p[1] = 5
|
||||
@@ -1818,7 +1844,7 @@ main func() void {
|
||||
|
||||
one u8 = 1
|
||||
single ?@mut anyopaque = &one
|
||||
typed ?@mut u8 = ptr_cast(u8, single)
|
||||
typed ?@mut u8 = ptrcast!(u8, single)
|
||||
if typed |p| {
|
||||
p^ = 2
|
||||
}
|
||||
@@ -1858,16 +1884,16 @@ main func() void {
|
||||
}
|
||||
|
||||
@(test)
|
||||
anyopaque_by_value_and_invalid_ptr_casts_are_rejected :: proc(t: ^testing.T) {
|
||||
anyopaque_by_value_and_invalid_ptrcasts_are_rejected :: proc(t: ^testing.T) {
|
||||
text := `Callback :: alias c_func() void
|
||||
main func() void {
|
||||
raw ?*mut anyopaque = none
|
||||
value anyopaque = undefined
|
||||
_ = ptr_cast(void, raw)
|
||||
_ = ptr_cast(anyopaque, raw)
|
||||
_ = ptr_cast(Callback, raw)
|
||||
_ = ptr_cast(u8, 1)
|
||||
_ = ptr_cast(1, raw)
|
||||
_ = ptrcast!(void, raw)
|
||||
_ = ptrcast!(anyopaque, raw)
|
||||
_ = ptrcast!(Callback, raw)
|
||||
_ = ptrcast!(u8, 1)
|
||||
_ = ptrcast!(1, raw)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -1888,9 +1914,9 @@ main func() void {
|
||||
found_target_type := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_by_value = found_by_value || strings.contains(diagnostic.message, "could not infer a concrete type")
|
||||
found_bad_target = found_bad_target || strings.contains(diagnostic.message, "ptr_cast target must be a sized runtime object type")
|
||||
found_bad_operand = found_bad_operand || strings.contains(diagnostic.message, "ptr_cast operand must be a pointer")
|
||||
found_target_type = found_target_type || strings.contains(diagnostic.message, "ptr_cast target must be a type")
|
||||
found_bad_target = found_bad_target || strings.contains(diagnostic.message, "ptrcast! target must be a sized runtime object type")
|
||||
found_bad_operand = found_bad_operand || strings.contains(diagnostic.message, "ptrcast! operand must be a pointer")
|
||||
found_target_type = found_target_type || strings.contains(diagnostic.message, "ptrcast! target must be a type")
|
||||
}
|
||||
testing.expect(t, found_by_value)
|
||||
testing.expect(t, found_bad_target)
|
||||
@@ -1898,6 +1924,138 @@ main func() void {
|
||||
testing.expect(t, found_target_type)
|
||||
}
|
||||
|
||||
@(test)
|
||||
old_intrinsic_spellings_are_not_recognized :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
_ = ptr_cast(1, 1)
|
||||
_ = size_of(1)
|
||||
_ = align_of(1)
|
||||
_ = min_value(1)
|
||||
_ = max_value(1)
|
||||
_ = div_trunc(1, 1)
|
||||
_ = div_floor(1, 1)
|
||||
_ = div_exact(1, 1)
|
||||
_ = div_ceil(1, 1)
|
||||
}
|
||||
`
|
||||
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)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
names := [?]string{
|
||||
"ptr_cast", "size_of", "align_of", "min_value", "max_value",
|
||||
"div_trunc", "div_floor", "div_exact", "div_ceil",
|
||||
}
|
||||
found := [len(names)]bool{}
|
||||
for diagnostic in diagnostics.items {
|
||||
if !strings.contains(diagnostic.message, "unresolved function") {
|
||||
continue
|
||||
}
|
||||
for name, index in names {
|
||||
found[index] = found[index] || strings.contains(diagnostic.message, name)
|
||||
}
|
||||
}
|
||||
for value in found {
|
||||
testing.expect(t, value)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
bare_intrinsic_names_are_available_to_user_functions :: proc(t: ^testing.T) {
|
||||
text := `ptrcast func() i32 { return 1 }
|
||||
sizeof func() i32 { return 2 }
|
||||
alignof func() i32 { return 3 }
|
||||
minval func() i32 { return 4 }
|
||||
maxval func() i32 { return 5 }
|
||||
divtrunc func() i32 { return 6 }
|
||||
divfloor func() i32 { return 7 }
|
||||
divexact func() i32 { return 8 }
|
||||
divceil func() i32 { return 9 }
|
||||
rem func() i32 { return 10 }
|
||||
mod func() i32 { return 11 }
|
||||
main func() i32 {
|
||||
return ptrcast() + sizeof() + alignof() + minval() + maxval() +
|
||||
divtrunc() + divfloor() + divexact() + divceil() + rem() + mod() - 66
|
||||
}
|
||||
`
|
||||
directory := "/tmp/brolang-test-user-intrinsic-names"
|
||||
main_path := "/tmp/brolang-test-user-intrinsic-names/main.bro"
|
||||
output := "/tmp/brolang-test-user-intrinsic-names-output"
|
||||
_ = 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)
|
||||
}
|
||||
|
||||
@(test)
|
||||
intrinsic_call_diagnostics_are_precise :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
_ = mystery!()
|
||||
_ = math.ptrcast!()
|
||||
}
|
||||
`
|
||||
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)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found_unknown := false
|
||||
found_qualified := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_unknown = found_unknown || diagnostic.message == "unknown intrinsic 'mystery!'"
|
||||
found_qualified = found_qualified || diagnostic.message == "intrinsic calls must be unqualified"
|
||||
}
|
||||
testing.expect(t, found_unknown)
|
||||
testing.expect(t, found_qualified)
|
||||
}
|
||||
|
||||
@(test)
|
||||
malformed_intrinsic_calls_have_targeted_parse_diagnostics :: proc(t: ^testing.T) {
|
||||
cases := [?]struct {
|
||||
text, message: string,
|
||||
}{
|
||||
{`main func() void { _ = sizeof! }`, "expected '(' after intrinsic name"},
|
||||
{`callback func() void {}
|
||||
main func() void { (callback)!() }
|
||||
`, "intrinsic calls require a direct name"},
|
||||
}
|
||||
for test_case in cases {
|
||||
source_file := source.Source{path="test.bro", text=test_case.text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
symbols := symbol.init_table()
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || diagnostic.message == test_case.message
|
||||
}
|
||||
testing.expect(t, found)
|
||||
|
||||
ast.destroy_module(&module)
|
||||
delete(stream.items)
|
||||
symbol.destroy_table(&symbols)
|
||||
source.destroy_diagnostics(&diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) {
|
||||
text := `Small :: c_struct {
|
||||
@@ -6541,7 +6699,7 @@ main func() void {}
|
||||
|
||||
@(test)
|
||||
constant_division_by_zero_has_a_precise_diagnostic :: proc(t: ^testing.T) {
|
||||
text := `value :: div_trunc(5, 0)
|
||||
text := `value :: divtrunc!(5, 0)
|
||||
main func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -9669,12 +9827,12 @@ compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T)
|
||||
signed += 6
|
||||
signed -= 2
|
||||
signed *= 3
|
||||
signed = div_trunc(signed, 4)
|
||||
signed = divtrunc!(signed, 4)
|
||||
unsigned u32 = 24
|
||||
unsigned += 6
|
||||
unsigned -= 2
|
||||
unsigned *= 3
|
||||
unsigned = div_trunc(unsigned, 4)
|
||||
unsigned = divtrunc!(unsigned, 4)
|
||||
real f64 = 24.0
|
||||
real += 6.0
|
||||
real -= 2.0
|
||||
@@ -9838,7 +9996,7 @@ checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
|
||||
a i32 = 10
|
||||
b i32 = 3
|
||||
c i32 = a - b
|
||||
return div_trunc(c, b)
|
||||
return divtrunc!(c, b)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -9933,20 +10091,20 @@ main func() void { _ = $half(4) }`, want="integer '/' is not allowed"},
|
||||
|
||||
@(test)
|
||||
division_builtins_diagnose_arity_operands_and_comptime_failures :: proc(t: ^testing.T) {
|
||||
text := `bad_arity :: div_floor(1)
|
||||
bad_bool :: rem(true, false)
|
||||
bad_family :: mod(i32(5), f32(3))
|
||||
zero_trunc :: div_trunc(1, 0)
|
||||
zero_floor :: div_floor(1.0, 0.0)
|
||||
zero_exact :: div_exact(1, 0)
|
||||
zero_ceil :: div_ceil(1.0, 0.0)
|
||||
zero_rem :: rem(1, 0)
|
||||
zero_mod :: mod(1.0, 0.0)
|
||||
inexact :: div_exact(5, 3)
|
||||
overflow_trunc :: div_trunc(min_value(i32), -1)
|
||||
overflow_floor :: div_floor(min_value(i32), -1)
|
||||
overflow_exact :: div_exact(min_value(i32), -1)
|
||||
overflow_ceil :: div_ceil(min_value(i32), -1)
|
||||
text := `bad_arity :: divfloor!(1)
|
||||
bad_bool :: rem!(true, false)
|
||||
bad_family :: mod!(i32(5), f32(3))
|
||||
zero_trunc :: divtrunc!(1, 0)
|
||||
zero_floor :: divfloor!(1.0, 0.0)
|
||||
zero_exact :: divexact!(1, 0)
|
||||
zero_ceil :: divceil!(1.0, 0.0)
|
||||
zero_rem :: rem!(1, 0)
|
||||
zero_mod :: mod!(1.0, 0.0)
|
||||
inexact :: divexact!(5, 3)
|
||||
overflow_trunc :: divtrunc!(minval!(i32), -1)
|
||||
overflow_floor :: divfloor!(minval!(i32), -1)
|
||||
overflow_exact :: divexact!(minval!(i32), -1)
|
||||
overflow_ceil :: divceil!(minval!(i32), -1)
|
||||
main func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -9986,28 +10144,28 @@ division_family_compiles_and_runs_for_integer_and_float_scalars :: proc(t: ^test
|
||||
directory := "/tmp/brolang-test-division-family"
|
||||
main_path := "/tmp/brolang-test-division-family/main.bro"
|
||||
output := "/tmp/brolang-test-division-family-output"
|
||||
text := `COUNT :: div_exact(8, 2)
|
||||
items [div_ceil(10, 3)]u8 :: [0, 0, 0, 0]
|
||||
text := `COUNT :: divexact!(8, 2)
|
||||
items [divceil!(10, 3)]u8 :: [0, 0, 0, 0]
|
||||
OPEN :: 5
|
||||
open_ceil i32 :: div_ceil(OPEN, 3)
|
||||
open_ceil i32 :: divceil!(OPEN, 3)
|
||||
|
||||
check_i32 func(a, b, qt, qf, qc, r, m i32) bool {
|
||||
return div_trunc(a, b) == qt and div_floor(a, b) == qf and
|
||||
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m
|
||||
return divtrunc!(a, b) == qt and divfloor!(a, b) == qf and
|
||||
divceil!(a, b) == qc and rem!(a, b) == r and mod!(a, b) == m
|
||||
}
|
||||
|
||||
check_f32 func(a, b, qt, qf, qc, r, m f32) bool {
|
||||
return div_trunc(a, b) == qt and div_floor(a, b) == qf and
|
||||
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m
|
||||
return divtrunc!(a, b) == qt and divfloor!(a, b) == qf and
|
||||
divceil!(a, b) == qc and rem!(a, b) == r and mod!(a, b) == m
|
||||
}
|
||||
|
||||
check_f64 func(a, b, qt, qf, qc, r, m f64) bool {
|
||||
return div_trunc(a, b) == qt and div_floor(a, b) == qf and
|
||||
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m
|
||||
return divtrunc!(a, b) == qt and divfloor!(a, b) == qf and
|
||||
divceil!(a, b) == qc and rem!(a, b) == r and mod!(a, b) == m
|
||||
}
|
||||
|
||||
edge_rem func(a, b i32) i32 { return rem(a, b) }
|
||||
edge_mod func(a, b i32) i32 { return mod(a, b) }
|
||||
edge_rem func(a, b i32) i32 { return rem!(a, b) }
|
||||
edge_mod func(a, b i32) i32 { return mod!(a, b) }
|
||||
|
||||
main func() i32 {
|
||||
if COUNT != 4 or items.len != 4 or open_ceil != 2 { return 1 }
|
||||
@@ -10015,9 +10173,9 @@ main func() i32 {
|
||||
if !check_i32(5, -3, -1, -2, -1, 2, -1) { return 3 }
|
||||
if !check_i32(-5, 3, -1, -2, -1, -2, 1) { return 4 }
|
||||
if !check_i32(-5, -3, 1, 1, 2, -2, -2) { return 5 }
|
||||
if div_trunc(u32(5), u32(3)) != 1 or div_floor(u32(5), u32(3)) != 1 or
|
||||
div_ceil(u32(5), u32(3)) != 2 or rem(u32(5), u32(3)) != 2 or mod(u32(5), u32(3)) != 2 { return 6 }
|
||||
if div_exact(i32(6), i32(3)) != 2 or div_exact(u32(6), u32(3)) != 2 { return 7 }
|
||||
if divtrunc!(u32(5), u32(3)) != 1 or divfloor!(u32(5), u32(3)) != 1 or
|
||||
divceil!(u32(5), u32(3)) != 2 or rem!(u32(5), u32(3)) != 2 or mod!(u32(5), u32(3)) != 2 { return 6 }
|
||||
if divexact!(i32(6), i32(3)) != 2 or divexact!(u32(6), u32(3)) != 2 { return 7 }
|
||||
if !check_f32(f32(5.0), f32(3.0), f32(1.0), f32(1.0), f32(2.0), f32(2.0), f32(2.0)) or
|
||||
!check_f32(f32(5.0), f32(-3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(2.0), f32(-1.0)) or
|
||||
!check_f32(f32(-5.0), f32(3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(-2.0), f32(1.0)) or
|
||||
@@ -10026,7 +10184,7 @@ main func() i32 {
|
||||
!check_f64(5.0, -3.0, -1.0, -2.0, -1.0, 2.0, -1.0) or
|
||||
!check_f64(-5.0, 3.0, -1.0, -2.0, -1.0, -2.0, 1.0) or
|
||||
!check_f64(-5.0, -3.0, 1.0, 1.0, 2.0, -2.0, -2.0) { return 9 }
|
||||
if div_exact(f32(6.0), f32(3.0)) != 2.0 or div_exact(f64(6.0), f64(3.0)) != 2.0 { return 10 }
|
||||
if divexact!(f32(6.0), f32(3.0)) != 2.0 or divexact!(f64(6.0), f64(3.0)) != 2.0 { return 10 }
|
||||
if edge_rem(-2147483648, -1) != 0 or edge_mod(-2147483648, -1) != 0 { return 11 }
|
||||
return 0
|
||||
}
|
||||
@@ -10051,24 +10209,24 @@ division_builtins_trap_for_runtime_zero_overflow_and_inexact_results :: proc(t:
|
||||
right: string,
|
||||
}
|
||||
cases := [?]Case{
|
||||
{name="div_trunc", type_name="i32", left="1", right="0"},
|
||||
{name="div_floor", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
|
||||
{name="div_exact", type_name="f64", left="1.0", right="0.0"},
|
||||
{name="div_ceil", type_name="i32", left="1", right="0"},
|
||||
{name="divtrunc", type_name="i32", left="1", right="0"},
|
||||
{name="divfloor", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
|
||||
{name="divexact", type_name="f64", left="1.0", right="0.0"},
|
||||
{name="divceil", type_name="i32", left="1", right="0"},
|
||||
{name="rem", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
|
||||
{name="mod", type_name="f64", left="1.0", right="0.0"},
|
||||
{name="div_exact", type_name="i32", left="5", right="3"},
|
||||
{name="div_trunc", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="div_floor", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="div_exact", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="div_ceil", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="divexact", type_name="i32", left="5", right="3"},
|
||||
{name="divtrunc", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="divfloor", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="divexact", type_name="i32", left="-2147483648", right="-1"},
|
||||
{name="divceil", type_name="i32", left="-2147483648", right="-1"},
|
||||
}
|
||||
for test_case, index in cases {
|
||||
directory := fmt.aprintf("/tmp/brolang-test-division-trap-%d", index)
|
||||
main_path := fmt.aprintf("%s/main.bro", directory)
|
||||
output := fmt.aprintf("/tmp/brolang-test-division-trap-output-%d", index)
|
||||
text := fmt.aprintf(
|
||||
"invoke func(a, b %s) %s {{ return %s(a, b) }}\nmain func() void {{ _ = invoke(%s, %s) }}\n",
|
||||
"invoke func(a, b %s) %s {{ return %s!(a, b) }}\nmain func() void {{ _ = invoke(%s, %s) }}\n",
|
||||
test_case.type_name, test_case.type_name, test_case.name, test_case.left, test_case.right,
|
||||
)
|
||||
_ = os2.remove_all(directory)
|
||||
@@ -10096,10 +10254,10 @@ qualified_division_builtin_names_resolve_as_package_functions :: proc(t: ^testin
|
||||
math_path := "/tmp/brolang-test-qualified-division/math/math.bro"
|
||||
main_path := "/tmp/brolang-test-qualified-division/app/main.bro"
|
||||
output := "/tmp/brolang-test-qualified-division-output"
|
||||
math_text := `div_floor func(a, b i32) i32 { return a + b }
|
||||
math_text := `divfloor func(a, b i32) i32 { return a + b }
|
||||
`
|
||||
main_text := `math :: import "../math"
|
||||
main func() i32 { return math.div_floor(20, 22) }
|
||||
main func() i32 { return math.divfloor(20, 22) }
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
@@ -10117,17 +10275,17 @@ main func() i32 { return math.div_floor(20, 22) }
|
||||
|
||||
@(test)
|
||||
division_builtins_emit_guards_rounding_and_single_integer_divisions :: proc(t: ^testing.T) {
|
||||
text := `floor_i32 func(a, b i32) i32 { return div_floor(a, b) }
|
||||
ceil_i32 func(a, b i32) i32 { return div_ceil(a, b) }
|
||||
exact_i32 func(a, b i32) i32 { return div_exact(a, b) }
|
||||
floor_u32 func(a, b u32) u32 { return div_floor(a, b) }
|
||||
rem_i16 func(a, b i16) i16 { return rem(a, b) }
|
||||
mod_i16 func(a, b i16) i16 { return mod(a, b) }
|
||||
floor_f32 func(a, b f32) f32 { return div_floor(a, b) }
|
||||
ceil_f64 func(a, b f64) f64 { return div_ceil(a, b) }
|
||||
exact_f32 func(a, b f32) f32 { return div_exact(a, b) }
|
||||
rem_f64 func(a, b f64) f64 { return rem(a, b) }
|
||||
mod_f32 func(a, b f32) f32 { return mod(a, b) }
|
||||
text := `floor_i32 func(a, b i32) i32 { return divfloor!(a, b) }
|
||||
ceil_i32 func(a, b i32) i32 { return divceil!(a, b) }
|
||||
exact_i32 func(a, b i32) i32 { return divexact!(a, b) }
|
||||
floor_u32 func(a, b u32) u32 { return divfloor!(a, b) }
|
||||
rem_i16 func(a, b i16) i16 { return rem!(a, b) }
|
||||
mod_i16 func(a, b i16) i16 { return mod!(a, b) }
|
||||
floor_f32 func(a, b f32) f32 { return divfloor!(a, b) }
|
||||
ceil_f64 func(a, b f64) f64 { return divceil!(a, b) }
|
||||
exact_f32 func(a, b f32) f32 { return divexact!(a, b) }
|
||||
rem_f64 func(a, b f64) f64 { return rem!(a, b) }
|
||||
mod_f32 func(a, b f32) f32 { return mod!(a, b) }
|
||||
main func() void {
|
||||
_ = floor_i32(5, 3)
|
||||
_ = ceil_i32(5, 3)
|
||||
|
||||
@@ -14,7 +14,7 @@ check_float func() i32 {
|
||||
|
||||
check_unsigned func() i32 {
|
||||
n u32 = 100
|
||||
n = div_trunc(n, 7) # 14
|
||||
n = divtrunc!(n, 7) # 14
|
||||
n -= 4 # 10
|
||||
if n == 10 {
|
||||
return 1
|
||||
@@ -27,7 +27,7 @@ main func() i32 {
|
||||
total += 10 # 10
|
||||
total -= 3 # 7
|
||||
total *= 4 # 28
|
||||
total = div_trunc(total, 2) # 14
|
||||
total = divtrunc!(total, 2) # 14
|
||||
|
||||
# binary operators honour precedence: 14 + (2 * 3) - 4 == 16
|
||||
total = total + 2 * 3 - 4
|
||||
|
||||
@@ -2,7 +2,7 @@ mem :: import "@std/mem"
|
||||
|
||||
_probe_count func(context ?*mut anyopaque) void {
|
||||
if context |raw| {
|
||||
counts *mut usize :: ptr_cast(usize, raw)
|
||||
counts *mut usize :: ptrcast!(usize, raw)
|
||||
counts[0] += 1
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ _probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
}
|
||||
|
||||
typed_allocator_test func() i32 {
|
||||
if (size_of(mem.Allocator) != 16) return 31
|
||||
if (sizeof!(mem.Allocator) != 16) return 31
|
||||
|
||||
first_calls [1]mut usize = [0]
|
||||
second_calls [1]mut usize = [0]
|
||||
@@ -76,7 +76,7 @@ typed_allocator_test func() i32 {
|
||||
}
|
||||
if (overflow_fallback_failed) return 37
|
||||
overflow_failed bool = false
|
||||
_ = mem.alloc(u64, first_allocator, max_value(usize)) catch |_| {
|
||||
_ = mem.alloc(u64, first_allocator, maxval!(usize)) catch |_| {
|
||||
overflow_failed = true
|
||||
yield overflow_fallback
|
||||
}
|
||||
|
||||
@@ -39,6 +39,6 @@ main func() i32 {
|
||||
if (pointer.value != 42) return 4
|
||||
buffer Buffer(u8, 4) :: Buffer(u8, 4) { values = [1, 2, 3, 4] }
|
||||
if (buffer.values.len != 4) return 2
|
||||
if (size_of(Buffer(u8, 4)) != 4) return 5
|
||||
if (sizeof!(Buffer(u8, 4)) != 4) return 5
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
(function_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)
|
||||
|
||||
@@ -30,8 +30,8 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
|
||||
new_capacity usize = 8
|
||||
if list.capacity >= 8 {
|
||||
half usize :: div_trunc(list.capacity, 2)
|
||||
if list.capacity > max_value(usize) - half {
|
||||
half usize :: divtrunc!(list.capacity, 2)
|
||||
if list.capacity > maxval!(usize) - half {
|
||||
new_capacity = minimum_capacity
|
||||
} else {
|
||||
new_capacity = list.capacity + half
|
||||
@@ -53,7 +53,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
|
||||
append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||
length usize :: list.items.len
|
||||
if length == max_value(usize) {
|
||||
if length == maxval!(usize) {
|
||||
return .out_of_memory
|
||||
}
|
||||
try reserve(list, length + 1)
|
||||
|
||||
+2
-2
@@ -76,7 +76,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
|
||||
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||
request usize = buffer.len
|
||||
maximum usize :: usize(max_value(c_long))
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
}
|
||||
@@ -94,7 +94,7 @@ _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize !
|
||||
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||
fd c_int :: c_int(stream)
|
||||
request usize = bytes.len
|
||||
maximum usize :: usize(max_value(c_long))
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
}
|
||||
|
||||
+16
-16
@@ -44,7 +44,7 @@ eql func($T type, left, right []T) bool {
|
||||
_empty_storage [1]mut u64 = [0]
|
||||
|
||||
_empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr)
|
||||
pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
@@ -57,17 +57,17 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return _empty_slice(T, 0)
|
||||
}
|
||||
|
||||
element_size usize :: size_of(T)
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return _empty_slice(T, count)
|
||||
}
|
||||
if count > div_trunc(max_value(usize), element_size) {
|
||||
if count > divtrunc!(maxval!(usize), element_size) {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T))
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptr_cast(T, bytes)
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..count]
|
||||
}
|
||||
return .out_of_memory
|
||||
@@ -82,18 +82,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
return _empty_slice(T, 0)
|
||||
}
|
||||
|
||||
element_size usize :: size_of(T)
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return _empty_slice(T, new_count)
|
||||
}
|
||||
if new_count > div_trunc(max_value(usize), element_size) {
|
||||
if new_count > divtrunc!(maxval!(usize), element_size) {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
old_memory ?*mut u8 = none
|
||||
old_size usize = 0
|
||||
if memory.len != 0 {
|
||||
old_memory = ptr_cast(u8, memory.ptr)
|
||||
old_memory = ptrcast!(u8, memory.ptr)
|
||||
old_size = memory.len * element_size
|
||||
}
|
||||
resized ?*mut u8 = raw_realloc(
|
||||
@@ -101,18 +101,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
old_memory,
|
||||
old_size,
|
||||
new_count * element_size,
|
||||
align_of(T),
|
||||
alignof!(T),
|
||||
)
|
||||
if resized |bytes| {
|
||||
pointer *mut T :: ptr_cast(T, bytes)
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..new_count]
|
||||
}
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
free func($T type, allocator Allocator, memory []mut T) void {
|
||||
if memory.len != 0 and size_of(T) != 0 {
|
||||
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T))
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ _power_of_two func(value usize) bool {
|
||||
|
||||
current usize = value
|
||||
while current > 1 {
|
||||
half usize = div_trunc(current, 2)
|
||||
half usize = divtrunc!(current, 2)
|
||||
if half * 2 != current {
|
||||
return false
|
||||
}
|
||||
@@ -141,7 +141,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
}
|
||||
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.malloc(c_ulong(size)))
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [none]
|
||||
@@ -150,7 +150,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
return none
|
||||
}
|
||||
|
||||
return ptr_cast(u8, memory[0])
|
||||
return ptrcast!(u8, memory[0])
|
||||
}
|
||||
|
||||
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@@ -165,7 +165,7 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
||||
|
||||
if memory |old_memory| {
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
|
||||
|
||||
@@ -30,8 +30,8 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
|
||||
new_capacity usize = 8
|
||||
if list.capacity >= 8 {
|
||||
half usize :: div_trunc(list.capacity, 2)
|
||||
if list.capacity > max_value(usize) - half {
|
||||
half usize :: divtrunc!(list.capacity, 2)
|
||||
if list.capacity > maxval!(usize) - half {
|
||||
new_capacity = minimum_capacity
|
||||
} else {
|
||||
new_capacity = list.capacity + half
|
||||
@@ -53,7 +53,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
|
||||
append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||
length usize :: list.items.len
|
||||
if length == max_value(usize) {
|
||||
if length == maxval!(usize) {
|
||||
return .out_of_memory
|
||||
}
|
||||
try reserve(list, length + 1)
|
||||
|
||||
@@ -76,7 +76,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
|
||||
_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||
request usize = buffer.len
|
||||
maximum usize :: usize(max_value(c_long))
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
}
|
||||
@@ -94,7 +94,7 @@ _system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize !
|
||||
_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||
fd c_int :: c_int(stream)
|
||||
request usize = bytes.len
|
||||
maximum usize :: usize(max_value(c_long))
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ eql func($T type, left, right []T) bool {
|
||||
_empty_storage [1]mut u64 = [0]
|
||||
|
||||
_empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr)
|
||||
pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
@@ -57,17 +57,17 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return _empty_slice(T, 0)
|
||||
}
|
||||
|
||||
element_size usize :: size_of(T)
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return _empty_slice(T, count)
|
||||
}
|
||||
if count > div_trunc(max_value(usize), element_size) {
|
||||
if count > divtrunc!(maxval!(usize), element_size) {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T))
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptr_cast(T, bytes)
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..count]
|
||||
}
|
||||
return .out_of_memory
|
||||
@@ -82,18 +82,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
return _empty_slice(T, 0)
|
||||
}
|
||||
|
||||
element_size usize :: size_of(T)
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return _empty_slice(T, new_count)
|
||||
}
|
||||
if new_count > div_trunc(max_value(usize), element_size) {
|
||||
if new_count > divtrunc!(maxval!(usize), element_size) {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
old_memory ?*mut u8 = none
|
||||
old_size usize = 0
|
||||
if memory.len != 0 {
|
||||
old_memory = ptr_cast(u8, memory.ptr)
|
||||
old_memory = ptrcast!(u8, memory.ptr)
|
||||
old_size = memory.len * element_size
|
||||
}
|
||||
resized ?*mut u8 = raw_realloc(
|
||||
@@ -101,18 +101,18 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
old_memory,
|
||||
old_size,
|
||||
new_count * element_size,
|
||||
align_of(T),
|
||||
alignof!(T),
|
||||
)
|
||||
if resized |bytes| {
|
||||
pointer *mut T :: ptr_cast(T, bytes)
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..new_count]
|
||||
}
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
free func($T type, allocator Allocator, memory []mut T) void {
|
||||
if memory.len != 0 and size_of(T) != 0 {
|
||||
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T))
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ _power_of_two func(value usize) bool {
|
||||
|
||||
current usize = value
|
||||
while current > 1 {
|
||||
half usize = div_trunc(current, 2)
|
||||
half usize = divtrunc!(current, 2)
|
||||
if half * 2 != current {
|
||||
return false
|
||||
}
|
||||
@@ -141,7 +141,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
}
|
||||
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.malloc(c_ulong(size)))
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [none]
|
||||
@@ -150,7 +150,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
return none
|
||||
}
|
||||
|
||||
return ptr_cast(u8, memory[0])
|
||||
return ptrcast!(u8, memory[0])
|
||||
}
|
||||
|
||||
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@@ -165,7 +165,7 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
||||
|
||||
if memory |old_memory| {
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
|
||||
|
||||
@@ -30,7 +30,7 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
||||
_empty_storage [1]mut u64 = [0]
|
||||
|
||||
_empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr)
|
||||
pointer *mut T :: ptrcast!(T, (&_empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
@@ -39,25 +39,25 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return _empty_slice(T, 0)
|
||||
}
|
||||
|
||||
element_size usize :: size_of(T)
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return _empty_slice(T, count)
|
||||
}
|
||||
if count > max_value(usize) / element_size {
|
||||
if count > maxval!(usize) / element_size {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T))
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptr_cast(T, bytes)
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..count]
|
||||
}
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
free func($T type, allocator Allocator, memory []mut T) void {
|
||||
if memory.len != 0 and size_of(T) != 0 {
|
||||
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T))
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
}
|
||||
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.malloc(c_ulong(size)))
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [none]
|
||||
@@ -95,7 +95,7 @@ _c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
return none
|
||||
}
|
||||
|
||||
return ptr_cast(u8, memory[0])
|
||||
return ptrcast!(u8, memory[0])
|
||||
}
|
||||
|
||||
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@@ -110,7 +110,7 @@ _c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usi
|
||||
|
||||
if memory |old_memory| {
|
||||
if alignment <= _malloc_alignment {
|
||||
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
|
||||
|
||||
@@ -383,6 +383,7 @@ module.exports = grammar({
|
||||
$.catch_expression,
|
||||
$.unary_expression,
|
||||
$.field_expression,
|
||||
$.intrinsic_call_expression,
|
||||
$.call_expression,
|
||||
$.index_expression,
|
||||
$.slice_expression,
|
||||
@@ -440,6 +441,12 @@ module.exports = grammar({
|
||||
field('field', $.identifier),
|
||||
)),
|
||||
|
||||
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),
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
(function_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)
|
||||
|
||||
@@ -2696,6 +2696,10 @@
|
||||
"type": "SYMBOL",
|
||||
"name": "field_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "intrinsic_call_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "call_expression"
|
||||
@@ -3271,6 +3275,39 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"intrinsic_call_expression": {
|
||||
"type": "PREC",
|
||||
"value": 9,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "function",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "marker",
|
||||
"content": {
|
||||
"type": "STRING",
|
||||
"value": "!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "arguments",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "argument_list"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"call_expression": {
|
||||
"type": "PREC_LEFT",
|
||||
"value": 9,
|
||||
|
||||
@@ -730,6 +730,10 @@
|
||||
"type": "integer",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "intrinsic_call_expression",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "multiline_string",
|
||||
"named": true
|
||||
@@ -1309,6 +1313,42 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "intrinsic_call_expression",
|
||||
"named": true,
|
||||
"fields": {
|
||||
"arguments": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "argument_list",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"function": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "identifier",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"marker": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "!",
|
||||
"named": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "keyed_field_initializer",
|
||||
"named": true,
|
||||
|
||||
+107077
-106183
File diff suppressed because it is too large
Load Diff
@@ -96,6 +96,34 @@ sum func(a, b i32) i32 ! Status {
|
||||
(enum_literal
|
||||
(identifier))))))))))))
|
||||
|
||||
==================
|
||||
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))))))))))
|
||||
|
||||
==================
|
||||
Errdefer
|
||||
==================
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
main func() void {
|
||||
_ = sizeof!(i32)
|
||||
# ^^^^^^ function.builtin
|
||||
# ^ operator
|
||||
_ = sizeof(i32)
|
||||
# ^^^^^^ function
|
||||
}
|
||||
Reference in New Issue
Block a user