rename intrinsics

This commit is contained in:
2026-07-14 19:22:20 +02:00
parent 471896b48a
commit 5157cf3bcc
26 changed files with 107640 additions and 106418 deletions
+26 -22
View File
@@ -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
+2 -2
View File
@@ -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`
+15 -14
View File
@@ -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
+1
View File
@@ -120,6 +120,7 @@ Expr :: struct {
body: []Stmt_Id,
diagnostic: source.Diagnostic_Id,
parenthesized: bool,
intrinsic: bool,
kind: Expr_Kind,
}
+44 -27
View File
@@ -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{
+8 -2
View File
@@ -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")
+24 -4
View File
@@ -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
View File
@@ -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
}
+1 -1
View File
@@ -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
}
+1
View File
@@ -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)
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+10 -10
View File
@@ -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)
+7
View File
@@ -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)
+37
View File
@@ -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,
+40
View File
@@ -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
View File
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
}