Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5157cf3bcc | |||
| 471896b48a |
+28
-24
@@ -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
|
||||
@@ -89,56 +89,60 @@ fields. `_` is not a keyword member name.
|
||||
- boolean `if` / `else if` / `else`, braceless single-statement branches, and optional parenthesized conditions
|
||||
- `while` loops with optional post-iteration update clauses
|
||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures
|
||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks
|
||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
|
||||
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
||||
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `yield :label value`
|
||||
- bare void `return`, same-line `return value`, value blocks, value `if`, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value`
|
||||
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
|
||||
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
||||
- direct `return match ...` and `yield match ...` value-control-flow operands
|
||||
|
||||
#### 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
|
||||
@@ -733,8 +733,8 @@
|
||||
(needs string building), struct field defaults to drop `&[]` on empty lists
|
||||
|
||||
29. fix bugs (implemented)
|
||||
- `return _` was already the supported empty return for void functions; the original
|
||||
bare-`return` report was stale
|
||||
- bare `return` is the empty return for void functions; `yield` always requires a
|
||||
same-line, non-void value because `break` handles valueless scope exits
|
||||
- catch value blocks may end by returning from the function instead of yielding when
|
||||
every path exits
|
||||
- implicit-conversion diagnostics render source-level composite and named types instead
|
||||
@@ -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,6 +834,9 @@
|
||||
- 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. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
|
||||
user functions (implemented)
|
||||
|
||||
## A word on unchecked casts
|
||||
|
||||
For casts that bypass safety checks, Honey provides builtin functions:
|
||||
@@ -842,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
|
||||
@@ -857,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{
|
||||
@@ -7516,7 +7533,7 @@ build_block :: proc(
|
||||
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else if !types.is_void(ctx.result) {
|
||||
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
|
||||
id := source.add(checker.diagnostics, statement.span, "non-void function must return a value")
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||
@@ -8118,6 +8135,16 @@ build_block :: proc(
|
||||
} else {
|
||||
yielded = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
|
||||
}
|
||||
if yielded != hir.INVALID_EXPR && types.is_void(checker.module.exprs[yielded].type) {
|
||||
id := source.add(checker.diagnostics, statement.span, "'yield' expression must produce a non-void value")
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span)
|
||||
if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR {
|
||||
id := source.add(checker.diagnostics, statement.span,
|
||||
@@ -8291,9 +8318,19 @@ build_value_block :: proc(
|
||||
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
expected, ctx.pkg, ctx.file,
|
||||
)
|
||||
value_type = checker.module.exprs[value].type
|
||||
}
|
||||
value_type = checker.module.exprs[value].type
|
||||
if is_runtime_type(checker, expected) {
|
||||
if types.is_void(value_type) {
|
||||
id := source.add(checker.diagnostics, yield_stmt.span, "'yield' expression must produce a non-void value")
|
||||
value = invalid_hir_expr(checker, yield_stmt.span, id)
|
||||
value_type = types.INVALID
|
||||
ctx.problematic^ = true
|
||||
} else if types.is_void(expected) {
|
||||
id := source.add(checker.diagnostics, yield_stmt.span, "void value context must fall through instead of yielding")
|
||||
value = invalid_hir_expr(checker, yield_stmt.span, id)
|
||||
value_type = types.INVALID
|
||||
ctx.problematic^ = true
|
||||
} else if is_runtime_type(checker, expected) {
|
||||
value = coerce_expr(checker, value, expected, yield_stmt.span)
|
||||
value_type = checker.module.exprs[value].type
|
||||
}
|
||||
@@ -9521,6 +9558,15 @@ build_value_loop :: proc(
|
||||
// The fall-through value initializes the slot before the loop (loop captures are
|
||||
// out of scope here), so the loop completing leaves it as the result.
|
||||
fall_value := build_expr(checker, fall_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
|
||||
if fall_value != hir.INVALID_EXPR && types.is_void(checker.module.exprs[fall_value].type) {
|
||||
for s in loop_block {
|
||||
append(body, s)
|
||||
}
|
||||
delete(loop_block, checker.allocator)
|
||||
id := source.add(checker.diagnostics, fall_stmt.span, "'yield' expression must produce a non-void value")
|
||||
ctx.problematic^ = true
|
||||
return invalid_hir_expr(checker, fall_stmt.span, id), types.INVALID
|
||||
}
|
||||
fall_value = resolve_loop_slot(ctx, &target, fall_value, checker.module.exprs[fall_value].type if fall_value != hir.INVALID_EXPR else types.INVALID, fall_stmt.span)
|
||||
if target.slot == hir.INVALID_LOCAL || fall_value == hir.INVALID_EXPR {
|
||||
for s in loop_block {
|
||||
|
||||
@@ -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")
|
||||
@@ -2463,12 +2469,19 @@ ct_exec_statements :: proc(
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid in a comptime value block")
|
||||
} else if statement.value_control_flow {
|
||||
flow, ok = ct_exec_statements(state, statement.body, true, depth+1)
|
||||
if ok && flow.kind == .Yield && types.is_void(state.values[flow.value].type) {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||
}
|
||||
} else {
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||
ok = expr_ok
|
||||
flow = expr_flow
|
||||
if ok && flow.kind == .Normal {
|
||||
flow = ct_flow(.Yield, value, statement.label)
|
||||
if types.is_void(state.values[value].type) {
|
||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||
} else {
|
||||
flow = ct_flow(.Yield, value, statement.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .If:
|
||||
|
||||
+41
-14
@@ -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)
|
||||
@@ -1282,14 +1302,11 @@ finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> sourc
|
||||
|
||||
parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := advance(parser)
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind == .Underscore {
|
||||
end := advance(parser)
|
||||
if current(parser).kind == .Newline || current(parser).kind == .Right_Brace || current(parser).kind == .Eof {
|
||||
id := ast.stmt_id(len(parser.module.statements))
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Return,
|
||||
span=span_from(start.span, end.span),
|
||||
name=end.symbol,
|
||||
span=start.span,
|
||||
expr=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -1321,12 +1338,10 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
return id
|
||||
}
|
||||
|
||||
// `yield <expr>` supplies the value of the enclosing value block. The checker
|
||||
// only accepts it as the final statement of a value block (a `{ ... }` on the
|
||||
// right of a declaration/assignment); it is the block analogue of `return`.
|
||||
// `yield <expr>` supplies a non-void value to an enclosing value construct.
|
||||
// The expression must start on the same line; `break` handles valueless exits.
|
||||
parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := advance(parser) // consume 'yield'
|
||||
skip_newlines(parser)
|
||||
// `yield :blk x` targets the loop labeled `blk`; a bare `yield x` targets
|
||||
// the directly-enclosing value block / if branch. No expression starts with
|
||||
// ':', so a leading colon is unambiguously a label.
|
||||
@@ -1335,9 +1350,21 @@ parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if name, name_ok := allow(parser, .Identifier); name_ok {
|
||||
label = name.symbol
|
||||
} else {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a loop label after ':'")
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a yield target label after ':'")
|
||||
}
|
||||
}
|
||||
if current(parser).kind == .Newline || current(parser).kind == .Right_Brace || current(parser).kind == .Eof {
|
||||
expr := invalid_expr(parser, start.span, "'yield' must produce a value")
|
||||
id := ast.stmt_id(len(parser.module.statements))
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Yield,
|
||||
span=start.span,
|
||||
label=label,
|
||||
expr=expr,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return id
|
||||
}
|
||||
if cf, is_cf := parse_value_control_flow(parser); is_cf {
|
||||
cf_span := parser.module.statements[cf].span
|
||||
body := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
||||
|
||||
+355
-107
@@ -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 {
|
||||
@@ -2289,7 +2447,7 @@ return_sink_and_unconsumed_values_have_distinct_hir :: proc(t: ^testing.T) {
|
||||
return 1
|
||||
}
|
||||
done func() void {
|
||||
return _
|
||||
return
|
||||
}
|
||||
main func() void {
|
||||
done()
|
||||
@@ -2327,6 +2485,96 @@ main func() void {
|
||||
testing.expect_value(t, hir_module.statements[main.body[2]].kind, hir.Stmt_Kind.Trap)
|
||||
}
|
||||
|
||||
@(test)
|
||||
bare_returns_and_strict_yields :: proc(t: ^testing.T) {
|
||||
text := `Failure :: enum { bad }
|
||||
noop func() void {}
|
||||
newline_return func() void {
|
||||
return
|
||||
}
|
||||
inline_return func() void { return }
|
||||
fallible_return func() void ! Failure { return }
|
||||
split_return func() i32 {
|
||||
return
|
||||
1
|
||||
}
|
||||
old_return func() void { return _ }
|
||||
missing_yield func() i32 {
|
||||
value :: {
|
||||
yield
|
||||
1
|
||||
}
|
||||
return value
|
||||
}
|
||||
missing_labeled_yield func() i32 {
|
||||
value :: block: {
|
||||
yield :block
|
||||
}
|
||||
return value
|
||||
}
|
||||
void_yield func() i32 {
|
||||
value :: { yield noop() }
|
||||
return value
|
||||
}
|
||||
sink_yield func() i32 {
|
||||
value :: { yield _ }
|
||||
return value
|
||||
}
|
||||
void_context func() void {
|
||||
fallible_return() catch |_| { yield 1 }
|
||||
}
|
||||
bad_comptime :: ${ yield noop() }
|
||||
main func() void {
|
||||
newline_return()
|
||||
inline_return()
|
||||
fallible_return() catch |_| {}
|
||||
_ = split_return()
|
||||
old_return()
|
||||
_ = missing_yield()
|
||||
_ = missing_labeled_yield()
|
||||
_ = void_yield()
|
||||
_ = sink_yield()
|
||||
void_context()
|
||||
}
|
||||
`
|
||||
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)
|
||||
|
||||
testing.expect_value(t, ast_module.statements[ast_module.functions[1].body[0]].expr, ast.INVALID_EXPR)
|
||||
testing.expect_value(t, ast_module.statements[ast_module.functions[2].body[0]].expr, ast.INVALID_EXPR)
|
||||
testing.expect_value(t, ast_module.statements[ast_module.functions[3].body[0]].expr, ast.INVALID_EXPR)
|
||||
testing.expect_value(t, len(ast_module.functions[4].body), 2)
|
||||
|
||||
missing_value := false
|
||||
non_void_function := false
|
||||
void_yields := 0
|
||||
sink_read := false
|
||||
void_context := false
|
||||
for diagnostic in diagnostics.items {
|
||||
missing_value = missing_value || strings.contains(diagnostic.message, "'yield' must produce a value")
|
||||
non_void_function = non_void_function || strings.contains(diagnostic.message, "non-void function must return a value")
|
||||
if strings.contains(diagnostic.message, "'yield' expression must produce a non-void value") {
|
||||
void_yields += 1
|
||||
}
|
||||
sink_read = sink_read || strings.contains(diagnostic.message, "'_' is a write-only sink and cannot be read")
|
||||
void_context = void_context || strings.contains(diagnostic.message, "void value context must fall through instead of yielding")
|
||||
}
|
||||
testing.expect(t, missing_value)
|
||||
testing.expect(t, non_void_function)
|
||||
testing.expect(t, void_yields >= 2)
|
||||
testing.expect(t, sink_read)
|
||||
testing.expect(t, void_context)
|
||||
}
|
||||
|
||||
@(test)
|
||||
unused_locals_and_params_warn_without_traps :: proc(t: ^testing.T) {
|
||||
text := `warn_only func(value i32, unused i32) i32 {
|
||||
@@ -6451,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}
|
||||
@@ -9579,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
|
||||
@@ -9748,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}
|
||||
@@ -9843,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}
|
||||
@@ -9896,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 }
|
||||
@@ -9925,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
|
||||
@@ -9936,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
|
||||
}
|
||||
@@ -9961,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)
|
||||
@@ -10006,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)
|
||||
@@ -10027,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)
|
||||
|
||||
@@ -23,8 +23,6 @@ _fail_allocator mem.Allocator :: mem.Allocator {
|
||||
vtable = &_fail_vtable,
|
||||
}
|
||||
|
||||
_noop func() void {}
|
||||
|
||||
run func() i32 ! mem.AllocError {
|
||||
values std.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||
defer arraylist.deinit(&values)
|
||||
@@ -65,7 +63,6 @@ run func() i32 ! mem.AllocError {
|
||||
failed_as_expected bool = false
|
||||
arraylist.append(&failed, 1) catch |_| {
|
||||
failed_as_expected = true
|
||||
yield _noop()
|
||||
}
|
||||
if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9
|
||||
arraylist.deinit(&failed)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -266,6 +266,19 @@ stmt_block_escape func() i32 {
|
||||
return hits # 4 + 1000 (defer) = 1004
|
||||
}
|
||||
|
||||
# A labeled block can also be exited through an ordinary nested block.
|
||||
stmt_block_nested func() i32 {
|
||||
hits i32 = 0
|
||||
outer: {
|
||||
{
|
||||
hits = 1
|
||||
break :outer
|
||||
}
|
||||
hits = 100 # skipped by break :outer
|
||||
}
|
||||
return hits
|
||||
}
|
||||
|
||||
# Item B: a `none` yielded before a concrete `yield :blk` that references a block local.
|
||||
lblock_local func() i32 {
|
||||
r :: blk: {
|
||||
@@ -319,6 +332,7 @@ main func() i32 {
|
||||
if (stmt_block(0) != 2) return 132
|
||||
if (stmt_block_escape() != 1004) return 133
|
||||
if (lblock_local() != 9) return 134
|
||||
if (stmt_block_nested() != 1) return 135
|
||||
|
||||
return 42
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,13 +25,13 @@ deinit func($T type, list @mut ArrayList(T)) void {
|
||||
|
||||
reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError {
|
||||
if minimum_capacity <= list.capacity {
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
@@ -48,18 +48,18 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
}
|
||||
list.items = grown.ptr[..length]
|
||||
list.capacity = new_capacity
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
list.items = list.items.ptr[..length + 1]
|
||||
list.items[length] = value
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
clear func($T type, list @mut ArrayList(T)) void {
|
||||
|
||||
+3
-3
@@ -71,12 +71,12 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
}
|
||||
offset += count
|
||||
}
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
_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)
|
||||
|
||||
@@ -44,7 +44,7 @@ _append func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void
|
||||
length = end - start,
|
||||
kind = kind,
|
||||
})
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
||||
@@ -92,7 +92,7 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
||||
}
|
||||
}
|
||||
try _append(tokens, .eof, cursor, cursor)
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
_kind_name func(kind Kind) *c_char {
|
||||
|
||||
@@ -25,13 +25,13 @@ deinit func($T type, list @mut ArrayList(T)) void {
|
||||
|
||||
reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError {
|
||||
if minimum_capacity <= list.capacity {
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
@@ -48,18 +48,18 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
}
|
||||
list.items = grown.ptr[..length]
|
||||
list.capacity = new_capacity
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
list.items = list.items.ptr[..length + 1]
|
||||
list.items[length] = value
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
clear func($T type, list @mut ArrayList(T)) void {
|
||||
|
||||
@@ -71,12 +71,12 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
}
|
||||
offset += count
|
||||
}
|
||||
return _
|
||||
return
|
||||
}
|
||||
|
||||
_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)
|
||||
|
||||
@@ -6,7 +6,7 @@ main func() void {
|
||||
|
||||
data :: mem.alloc(u8, allocator, 24) catch |_| {
|
||||
_ = c.printf("Failed to allocate memory\n")
|
||||
return _
|
||||
return
|
||||
}
|
||||
defer mem.free(u8, allocator, data)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -262,15 +262,13 @@ module.exports = grammar({
|
||||
|
||||
expression_statement: $ => $.expression,
|
||||
|
||||
return_statement: $ => seq(
|
||||
return_statement: $ => prec.right(seq(
|
||||
'return',
|
||||
repeat($._newline),
|
||||
field('value', $._value),
|
||||
),
|
||||
optional(field('value', $._value)),
|
||||
)),
|
||||
|
||||
yield_statement: $ => seq(
|
||||
'yield',
|
||||
repeat($._newline),
|
||||
optional(seq(':', field('label', $.identifier))),
|
||||
field('value', $._value),
|
||||
),
|
||||
@@ -385,6 +383,7 @@ module.exports = grammar({
|
||||
$.catch_expression,
|
||||
$.unary_expression,
|
||||
$.field_expression,
|
||||
$.intrinsic_call_expression,
|
||||
$.call_expression,
|
||||
$.index_expression,
|
||||
$.slice_expression,
|
||||
@@ -442,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)
|
||||
|
||||
@@ -1714,28 +1714,33 @@
|
||||
"name": "expression"
|
||||
},
|
||||
"return_statement": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "return"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_newline"
|
||||
"type": "PREC_RIGHT",
|
||||
"value": 0,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "return"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "value",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "value",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_value"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"yield_statement": {
|
||||
"type": "SEQ",
|
||||
@@ -1744,13 +1749,6 @@
|
||||
"type": "STRING",
|
||||
"value": "yield"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_newline"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
@@ -2698,6 +2696,10 @@
|
||||
"type": "SYMBOL",
|
||||
"name": "field_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "intrinsic_call_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "call_expression"
|
||||
@@ -3273,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,
|
||||
@@ -1667,7 +1707,7 @@
|
||||
"fields": {
|
||||
"value": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"required": false,
|
||||
"types": [
|
||||
{
|
||||
"type": "block",
|
||||
|
||||
+97498
-99456
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
|
||||
==================
|
||||
@@ -154,3 +182,56 @@ work func() i32 ! Failure {
|
||||
(return_statement
|
||||
(expression
|
||||
(integer)))))))
|
||||
|
||||
==================
|
||||
Bare return and value yield
|
||||
==================
|
||||
|
||||
done func() void {
|
||||
return
|
||||
}
|
||||
|
||||
inline func() void { return }
|
||||
|
||||
choose func() i32 {
|
||||
result :: { yield 1 }
|
||||
return result
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(function_declaration
|
||||
(identifier)
|
||||
(parameter_list)
|
||||
(type
|
||||
(builtin_type))
|
||||
(block
|
||||
(statement
|
||||
(return_statement))))
|
||||
(function_declaration
|
||||
(identifier)
|
||||
(parameter_list)
|
||||
(type
|
||||
(builtin_type))
|
||||
(block
|
||||
(statement
|
||||
(return_statement))))
|
||||
(function_declaration
|
||||
(identifier)
|
||||
(parameter_list)
|
||||
(type
|
||||
(builtin_type))
|
||||
(block
|
||||
(statement
|
||||
(constant_declaration
|
||||
(identifier)
|
||||
(block
|
||||
(statement
|
||||
(yield_statement
|
||||
(expression
|
||||
(integer)))))))
|
||||
(statement
|
||||
(return_statement
|
||||
(expression
|
||||
(identifier)))))))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
main func() void {
|
||||
_ = sizeof!(i32)
|
||||
# ^^^^^^ function.builtin
|
||||
# ^ operator
|
||||
_ = sizeof(i32)
|
||||
# ^^^^^^ function
|
||||
}
|
||||
Reference in New Issue
Block a user