Compare commits

..

105 Commits

Author SHA1 Message Date
hl-valdemar 378eb637c6 better escape code support 2026-08-11 21:18:57 +02:00
hl-valdemar c2343b54bb usize indexing only 2026-08-11 20:38:38 +02:00
hl-valdemar fa53ca2219 conditionally unwrapping while loops 2026-08-11 20:05:55 +02:00
hl-valdemar ba6052eef3 move hide behind @ and specify with : 2026-08-10 23:25:55 +02:00
hl-valdemar 5244bfbf1b improve error messages 2026-08-09 22:42:02 +02:00
hl-valdemar 8785912b73 file-local / package-local decls 2026-08-09 17:18:41 +02:00
hl-valdemar a7a32894f3 error inference in fallible functions 2026-08-09 16:18:10 +02:00
hl-valdemar 572ffe7d07 fallible main 2026-08-09 15:08:20 +02:00
hl-valdemar 6688822de2 struct field reordering (minimizing padding) 2026-08-09 14:12:39 +02:00
hl-valdemar 729488e702 structs as errors in fallibles 2026-08-08 22:49:55 +02:00
hl-valdemar e0f1d2d7cb improve int/float type resolution 2026-08-08 22:38:13 +02:00
hl-valdemar 081a3fd5df mutable decl syntax change 2026-08-05 21:49:12 +02:00
hl-valdemar 88b94197c9 better try/catch fallback 2026-08-05 20:55:00 +02:00
hl-valdemar aadce94b42 yield type check against yield target 2026-08-05 17:35:33 +02:00
hl-valdemar 84b88f6127 fix honey compiler blockers 2026-08-05 08:53:43 +02:00
hl-valdemar 9e79d6692b rename expand to inline 2026-08-02 21:14:49 +02:00
hl-valdemar c92723bc14 support shorthand enums in value-if 2026-08-02 19:45:30 +02:00
hl-valdemar f25f76adff refine distinct construction semantics 2026-08-02 19:14:59 +02:00
hl-valdemar b9526b5f06 disambiguate enum blocks and complete distinct type operations 2026-08-02 15:27:43 +02:00
hl-valdemar 91aa601464 fix(checker): infer return match calls 2026-08-01 23:59:11 +02:00
hl-valdemar f267e8c3cb package-private visibility 2026-08-01 23:22:14 +02:00
hl-valdemar 304880a9aa bug fix 2026-08-01 22:03:08 +02:00
hl-valdemar a8593c00f9 allow both proc and func in prototype 2026-07-25 00:03:12 +02:00
hl-valdemar 96275121be fix: bug hunt 2026-07-24 23:56:30 +02:00
hl-valdemar 8b50eb7606 whole-function comptime folding for zero-runtime value calls 2026-07-23 09:44:24 +02:00
hl-valdemar 21ff291788 fix comptime yield targeting 2026-07-22 12:12:44 +02:00
hl-valdemar ec5880e757 caller-context comptime preservation 2026-07-22 11:36:12 +02:00
hl-valdemar 07e89e23e1 array reflection 2026-07-22 11:13:46 +02:00
hl-valdemar 0d04925b3a remove undefined global constraint 2026-07-22 02:46:29 +02:00
hl-valdemar 63f000dd42 weak contextual string-literal inference 2026-07-22 02:32:47 +02:00
hl-valdemar c389c19a81 stop compilation after checker errors 2026-07-22 02:13:57 +02:00
hl-valdemar 8e153fa84e refine returned match errors 2026-07-22 02:07:51 +02:00
hl-valdemar 2c2a310e6d update tree-sitter revision 2026-07-22 01:06:17 +02:00
hl-valdemar 09571ffeb9 sync stdlib from honey 2026-07-22 00:54:43 +02:00
hl-valdemar 9c6215776e add constcast and immutable free 2026-07-22 00:44:47 +02:00
hl-valdemar 5f343ad2d3 unify catch fallback value sources 2026-07-22 00:39:01 +02:00
hl-valdemar 17508ff751 add noreturn and unreachable 2026-07-22 00:30:13 +02:00
hl-valdemar 402871ef7a rename none to null 2026-07-22 00:07:42 +02:00
hl-valdemar 1619ea98a3 preserve active comptime state in local type declarations 2026-07-21 22:58:44 +02:00
hl-valdemar dd00af7731 memcopy! and memset! intrinsics 2026-07-21 21:57:27 +02:00
hl-valdemar 297f2e3078 struct field declared type inference 2026-07-20 16:51:50 +02:00
hl-valdemar ad4802a270 preserve global resolved type properly 2026-07-20 16:11:36 +02:00
hl-valdemar 05aa7d084a make record fields resolve type-factory calls before runtime validation 2026-07-20 16:05:28 +02:00
hl-valdemar ec36b6b861 fix optional-presence comparison 2026-07-20 15:46:04 +02:00
hl-valdemar deab47e75e finite-domain enum return analysis 2026-07-20 15:13:26 +02:00
hl-valdemar 10abba54a5 comptime-state propagation through generated struct field resolution 2026-07-20 08:38:52 +02:00
hl-valdemar 59bbb197e0 struct type construction 2026-07-19 23:38:10 +02:00
hl-valdemar 944648fce6 tree-sitter rev-pin update 2026-07-19 02:07:06 +02:00
hl-valdemar 2e09864adf tree-sitter grammar update 2026-07-19 02:00:40 +02:00
hl-valdemar 95f90cc306 close some gaps in the type system 2026-07-19 01:44:06 +02:00
hl-valdemar c7e3162ecb bitwise operations 2026-07-19 00:45:51 +02:00
hl-valdemar f9448363e4 grouped comptime param update 2026-07-18 23:57:17 +02:00
hl-valdemar 9eb7476522 default struct fields 2026-07-18 16:33:31 +02:00
hl-valdemar e889a99e55 bare func declaration identities (comptime) 2026-07-18 14:29:03 +02:00
hl-valdemar 85693e57e1 function values as comptime params 2026-07-18 10:31:17 +02:00
hl-valdemar 9f433af724 preserve application and error inference in test builds 2026-07-18 00:46:05 +02:00
hl-valdemar ef91f13e7b unsigned integer constraint (uint) 2026-07-18 00:17:13 +02:00
hl-valdemar b09787029d native test framework 2026-07-18 00:12:12 +02:00
hl-valdemar cedc63b28b upgrade enum discriminants 2026-07-17 21:28:14 +02:00
hl-valdemar 866e28adb8 file io 2026-07-17 20:52:53 +02:00
hl-valdemar 97f1c06057 comptime expandable match statements 2026-07-17 14:40:18 +02:00
hl-valdemar 1f25e6cd1d better comptime match-statement support 2026-07-16 09:01:44 +02:00
hl-valdemar 3cc750b3b2 braceless one-statement for-loops 2026-07-15 23:31:58 +02:00
hl-valdemar 7de0b7f268 diagnostics upgrade 2026-07-15 23:12:04 +02:00
hl-valdemar c4fa8e930f richer formatting 2026-07-15 21:10:54 +02:00
hl-valdemar 1165cfb7c0 fix comptime specialization, implement io.print 2026-07-15 19:52:14 +02:00
hl-valdemar 0b2055d64b reflection foundation, tuples, debug.print 2026-07-15 19:50:24 +02:00
hl-valdemar 267947e79d replace leading _ for private symbols with keyword hide 2026-07-14 21:22:04 +02:00
hl-valdemar 5157cf3bcc rename intrinsics 2026-07-14 20:15:52 +02:00
hl-valdemar 471896b48a favor return over return _ (void return); newline/closing terminates 2026-07-14 19:11:58 +02:00
hl-valdemar eac5b32738 update todo 2026-07-14 00:03:17 +02:00
hl-valdemar 0b0b00a050 bug fixes 2026-07-13 23:56:55 +02:00
hl-valdemar 0eeacc2e37 scalar family constraints for struct fields 2026-07-13 23:15:22 +02:00
hl-valdemar 6de4d9f9f3 errdefer and try/defer fix 2026-07-13 19:57:23 +02:00
hl-valdemar 9e75549d02 tree-sitter description 2026-07-13 18:04:07 +02:00
hl-valdemar 5a958d9bfd allow keywords as values in enums (and tagged unions) 2026-07-13 18:04:07 +02:00
hl-valdemar de56dc7315 makefile 2026-07-13 18:04:07 +02:00
hl-valdemar 6455df52b4 add ArrayList alias to std 2026-07-13 18:04:07 +02:00
hl-valdemar 288df082e2 io interface (first pass) 2026-07-13 18:04:07 +02:00
hl-valdemar 2ed333c70d enforce integer division via explicit builtins 2026-07-13 18:04:07 +02:00
hl-valdemar a4d0fb1e26 tiny lexer test (testbed) 2026-07-13 18:04:07 +02:00
hl-valdemar 0a424ec6d2 mem.eql (stdlib) 2026-07-13 10:22:40 +02:00
hl-valdemar c914083102 enforce leading comptime params 2026-07-12 20:31:41 +02:00
hl-valdemar 2dea7711f2 inferred leading comptime params 2026-07-12 19:00:06 +02:00
hl-valdemar 0706188b98 stlib arraylist 2026-07-12 16:59:46 +02:00
hl-valdemar b0c716537e bug fixes 2026-07-12 13:04:20 +02:00
hl-valdemar cff9e9500f split up mem_allocator examples 2026-07-12 00:48:13 +02:00
hl-valdemar fbbbfa454c typed alloc 2026-07-12 00:31:12 +02:00
hl-valdemar 220b1c6e82 _ prefixed top-level symbols now file-local 2026-07-11 23:37:56 +02:00
hl-valdemar 6dd6b7ff54 add realloc (and update c_allocator) 2026-07-11 23:26:20 +02:00
hl-valdemar 90869dcb4d gitignore .DS_Store 2026-07-08 22:09:26 +02:00
hl-valdemar 2cda024614 allocation related primitives 2026-07-08 22:08:02 +02:00
hl-valdemar 5e18df9bc1 warning diagnostics for unused locals 2026-07-08 20:21:15 +02:00
hl-valdemar f171a6579d function literals (non-capturing) 2026-07-07 17:24:25 +02:00
hl-valdemar 95c61311ca allocator interface (first pass) 2026-07-07 16:20:51 +02:00
hl-valdemar 7ce3917c15 toolchain version command 2026-07-06 19:36:55 +02:00
hl-valdemar e7f69ffb5a add new and init to toolchain 2026-07-06 19:36:55 +02:00
hl-valdemar cdde49e68b build system (first pass) 2026-07-05 00:09:39 +02:00
hl-valdemar 4ebe9c90e9 comptime storage and function values 2026-07-03 23:35:00 +02:00
hl-valdemar ee41a54e41 runtime mutable global state 2026-07-03 22:40:21 +02:00
hl-valdemar adf142736c more comptime eval 2026-07-03 18:18:45 +02:00
hl-valdemar e00a4e929a comptime eval 2026-07-03 18:18:45 +02:00
hl-valdemar b94687c30a comptime type params 2026-07-02 22:24:57 +02:00
hl-valdemar a98b26446d array size inference from value 2026-07-02 20:20:31 +02:00
hl-valdemar 7cda126924 comptime value-params 2026-07-02 20:10:02 +02:00
138 changed files with 38727 additions and 2311 deletions
+1
View File
@@ -1 +1,2 @@
/build/
.DS_Store
+236 -31
View File
@@ -8,84 +8,289 @@ roadmap and milestone history.
### source, declarations, and packages
- newline-terminated statements and `#` comments
- immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
- immutable package globals, function-local mutable locals, and mutable local declarations initialized with `undefined`
- immutable `name :: value` / `name Type :: value` bindings and mutable `name := value` / `name Type := value` bindings
- `=` is assignment, including `_ = value` sinks; keyed record initializers and named struct field defaults also use `=`
- immutable package globals, mutable runtime globals, function-local mutable locals, and mutable declarations initialized with `undefined`
- package-level functions, globals, native type declarations, and `Name :: alias T`
- directory packages with merged declarations
- file-local relative imports, import aliases, and qualified member access
- transparent declaration aliases with `Name :: alias package.Member`; functions/type factories,
named types, and globals retain their original declaration or storage identity
- bare `@hide` and explicit `@hide:package` make a named top-level declaration package-local;
`@hide:file` makes it file-local, and any qualifier may precede its declaration on a separate
line; the qualifier words remain valid identifiers, declarations are public by default, and imports are always file-local
- relative `.h` imports as synthetic C header package namespaces
- native `name test { ... }` declarations with fallible-void results inferred from `testing.Error`
and errors propagated by `try`, plus anonymous
transitive `test import "..."` discovery used only by test builds
- root `main` validation with trap executable recovery for missing or unusable entry points
### scalar, aggregate, and pointer types
- exact-width integers, `isize`, `usize`, `f32`, `f64`, `bool`, `void`, and contextual `int`, `float`, and `range` constraints
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, `noreturn`, and `anyopaque`; `noreturn` is a bottom type valid as a native function result and coerces to any expected value type; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
- 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)`
- arrays `[N]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`
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic/bitwise expressions, and typed compile-time evaluation for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar casts through keywords or transparent aliases, such as `i32(x)`, `c_float(x)`, or `StringId(x)`
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `T`
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
- pointer-to-array `.len`, indexing, slicing, `.ptr` on slices and pointers-to-arrays, implicit address-taking for array-variable slices, and pointer/slice sentinel weakening
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
- `ptrcast!(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
- unsafe `constcast!(value)` for restoring mutability to pointers, optional pointers, and slices without changing their child type or shape
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, supporting `\\`, `\"`, `\n`, `\r`, `\t`, `\0`, and `\xNN` escapes, 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
- nominal distinct types with exact backing construction, native enums with optional explicit integer backing, contextual enum literals, and imported C enums as target-backed integer aliases
- source-order native structs, defined/opaque `c_struct`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
- optionals with `null`, `orelse`, postfix `?`, conditional `if`/`while` unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
- nominal distinct types with explicit scalar backing conversion during construction and explicit scalar backing extraction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
- compiler-reordered native structs with fields laid out by decreasing alignment (declaration order breaks ties and remains the reflection/diagnostic order), opaque nominal records with `Name :: opaque`, complete source-order `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
- fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
- bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error
#### distinct types
`Name :: distinct T` creates a nominal identity and reuses `T`'s runtime representation. When
`T` is a concrete numeric scalar, construction first applies the corresponding explicit scalar
cast, so `UserID(index)` is sufficient for `UserID :: distinct u32` even when `index` is `usize`.
There is still no implicit conversion in either direction. Construction with a non-scalar or
distinct immediate backing requires that exact backing type. An explicit scalar cast extracts one
layer: `u32(id)` works for `UserID`, while nested distinct values must be peeled one declared layer
at a time.
Scalar-backed distinct values support the operations of their representation while preserving the
nominal result type: checked integer `+`, `-`, `*`, unary `-`, bitwise operators, shifts,
comparisons, and compound assignments; float arithmetic, unary `-`, comparisons, and compound
assignments; and boolean equality/inequality. Integer literals and float literals are contextual.
Typed backing values and separate distinct identities remain incompatible in ordinary operations;
an explicit constructor is required to cross that boundary. Distinct integers require an explicit
`usize` cast for indices and slice bounds; `minval!` / `maxval!` return the distinct type.
Runtime and comptime behavior match.
`typeinfo!(Distinct).backing` reports the immediate declared backing. Standard formatting peels
distinct layers recursively, so all scalar format verbs behave like the final scalar backing.
#### native record constraint fields
A direct `int`, `uint`, `float`, or `range` field in a named native struct or union is a
program-wide constraint, not per-value polymorphism. Before record layout, all reachable keyed
constructors, field assignments, and concrete uses of field reads contribute demands and the field
resolves once to one concrete runtime type. Compatible scalar demands widen normally. Integer
literals remain provisional until inference settles, so a later `usize` use can resolve an `int`
field to `usize`; otherwise literal-only `int` fields use the widest smallest-signed type required,
literal-only `uint` fields use the widest smallest-unsigned type required, and literal-only `float`
fields use `f64`.
An undemanded field or incompatible demands are errors. This inference applies only to direct
fields of named native records. `c_struct` fields, nested constraints such as `[]int` / `[]uint`, and fields in
anonymous generated records still require concrete runtime types.
#### keyword member names
Reserved keywords are valid native enum members and tagged-union variants when used in an
unambiguous member context:
```bro
TokenKind :: enum {
if
else
return
}
Token :: union(TokenKind) {
if i32
else void
return i32
}
conditional func() TokenKind { return TokenKind.if }
fallback func() TokenKind { return .else }
token func() Token { return Token{ if = 1 } }
```
Keyword variants also work with field access and `.variant` match patterns; `.else:` remains
distinct from the `else:` catch-all arm. No escaping syntax is required. Keywords remain reserved
for ordinary declarations, struct fields, untagged-union fields, and anonymous payload-struct
fields. `_` is not a keyword member name.
### expressions and control flow
- checked integer `+ - * /`, unary `-`, divide-by-zero traps, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
- assignments and compound assignments `+= -= *= /=` with single evaluation of complex lvalues
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
- 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
- bare block scopes and `defer`, including LIFO flushing on fall-through, `return`, `break`, and `continue`
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `yield :label value`
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
- Zig-style integer bitwise complement `~`, binary `&`, `|`, `xor`, shifts `<<` / `>>`, and saturating left shift `<<|`; postfix `^` remains pointer dereference
- assignments and compound assignments `+= -= *= /= &= |= xor= <<= >>= <<|=` with single evaluation of complex lvalues; `/=` is float-only and `xor=` is contiguous
- field access through struct values and pointers, exact `usize` indices and slice bounds, and contextual integer constants in those positions
- boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call
- `while` loops with conditional unwrap captures and guards plus 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; `inline for` specializes a comptime aggregate into one checked body per element
- `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
- always-trapping `unreachable`, a `noreturn` expression that diagnoses use during comptime evaluation and terminates the current runtime path
- bare void `return`, same-line `return value`, value blocks, value `if` with implicit single-expression branches, 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
- a final `inline |value|:` enum arm or `inline |payload[, tag]|:` tagged-union arm generates one specialized arm for each variant not covered earlier; enum values and optional tags are comptime-known, while union payloads keep their concrete variant type
- fallible `try` and uniform `catch [|e|] value_source` fallbacks; captures work with ordinary expressions, value blocks, and value-producing `if` / loops / `match`
- direct `return match ...` and `yield match ...` value-control-flow operands
#### bitwise operations
Bitwise operands must be concrete integers. `~` preserves its operand type. `&`, `xor`, and `|`
use the ordinary common-integer widening rules; incompatible fixed integer families remain errors.
Shifts preserve the left operand type and require a concrete unsigned count. `>>` is arithmetic for
signed integers and logical for unsigned integers.
Ordinary `<<` and `>>` reject compile-time-known counts at least as large as the left type's bit
width and trap for such runtime counts. `<<` discards shifted-out bits. Saturating `<<|` permits any
unsigned count: zero remains zero, unsigned nonzero values clamp to the type maximum, and signed
values clamp to the minimum or maximum according to their sign.
Binary precedence, from tightest to loosest, is:
```text
* /
+ -
<< >> <<|
& xor |
== != < > <= >=
and
or
```
Each level is left-associative. Because `|` also delimits `if`, `while`, and `for` captures, a
bitwise-OR header expression must be parenthesized before a capture list, for example
`while (flags | mask) |value| { ... }`.
#### 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 intrinsics:
| Builtin | Result |
| --- | --- |
| `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
divtrunc!(a, b) * b + rem!(a, b) == a
divfloor!(a, b) * b + mod!(a, b) == a
```
Negative operands distinguish the operations:
```bro
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 `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.
Only these six division bang calls select integer-division behavior. Bare calls such as
`divfloor(a, b)` and qualified calls such as `math.divfloor(a, b)` resolve to ordinary functions.
#### typed memory operations
`memcopy!(destination, source)` and `memset!(destination, value)` are available at runtime and
comptime. A destination must be a mutable slice or mutable pointer-to-array. A `memcopy!` source
may be a slice or pointer-to-array; many-item pointers must first be sliced. Array pointers are
treated as regions containing their explicit logical elements, including a sentinel only when it
is part of that array region.
`memcopy!` requires the same element type after alias resolution and the same element count. Its
non-empty regions must not overlap. Comptime calls diagnose unequal lengths and overlap; runtime
calls trap for either condition or if the element count cannot be converted to a byte count.
Zero-sized elements still require equal counts. Empty copies are no-ops and may name the same
region.
`memset!` coerces `value` to the destination element type. Use `memset!(destination, 0)` to zero a
region; there is no separate `memzero!`, and `memset!` does not promise secure zeroing. Copying or
filling with `undefined` transfers undefined state without reading it. Each operand is evaluated
exactly once. Bare functions named `memcopy` or `memset` remain ordinary user functions.
### functions, C interop, and linking
- demand-monomorphized Brolang and C-ABI functions
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
- later comptime value parameters may depend on earlier type parameters, as in `factory func($T type, $default T) type`
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
- direct bodyful value calls with no runtime parameters, including parameterless and all-`$` functions, are evaluated at comptime when their resolved result can materialize; otherwise they retain their zero-argument runtime specialization, while a reached `compile_error!` remains a diagnostic
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, exact type `==`/`!=`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
- `struct_type!(layout, names, types, defaults)` constructs a nominal record type from comptime fixed arrays or tuples and is valid in every type position; layout is `.auto` or `.c`, bare `null` means a required field, and `some!(null)` installs an optional `null` default
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(null)` differs from outer `null`
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
- anonymous keyed records use `{x = 1, name = "bro"}`; without context their declaration-ordered names and inferred value types form a structurally interned record type, while a record context applies that type's coercions and field defaults; `{}` remains an empty tuple without context and constructs an empty contextual record when a record is expected
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
- `tag!(value)` reads a tagged union's active discriminant and folds when the value is comptime-known; `tagname!(enum_value)` requires a comptime-known enum value and returns its immutable declaration name
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
- bare `func(...) R` and `c_func(...) R` values are comptime-only declaration identities; arrays, native records, optionals, and tagged unions containing one are also comptime-only and cannot enter runtime storage, ordinary ABI parameters/results, runtime globals, or C-layout records
- native function pointer values and types use `@func(...) R`, fallible `@func(...) R ! E`, and optional `?@func(...) R`; bare native identities implicitly materialize compatible pointers when a runtime pointer context requires one, but pointers never convert back to bare identities
- statically known bare identities and comptime-known pointers lower calls directly; native indirect calls remain available for runtime-selected non-variadic pointers
- Apple Silicon C ABI lowering for scalars, pointers, fixed-signature plain records/unions, small aggregates, homogeneous float aggregates, and indirect aggregate returns
- imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, and pointers to opaque records
- imported C typedefs, scalar constants, enum constants, fixed arrays, complete plain structs/unions, C `void*` as nullable `anyopaque` pointers, and pointers to opaque records
- imported external C object variables, including mutable variables and immutable object globals
- object-like scalar and plain record/union macro constants
- supported static inline C functions through generated external wrappers
- C function pointer types, imported nullable callback typedefs, concrete `c_func` callback values, and postfix calls through non-null function pointers
- `brolang translate-c <header.h>` for native `.bro` bindings from supported C declarations
- C function pointer types, imported nullable callback typedefs, bare `c_func` identities with one-way pointer materialization, and postfix calls through non-null function pointers
- `brolang translate-c <header.h>... [--output-dir <dir>]` for native `.bro` bindings from supported C declarations, with package-wide declaration deduplication when writing multiple headers
- `brolang --translate-c stdio.h` for offline bindings from Zig-bundled standard C headers
- ordered linking of additional C sources, objects, archives, library paths, and libraries through compiler CLI options
### standard packages
- `std/mem/heap` v1 byte allocation over libc: `alloc(size usize) ?*mut u8` and `free(ptr ?*mut u8)`
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
- entry points are either `main func() ...` or `main func(init process.Init) ...`; their success channel is `void`, `i32`, or `int` and may have an error channel; an unhandled entry error exits with status 1. `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
- `std/testing` supplies fallible `expect`, expected-first `expect_equal`, and exact compile-time `expect_type`; direct calls through
an alias of exactly `@std/testing` receive compiler-injected source locations
### compiler behavior
- error-tolerant compilation with diagnostics and runtime traps where recovery is possible
- lazy semantic checking of demanded function specializations
- static, eager runtime, and deferred problematic globals with cycle diagnostics
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
- demand-driven LLVM declarations for referenced foreign functions
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point obtains the hidden system I/O provider and constructs the init value
- `brolang test [root]` reuses `build.bro`, discovers only explicit test-import edges, skips the
application entry point, and runs tests sequentially while continuing after assertion failures
- replaceable dynamically loaded libclang C-import backend
- C-header import caching by canonical path, target, include paths, and defines
## PLANNED / DEFERRED
- comptime polymorphism
- tuples and native Brolang variadic functions
- exporting Brolang functions to C and broader target-specific C ABI lowering
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
- typed heap allocation, allocator parameters, arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
- recursive type factories, reflection payloads beyond records/enums, and type-producing unions/enums
- 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
- distinct-type backing operators and reverse explicit conversions
- string concatenation operator
+9
View File
@@ -0,0 +1,9 @@
.PHONY: build install
build:
mkdir -p build
odin build . -out:build/brolang
install: build
install -d "$(HOME)/.brolang/bin"
install -m 755 build/brolang "$(HOME)/.brolang/bin/bro"
+124 -11
View File
@@ -9,6 +9,20 @@ odin build . -out:build/brolang
./build/prototype
```
Programs may receive the system I/O capability explicitly. Standard-stream
helpers bind the provider, handle, and callback; `main func() ...` remains valid.
```bro
io :: import "@std/io"
process :: import "@std/process"
main func(init process.Init) void {
io.print(io.stdout(init.io), "hello {s} {d}\n", {"bro", 37}) catch |_| {
return
}
}
```
Bodyless `c_func` declarations bind exact external symbols and require concrete
types. C primitives use atomic target-dependent names and remain semantically
distinct from exact-width Brolang primitives:
@@ -30,6 +44,58 @@ invocation in command-line order:
`-l<name>`. `--c-include-path <dir>` and `--c-define <name[=value]>` configure
C preprocessing.
Instead of passing these on the command line, a project can describe its build
in Brolang itself. `brolang new <project>` creates a project with local `std`
and `ffi` copies; `brolang init` does the same for the current directory without
overwriting existing files. `brolang build [root]` reads a `config` constant
from exactly one of `root/build.bro` or `root/build.hon` and compiles the
program package it names. Without `root`, it searches the current directory
and parents for the nearest build file. Build outputs are written to
`root/build/<name>`.
```bro
b :: import "@std/build"
config :: b.BuildConfig{
name = "manual",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &["examples/build/manual/native.c"],
}
```
`name` is a plain executable name, and `source` is the program package relative
to the build file. The list fields map to the matching C options (`libraries`
`-l`, `lib_paths``-L`, `includes``-I`, `defines` → C defines, `links`
linker inputs) and, like those flags, their paths are relative to the invocation
directory. Lists take the address of an array literal; empty lists are written
`&[]`. See `examples/build/` for runnable projects.
Projects can declare tests directly and run them with `brolang test [root]`.
The command reads the same build file—exactly one of `build.bro` or
`build.hon`—writes `build/<name>-test`, and reuses its C link inputs,
libraries, include paths, and defines.
```bro
math :: import "../math"
testing :: import "@std/testing"
test import "../math"
addition test {
try testing.expect(math.add(20, 22) == 42)
try testing.expect_equal(42, math.add(20, 22))
}
```
`test import` discovers tests transitively without creating a namespace;
calling package code still requires an ordinary import. Ordinary imports do
not discover dependency tests. Assertions report their source location, a
failure ends only the current test, and the runner continues with the suite.
Relative `.h` imports create synthetic package namespaces backed by libclang:
```bro
@@ -42,14 +108,20 @@ main func() void {
Header imports expose supported external functions, typedefs, C scalars, fixed
arrays, complete plain structs and unions, function pointer typedefs, and
pointers to opaque records. Plain records can be constructed with keyed
pointers to opaque records. C `void*` imports as nullable `anyopaque` pointers.
Plain records can be constructed with keyed
literals, accessed by field, and passed or returned by value through fixed C
signatures on `aarch64-macos`. Unsupported or incomplete records remain
pointer-only. Header imports never add linker inputs; implementations must still
be supplied explicitly with the C-prefixed linking options. Set
`BROLANG_LIBCLANG_PATH` when libclang is not installed in a standard location.
For offline bindings, `brolang --translate-c stdio.h` resolves standard C
headers through the Zig libc headers used by the backend.
headers through the Zig libc headers used by the backend. Multiple headers can
be generated into one deduplicated package:
```sh
brolang --translate-c stdio.h stdlib.h unistd.h fcntl.h errno.h --output-dir ffi/c
```
```bro
native :: import "../include/native.h"
@@ -74,6 +146,20 @@ call_mapper func(mapper native.Imported_Mapper) c_int {
}
```
Native Brolang function pointer values use `@func(...) R`, with fallible
channels written on the result:
```bro
call func(callback @func(value i32) i32, value i32) i32 {
return callback(value)
}
```
Bare `func(...) R` and `c_func(...) R` values are comptime-only declaration
identities. They implicitly materialize compatible pointers in runtime pointer
contexts; pointers do not convert back to bare identities. Aggregates containing
bare identities are likewise comptime-only.
Bodyless manual and imported C functions may be variadic:
```bro
@@ -104,43 +190,70 @@ package loader -> per-file lexer/parser/AST -> checker/HIR -> lower/IR -> opt ->
```
Source diagnostics do not block executable generation. When recovery is
possible, invalid code lowers to runtime diagnostic traps and the compiler
returns status `1`. Infrastructure or backend failures return status `2`.
possible, errors lower to runtime diagnostic traps; warnings do not trap. Any
source diagnostic makes the compiler return status `1`. Infrastructure or
backend failures return status `2`.
Top-level function bodies are semantically checked lazily when a concrete
specialization is demanded.
Every immediate `.bro` file in the input directory belongs to the root
Every immediate `.bro` or `.hon` file in the input directory belongs to the root
package. Imports are relative directory paths and are local to the file that
declares them:
declares them. Imports beginning with `@` resolve from the project root:
```bro
import "../math"
other_math :: import "../math"
mem :: import "@std/mem"
value :: math.sum(other_math.value, 1)
```
Top-level declarations are public by default. Prefix a declaration with bare `@hide`
to make it package-local, or spell the scope explicitly with `@hide:package` or
`@hide:file`. The qualifier is contextual, so `hide`, `package`, and `file` remain
available as identifiers. Imports are always file-local and cannot use visibility
qualifiers or be re-exported:
```bro
@hide
shared_helper func() i32 { return 42 }
@hide:file
implementation_detail func() i32 { return shared_helper() }
```
Current prototype features:
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
- `#` comments
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
- Immutable inferred/typed `::` bindings, mutable inferred/typed `:=` 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`, and defined or opaque `c_struct`
- 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
- Contextual integer constants and compile-time folding of addition and unary negation trees
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
- Scalar-backed nominal `distinct` types with same-identity runtime/comptime operators, explicit backing extraction casts, integer bounds, reflection, and recursive standard formatting
- Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
- Qualified imported globals and functions with package-aware symbol mangling
- Demand-monomorphized Brolang and C-ABI functions
- Recursively stable comptime values—including booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities—may be interleaved with runtime parameters, are erased from the ABI, and specialize from explicit arguments, declaration identity, or exact inference provenance
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`/`errdefer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
- Comptime-only native and C function identities (`func(...) R`, `c_func(...) R`) with structural comptime-only propagation through aggregates
- Native function pointer values and types (`@func(...) R`, `@func(...) R ! E`, `?@func(...) R`) with implicit bare-to-pointer materialization
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
- Bodyless concrete C function declarations with exact external symbol names
- 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
- Static, eager runtime, and deferred problematic globals
- Float-only `/` plus explicit `divtrunc!`, `divfloor!`, `divexact!`, `divceil!`, `rem!`, and `mod!` scalar intrinsics
- Runtime/comptime typed `memcopy!` and `memset!` over slices and pointers-to-arrays, with checked lengths and overlap
- Static, eager runtime, mutable runtime, and deferred problematic globals
- Runtime diagnostics followed by `llvm.trap`
See [LANGUAGE.md](LANGUAGE.md) for the concise implemented and planned language
@@ -149,5 +262,5 @@ feature ledger, and [TODO.md](TODO.md) for the implementation roadmap.
Compiler exit statuses:
- `0`: executable produced without source diagnostics
- `1`: executable produced with source diagnostics and embedded traps
- `1`: executable produced with source diagnostics; errors may embed traps, warnings do not
- `2`: executable could not be produced
+515 -125
View File
@@ -11,8 +11,8 @@
1. interop type foundation (implemented)
- unsigned integers, floats, and target-dependent c scalar types
- atomic `c_*` primitive types remain distinct until target-aware lowering
- `c_func` and pointer-only `c_struct`; `c` remains an ordinary identifier
- keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`)
- `c_func`, complete `c_struct`, and pointer-only `opaque`; `c` remains an ordinary identifier
- keep binding mutability (`::` / `:=`) separate from element or pointee mutability (`mut`)
- arrays and indexing
- `[N]T`: array with `N` logical elements
- `[N;S]T`: array with `N` logical elements followed by sentinel `S`
@@ -28,9 +28,9 @@
- character literals
- optionals with trapping unwrap and fallback operations
- native structs with compiler-controlled layout
- pointer-only `c_struct` support with target c layout
- complete `c_struct` support with target c layout and pointer-only `opaque` records
- `Some :: c_struct { ... }`: defined c-layout struct
- `Some :: c_struct`: opaque c-layout struct
- `Some :: opaque`: incomplete nominal record
- passing c structs by value was deferred until milestone 4.1
2. restricted c header imports (implemented)
@@ -91,13 +91,13 @@
- operators: `and`, `or`, `!`
- lazy evaluation / short-circuit evaluation
- if statements (implemented). example: `if condition { ... } else if { ... } else { ... }`
- conditions must be `bool`; block-scoped locals with shadowing across blocks
- conditions must be `bool`; block-scoped locals do not escape their blocks
- lowered through new `Label` / `Br` / `Cond_Br` IR opcodes (alloca-backed locals, no phi nodes)
- conditional unwrapping for optionals (`?T`) (implemented): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
- conditional unwrapping for optionals (`?T`) (implemented): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `null`
- single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if`
- `|` lexes as a new `Pipe` token; the `.If` reuses AST `name` / HIR `local` to carry the binding (no new statement kind)
- new `Optional_Is_Some` / `Optional_Value` IR opcodes (the `Unwrap` presence-test + extract, minus the trap)
- conditional unwrapping with guard clause (implemented): `if val |v : v >= 10| { ... } else { ... }` - enter the then-block when `val` is not `none` and the guard is true
- conditional unwrapping with guard clause (implemented): `if val |v : v >= 10| { ... } else { ... }` - enter the then-block when `val` is not `null` and the guard is true
- multi-unwrap (implemented; see section below)
- while loops (implemented; operates on boolean conditions). examples:
- `while condition { ... }` - iterate while the condition is true
@@ -116,7 +116,7 @@
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
6. compound assignment: `+=`, `-=`, `*=`, `/=` (implemented)
6. compound assignment: `+=`, `-=`, `*=`, `/=` (implemented; division semantics superseded by milestone 32)
- added the binary arithmetic operators `-`, `*`, `/` (previously only `+` existed); `*`/`/`
bind tighter than `+`/`-`, and prefix `-` (negation) is unchanged
- compound assignments preserve the target, operator, and right-hand side explicitly through
@@ -124,10 +124,9 @@
operation, and stores through that address
- side-effecting index, field-base, and dereference expressions are evaluated once in
left-to-right order
- integer arithmetic traps on overflow (`Sub_Checked`/`Mul_Checked` via the LLVM
`.with.overflow` intrinsics) and integer `/` traps on divide-by-zero and `INT_MIN / -1`;
floats follow IEEE (`fadd`/`fsub`/`fmul`/`fdiv`, no trap)
- constant folding (global initializers) covers `-`, `*`, `/` alongside `+`
- integer `+`, `-`, and `*` trap on overflow; milestone 32 later restricted `/` and `/=` to
floats and introduced the explicit integer/float division family
- constant folding (global initializers) covers the arithmetic family
7. enums (native and c interop) (implemented; see below)
- native enums are nominal value types with integer runtime representations
@@ -140,10 +139,13 @@
8. distinct types (implemented; see below)
- nominal declarations preserve identity across packages and reuse the backing runtime representation
- construction uses `Type(value)` with exactly one value of the exact backing type
- no implicit conversion to or from the backing type
- backing-type operators and reverse explicit conversions remain deferred
- concrete runtime backing types are supported; unresolved, `int`, `void`, function, and opaque backings are rejected
- construction of a numeric scalar-backed distinct type applies the backing's explicit scalar
cast before wrapping; non-scalar and nested-distinct backings still require the exact immediate type
- no implicit conversion crosses the nominal boundary or mixes separate distinct declarations
- explicit scalar casts extract one declared distinct layer at a time
- scalar-backed values support matching runtime/comptime arithmetic, bitwise, shift, comparison,
compound-assignment, bounds, indexing, reflection, and standard formatting behavior
- unresolved, `int`, `void`, function, and opaque backings remain invalid runtime declarations
9. allow pointer field access pass-through (implemented)
- having a pointer (`ptr`) to a struct, we should allow access through `ptr.field` as opposed to mandating `ptr^.field`
@@ -164,18 +166,25 @@
- added a native type-alias declaration `Name :: alias T` (parser/lexer/token surface; the
`types.define_alias` / `.Alias` machinery already existed) so C typedefs and callback
typedefs round-trip
- emits functions, complete/opaque structs (collapsing `typedef struct {...} Foo`),
- emits functions, complete structs and opaque records (collapsing `typedef struct {...} Foo`),
typedef aliases, and scalar/aggregate/enum-member constants
- C unions, external variables, static-inline functions, and unsupported declarations have
no hand-writable spelling and are emitted as `# unsupported in bindings:` comments
(functions that reference an un-spellable union therefore keep a dangling reference)
11.1. opaque, anyopaque, and pointer casts (implemented; v1)
- `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`
- `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):
- allow mutable local declarations with `undefined`
- allow mutable local and global declarations with `undefined`
- undefined values are assigned a poison value (0xaa...)
- allows for something like:
```
a int = undefined
a int := undefined
if (condition) {
a = 42
} else {
@@ -183,7 +192,7 @@
}
```
- disallow: `b :: undefined` since assigning undefined to something that can't change defeats the purpose
- disallow assigning `undefined` after declaration; use optionals and `none` for values that intentionally move back to an empty state
- disallow assigning `undefined` after declaration; use optionals and `null` for values that intentionally move back to an empty state
13. introduce `float` and `range` type constraints (the `int` family generalized) (implemented)
- `float` resolves a local binding to any float scalar (`f32`/`f64`) via static analysis;
@@ -246,15 +255,15 @@
c :: b + 3 # c is constrained to `i32`
```
16. for if statements, allow `if (cond) one-line statement` or `if some_func(some_arg) one-line statement` (instead of forcing either `if (cond) { block }` or `if cond { block }`) (implemented)
- if statements without a bracketed body must wrap the condition in parentheses UNLESS it's a function call
16. allow brace-less single-statement `if` and `for` bodies (implemented)
- brace-less bodies must wrap the condition or iterable in parentheses UNLESS it's a function call
- brace-less single-statement bodies apply to the then-body, the `else`-body, and the
unwrap/guard forms (`if v |x| stmt`); each branch is independent, so braced and
unwrap/guard forms (`if (v) |x| stmt`); each branch is independent, so braced and
brace-less branches mix freely
- the parenthesize-or-call rule constrains only the then-branch condition; `else` and the
unwrap `|...|` already delimit, so they need no parentheses
- the brace-less statement may sit on the line after the condition
- parser-only change (`parse_branch_body` in `compiler/parser/parser.odin`): a brace-less
- the parenthesize-or-call rule constrains `if` then-branch conditions (including unwraps)
and `for` iterables; `else` bodies have no preceding expression to constrain
- the brace-less statement may sit on the following line
- parser-only change (`parse_control_body` in `compiler/parser/parser.odin`): a brace-less
body is just a 1-element statement slice, so the checker and codegen are unchanged
17. multi-line strings (implemented; see below)
@@ -294,6 +303,9 @@
- `defer <stmt>` runs the statement when the enclosing block scope exits, in reverse
(LIFO) order, on every exit path: fall-through, `return`, `break`, `continue`. The
deferred statement may be a block (`defer { ... }`)
- `errdefer [|error|] <stmt>` is the fallible-function counterpart: it runs only when
an explicit or `try`-propagated error exits its active block scope, can capture the
widened enclosing error, and stays interleaved with ordinary defers in LIFO order
- bare block statements `{ ... }` were added as the enabling feature: a `{ ... }`
introduces a nested scope (locals are name-scoped to it; defers inside it fire at the
closing brace). A leading `{` is unambiguous since struct literals are postfix only
@@ -304,16 +316,14 @@
the innermost loop body; fall-through flushes the current block's own defers. Deferring
a `return`/`break`/`continue`/`defer`, a `return` inside a `defer`, or a `break`/
`continue` that would escape a `defer` are all rejected
- implemented entirely in lexer/parser/checker (new `Keyword_Defer`; `Block`/`Defer` AST
kinds reusing `body`/`update`; `parse_block_statement`/`parse_defer`). No HIR opcode:
a bare block is built and spliced inline, and a deferred statement is built once at the
`defer` site and its hir replayed at each exit, so lowering/codegen are unchanged
- cleanup is statically expanded with no runtime registration stack; `try` carries its
active error-exit cleanup into lowering so propagation cannot bypass either defer form
20. add `yield` statement (implemented; first pass — value blocks only; see below)
- a `{ ... }` on the right of a declaration or assignment is a *value block*: its final
statement must be `yield <expr>`, which supplies the block's value (the block analogue
of `return`). Supported: `x :: { ...; yield v }` (untyped — the local takes the yield's
natural type), `x T = { ... }` (coerces to `T`), and `target = { ... }` (coerces to the
natural type), `x T := { ... }` (coerces to `T`), and `target = { ... }` (coerces to the
target's type, including complex targets like `a[i] = { ... }`)
- the yielded value is captured *before* the block's defers run (a defer that mutates a
block local can't change what is yielded), reusing the `return` spill-to-temp pattern
@@ -344,9 +354,9 @@
`target = if …` are supported too
- value-loop: a labeled body `for/while … blk: { … }` whose early exits are
`yield :blk x` and whose body ends in an unlabeled fall-through `yield` (the value
when the loop completes). The `{T, none}` yields resolve the result to `?T`
(a pure-AST `none`-scan picks optionality; the first concrete yield fixes the element
type). E.g. `active_ent_idx :: for 0..10 |i| blk: { if (cond) yield :blk i; yield none }`
when the loop completes). The `{T, null}` yields resolve the result to `?T`
(a pure-AST `null`-scan picks optionality; the first concrete yield fixes the element
type). E.g. `active_ent_idx :: for 0..10 |i| blk: { if (cond) yield :blk i; yield null }`
resolves to `?usize`
- new `blk:` / `yield :blk` label surface adds one `label` field to the AST `Stmt`; no new
token (`blk:` is `Identifier Colon`, `:blk` is `Colon Identifier`). The parser carries a
@@ -363,7 +373,7 @@
loop. The TODO "BAD" loops (unlabeled yield from inside an `if`, an unbound labeled loop)
fall out of these naturally
- follow-ups: a branch that early-`return`s instead of yielding, unwrap-`if` as a value
source, and `none`-before-concrete typing in untyped loops are done in 20.6; labeled value
source, and `null`-before-concrete typing in untyped loops are done in 20.6; labeled value
blocks and `yield`/`break` to an outer loop are done in 20.7
20.6 value if/loop follow-ups (implemented; checker-only)
@@ -376,16 +386,16 @@
guard), each branch assigning the slot; the HIR `.If` carries the unwraps, which the existing
lowering already handles. (The simple "unwrap or fallback" case is just `orelse` —
`name :: opt orelse d` — already a plain expression.)
- untyped value loops pre-type their element from the first concrete (non-`none`) yield
- untyped value loops pre-type their element from the first concrete (non-`null`) yield
regardless of source order (a capture-scoped probe build, `value_loop_element_type`), so a
`none` yielded before any concrete value still resolves the result to `?T`
`null` yielded before any concrete value still resolves the result to `?T`
- still checker-only; no HIR/lowering change
20.7 labels — value blocks + yield/break to an outer loop (implemented; first lowering change)
- `x :: blk: { …; yield :blk v }` — a labeled value *block* (the disambiguated form of "an
if/loop at the end of a block"; an unlabeled trailing if/loop stays ambiguous and is not a
value source). `yield :blk v` exits the block with a value; every path must yield. Carries
the same `{T, none}` → `?T` typing, defer-capture, and reassignment forms as value loops
the same `{T, null}` → `?T` typing, defer-capture, and reassignment forms as value loops
- `yield :outer v` to an enclosing (non-innermost) value loop/block, plus plain `break :L` /
`continue :L` to an enclosing labeled loop
- a label now names a first-class exit target: `label` added to the HIR `Stmt` (on
@@ -399,12 +409,11 @@
`.Block` break target; not a loop, so unlabeled `break`/`continue` and `continue :blk` skip
it). The checker tracks a parallel `loop_is_loop` stack so labeled `break` reaches a loop or
block while `continue` and unlabeled `break`/`continue` reach only the innermost loop
- untyped block `none`-before-concrete typing now builds the block's leading (yield-free)
- untyped block `null`-before-concrete typing now builds the block's leading (yield-free)
statements first (a throwaway probe), so a first concrete `yield :blk` that references a
block local still resolves the result to `?T`
- deferred (`// ponytail:`): the same `none`-before-concrete typing in an untyped block (or
loop) whose concrete yield references a local declared *past* the first yield (annotate);
same-label loop/block shadowing resolves innermost-wins
- deferred (`// ponytail:`): the same `null`-before-concrete typing in an untyped block (or
loop) whose concrete yield references a local declared *past* the first yield (annotate)
21. unions and tagged unions (implemented; first pass — native untagged unions only; see below)
- inspired by zig
@@ -458,7 +467,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
@@ -621,30 +630,378 @@
parameters
25. dynamic heap allocation (implemented; v1)
- `std/mem/heap` is a tiny relative-importable package over libc `malloc`/`free`
- `heap.alloc(size usize) ?*mut u8` returns nullable mutable byte memory; callers use existing optional unwraps
- `heap.free(ptr ?*mut u8) void` forwards to C `free`, including `none` / null
- typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
- `std/mem` exposes a plain-data `Allocator` contract with `?@mut anyopaque` context and `alloc`, `realloc`, and `free` `@func` pointers
- `mem.c_allocator` is the libc-backed allocator; `mem.alloc(mem.c_allocator, size, alignment)` returns nullable mutable byte memory
- `mem.free(mem.c_allocator, ptr, size, alignment)` frees with the same allocator; `malloc` handles default-aligned requests and `posix_memalign` handles larger power-of-two alignments
- `mem.realloc` preserves alignment and the original allocation on failure; zero size frees, and over-aligned blocks use allocate/copy/free
- typed allocation helpers, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
26. import from project "root" (implemented)
- imports beginning with `@` resolve from the compiler process cwd / project root
- `heap :: import "@std/mem/heap"` works from any package depth without `../../../` path math
- imports beginning with `@` resolve from the project root
- `brolang build [root]` uses `root` as the project root; without `root`, it searches cwd and parents for `build.bro`
- direct `brolang <package-dir> -o <out>` defaults the project root to the package dir; `--root <dir>` overrides it
27. comptime polymorphism (zig inspired)
27. comptime integer value parameters (implemented; v1)
- `$N` marks an integer comptime parameter in a normal `func` signature:
`make_array func($N usize) [N]u8`
- callers pass a compile-time integer expression; the value specializes the function
and is omitted from the runtime ABI
- inside the specialization, `N` is visible as an immutable compile-time integer in
array counts, types, and body expressions
- v1 intentionally supports integer values only; no comptime branch pruning or
user-function execution
28. comptime execution
27.5 comptime type parameters (implemented; v1)
- `$T type` marks an explicit comptime type parameter in a normal `func` signature:
`max func($T type, a, b T) T`
- callers pass the type explicitly as an ordinary comptime argument (`max(i32, a, b)`);
the type argument specializes the function and is omitted from the runtime ABI
- inside the specialization, `T` is visible in parameter, result, local, array, pointer,
slice, and fallible type syntax
- v1 intentionally keeps `type` contextual to comptime parameter declarations; no
inferred type parameters, first-class type values, or comptime execution
29. brolang build system (requires comptime execution)
27.6 comptime-evaluable constants/functions (implemented)
- `$expr` forces comptime evaluation of an expression:
`x :: $32`, `n :: $sum(1, 2)`, and `res :: ${ ... }`
- constant contexts such as array counts and comptime value arguments implicitly
require comptime evaluation; ordinary immutable bindings remain ordinary bindings
- ordinary `func` calls are comptime-evaluable when reached from a comptime context;
do not add a separate `$sum func(...)` declaration form
- v1 evaluator supported integer literals/arithmetic, boolean conditions, immutable
locals, `return`, `if`/`else`, comptime blocks, and direct calls to other evaluable
brolang functions
- broader typed execution is milestone 27.7
27.7 broader Zig-style comptime execution (implemented; v1)
- `compiler/checker/comptime.odin` owns checker-local evaluator state, typed
comptime values, execution, and HIR materialization; `checker.odin` keeps type
checking, inference, specialization, and build orchestration
- typed `$` values cover bools, integers, floats, strings, arrays, structs, tagged
unions, enums, optionals, and fallibles
- supports mutable comptime locals/assignment, `if`, `while`, `for`,
`break`/`continue`, `defer`, `match`, value blocks/`yield`, direct calls to
bodyful Brolang functions, and `try`/`catch`
- successful `$` results materialize back into ordinary HIR expressions so lowering
and LLVM stay unchanged
- evaluation uses a fixed `100_000` step quota
- immutable locals/globals with comptime-known initializers may feed comptime
evaluation; runtime-dependent values remain invalid in comptime contexts
- runtime-only behavior is rejected in comptime: external/bodyless `c_func`,
writable globals, and materializing comptime storage pointers/slices as runtime memory
- milestone 39 extends specialization keys from integer/type/string values to
recursively stable values while keeping runtime ABI erasure unchanged
27.8 source-defined mutable runtime globals (implemented)
- allow mutable global declarations in Brolang source for process-global runtime
state, matching the writable-global support already needed for imported C globals
- require an initializer and infer or explicitly declare a concrete runtime storage type;
constraints (`int`/`float`/`range`) and inferred array counts resolve through the existing
inference fixpoint
- emit source-defined mutable globals as writable globals, not constants
- allow `undefined` initializers for runtime storage initialized explicitly by a function
- allow assignment, address-taking, field/index mutation, and pointer passing under
the same mutability rules as other writable locations
- keep mutable globals invalid in comptime evaluation; `$global_var` and writes from
comptime execution must remain runtime-dependent errors
- define initialization order and cycle behavior by reusing the existing global
initializer dependency/cycle system where possible
- reject user-visible name shadowing across imports, named types, globals, functions,
params, locals, comptime params, captures, and labels; `_` remains reusable
27.9 comptime storage and function values (implemented; practical v1)
- comptime locals, params, and immutable globals can own evaluator storage cells
addressable through places instead of compiler-owned memory
- comptime supports address/deref, mutable pointer and slice mutation, field/index
places, slicing, `.len`, `.ptr`, pointer captures, and pointer-param aliasing
- comptime storage pointers/slices cannot materialize as runtime memory; escaped
dead storage is rejected
- bare concrete non-comptime function names are comptime-only declaration identities;
native function pointer types use `@func(...) R` and fallible `@func(...) R ! E`
- bare identities implicitly materialize compatible runtime pointers, never the reverse;
aggregates containing bare identities remain comptime-only
- comptime-known native/bodyful `c_func` values can be called; bodyless/imported
callbacks remain runtime-only
- native function pointers are non-variadic v1; C variadic function pointers stay
under `*c_func(...) R`
28. brolang build system (implemented; v0 shipped)
- `brolang build [root]` reads a declarative `config` constant from
`root/build.bro` (importing `@std/build`'s `BuildConfig`) and compiles the
program package it names. The config is read from the checked HIR — build.bro
is never lowered — so it is literal-only.
- `brolang new <project>` and `brolang init` create the default project layout
with local `std`, `ffi`, `vendor`, `source`, and `build.bro`
- enabled `&<array literal>` (Zig's `&.{...}`): the literal is promoted to an
anonymous global whose address decays to a slice, so list fields like
`libraries = &["raylib"]` work; empty lists are `&[]`
- deferred: build graph / steps / caching, multiple artifacts, and computed paths
(needs string building); milestone 44 later removed explicit `&[]` build-config fields
29. fix bugs (implemented)
- 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
of internal `<type N>` ids
- aliases resolve transparently in value contexts, including composed enum/union sums
- final open-constant defaults feed one last inference fixpoint before stale
specializations are pruned
30. Zig-style type factories and basic `std/arraylist` (implemented; v1)
- comptime-only functions may return `type`; anonymous `struct { ... }` expressions and
factory calls such as `ArrayList(i32)` resolve to cached nominal concrete types
- factory parameters use the existing explicit `$T type` / integer comptime parameters;
normal comptime control flow and helper factory calls are supported
- type-factory calls work in signatures, nested types, struct literals, and type builtins;
runtime materialization and recursive specializations are diagnosed
- `std/mem` adds typed `empty` and failure-preserving `realloc`, including overflow,
zero-count, zero-sized-type, and alignment handling
- `std/arraylist.ArrayList(T)` exposes `items`, `capacity`, and `allocator`, with fallible
reserve/append, roughly 1.5x growth from 8, clear-without-free, and reusable deinit
- deferred: recursive factories, reflection, type-producing
unions/enums, pop/insert/remove/shrink/clone container operations
31. generic container ergonomics (spike completed; no language change)
- type factories already provide the important half of generic structs: `ArrayList(T)` is a
cached, concrete nominal type whose layout contains `T`; no type information is carried at
runtime
- the verbosity comes from free functions repeating explicit comptime type arguments
(`deinit(i32, &values)`, `append(i32, &values, value)`), not from a missing generic data model
- do not add functions inside structs, implicit `Self`, associated lookup, or per-value runtime
type metadata for this; those features would add a second namespace/member model without
improving layout or specialization
- the smallest fitting feature is call-local inference of omitted comptime type arguments:
```
values ArrayList(i32) := arraylist.init(mem.c_allocator)
defer arraylist.deinit(&values)
try arraylist.append(&values, 42)
```
`T` comes from the expected result for `init` and from the concrete receiver argument for the
other calls
- keep functions package-scoped and keep the explicit form valid; this preserves simple name
resolution and gives ambiguous calls an escape hatch
31.5. inferred comptime parameters (implemented)
- comptime parameters may appear anywhere and are erased while preserving runtime parameter order
- a native call may omit `$T type`, integer, or immutable byte-string comptime arguments when every
value is uniquely recoverable from runtime argument types and/or the immediate expected result
- inference structurally matches direct type parameters, pointers/slices/arrays/optionals/
fallibles/functions, direct array counts, and canonical generated type-factory provenance;
forwarding/non-invertible factories keep the explicit spelling
- concrete evidence is exact; contextual numeric constants are weak evidence and are rebuilt with
the resolved parameter type before ordinary coercion
- `_` explicitly leaves one comptime argument to inference; exactly one complete argument mapping
must succeed, with missing and ambiguous mappings diagnosed
- the existing specialization/HIR/LLVM ABI is unchanged; `std/mem` and `std/arraylist` now use the
inferred form where their arguments or result provide enough information
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 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 `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 `divtrunc!`
33. explicit I/O provider (implemented)
- `main` may take one canonical `@std/process Init`; parameterless entry points remain valid
- the compiler supplies a `hide system` macOS provider and constructs `Init` in the external C wrapper
- readers and writers bind provider context, a generalized handle, and a direct callback
- standard-stream helpers and existing-file operations use the injected provider; files retain it
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
- the system provider uses unbuffered POSIX file descriptors, retries interrupted open/read/write,
and allocates nothing
34. package declaration aliases and root `std.ArrayList` (implemented)
- bare qualified aliases use `Name :: alias package.Member` without adding a keyword
- functions/type factories, named types, and globals transparently retain the target identity;
mutable global aliases therefore share the original storage
- aliases resolve transitively at load time, consume their file-local import, preserve explicit
`hide` visibility, and diagnose missing, hidden, unavailable, ambiguous, cyclic, or
conflicting targets
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
36. transitive package namespaces (spike completed; no language change)
- imports remain file-local implementation details, including explicitly named imports such as
`rl :: import "@vendor/raylib"`; naming an import only chooses its local qualifier
- packages expose declarations, not their imports, so imported namespaces never become public or
transitively reachable package members
- callers import each package they use directly; subdirectory layout does not create namespaces
- this keeps package lookup shallow and deterministic and avoids overloading import aliases with
declaration visibility
36.5. explicit `hide` package-local declarations (implemented)
- `hide name ...` gives any named top-level function, global, native/C record, union, enum,
opaque/distinct type, or declaration/type alias package-local visibility across sibling files
- declarations remain public by default; a leading underscore is an ordinary identifier and `_`
remains the write-only sink
- hidden declarations occupy the package namespace, collide with sibling declarations of the
same name, and remain absent from imported package namespaces
- `hide` is reserved for named top-level declarations and is rejected on imports, locals,
parameters, fields, and anonymous declarations
37. tuples, reflection, process init, and printing (implemented)
- tuples are unnamed-field structs with structural anonymous values, nominal named declarations,
brace literals, numeric fields, and no runtime metadata
- `@std/meta`, `typeinfo!`, `field!`, `compile_error!`, specialization-time branches, and semantic
`inline for` use checker-owned persistent compile-time values for aggregate-first reflection and
heterogeneous static expansion; inline-loop control is recursively resolved at comptime
- interleaved comptime parameters use semantic candidate resolution, immutable byte values specialize
by contents, and all comptime parameters remain erased from the runtime ABI
- `io.print(writer, format, args)` validates and expands `{s}` / `{d}` formatting at comptime,
is implemented in ordinary `@std/io` code, performs no allocation or runtime parsing, and
propagates the first write error
- `debug.print` reuses formatting through an independent stderr backend, ignores failures, and adds
no newline
- entrypoint validation accepts only parameterless `main` or canonical `main(process.Init)`, validates
the hidden `@std/io.system` provider, and injects the provider through the generated C entrypoint;
native variadics and additional startup data remain deferred
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
user functions (implemented)
39. stable comptime values and richer formatting (implemented)
- comptime parameters accept booleans, integers, floats, types, immutable bytes,
enums, fixed arrays, records/tuples, optionals, and tagged unions recursively
- canonical specialization keys include deterministic type identity, exact float bits,
byte contents, ordered aggregate children, optional state, and active union variants;
FNV-1a fingerprints accelerate lookup while exact key comparison handles collisions
- equal structural values reuse specializations and stable emitted names, distinct values
specialize separately, aggregate inference uses exact type-factory provenance, and all
comptime parameters remain erased from the runtime ABI
- undefined values, pointers, general slices, fallibles, ranges, and untagged
unions diagnose that they have no stable comptime identity
- `@std/meta.TypeInfo.enum` carries declaration-ordered `EnumInfo.fields`, enabling enum
formatting through `field!` without runtime reflection metadata
- `io.print` and `debug.print` retain their APIs and expand `{}`, `{s}`, `{d}`, `{b}`,
`{o}`, `{x}`, `{X}`, `{c}`, and `{e}` at comptime; `{{` and `}}` remain escapes
- integer output uses one base-aware 65-byte stack buffer; float output uses fixed-buffer
libc `snprintf` with 32-bit and 64-bit general/scientific precision and propagates failure
40. compile-time `inline` (implemented)
- expansion-oriented `expand for` is strictly renamed to `inline for`; `expand` is no longer a keyword
- final inlined enum and tagged-union match arms generate checker-local specialized arms only
for variants not covered by preceding explicit arms
- generated enum values and union tags are static bindings, heterogeneous payloads retain their
concrete types, and `void` payloads support value and pointer captures without runtime storage
- `tag!` reads or folds a tagged union discriminant, while `tagname!` turns a comptime-known enum
value into its immutable field-name string
41. native test framework (implemented; v1)
- `name test { ... }` declares an implicit `void ! testing.Error` test omitted from executable builds
- anonymous `test import` edges discover dependency tests transitively; ordinary imports remain
separate namespaces and never discover tests
- direct `@std/testing` `expect` and expected-first `expect_equal` calls inject their source locations
- `brolang test` reuses `build.bro`, runs tests sequentially, continues after assertion failures,
prints a summary, and returns failure when a test fails or traps
- filtering, skipping, fixtures, snapshots, parallelism, isolation, allocators, and more assertion
families remain deferred
42. unsigned integer constraint (implemented)
- `uint` is the unsigned subset constraint while `int` remains the whole integer family
- literal-only values choose the smallest fitting `u8`, `u16`, `u32`, or `u64`
- native unsigned scalars and target-classified unsigned C scalars satisfy the constraint
- `isize` and `usize` remain concrete pointer-sized types
43. comptime function parameters (implemented)
- bare native and C function identities, function literals, and comptime-only aggregates
containing them specialize by declaration identity rather than runtime address
- repeated declarations reuse specializations, distinct declarations specialize separately, and
function-valued parameters remain erased from the runtime ABI
- statically known callback invocations lower to direct calls; runtime-selected function pointers
remain indirect, and bodyless C declarations remain runtime-only during comptime execution
- runtime pointer contexts implicitly materialize a bare identity; pointer-to-identity conversion is
rejected, and bare-containing aggregates cannot enter runtime storage or ABI/C layouts
44. struct field defaults on declaration (implemented)
- named native structs accept `field T = expression`; keyed construction evaluates defaults
for omitted fields while explicit initializers override them
- defaults resolve names in the declaration file, participate in record constraint inference,
and work during runtime and comptime construction
- `@std/build.BuildConfig` uses defaults for optional list fields
- fields without defaults remain required; C-layout records, unions, tuples, and anonymous
generated structs do not accept defaults
45. typed memory intrinsics (implemented)
- `memcopy!` copies equal-length, non-overlapping slices or pointers-to-arrays with identical
element types; comptime diagnoses invalid regions and runtime guards length, size, and overlap
- `memset!` fills a mutable region with a value coerced to its element type; bytes lower to LLVM
memset and wider values use typed stores
- both operations evaluate operands once, preserve undefined state without observing it, and work
identically during comptime evaluation; zeroing is `memset!(destination, 0)`
- `std/mem` aligned reallocation uses `memcopy!`
46. fix `EnumFieldStruct` in `std/meta` (implemented)
- comptime local declarations resolve type syntax in the active interpreter state, preserving match captures
- `EnumFieldStruct` sizes its working arrays directly from the comptime `.enum |info|` capture
47. rename optional `none` to `null` (implemented)
48. add `noreturn` and `unreachable` (implemented)
- `noreturn` is the native bottom type, permitted as a function result but rejected for storage,
parameters, record fields, and the C ABI
- `noreturn` expressions coerce to any expected value type and terminate path analysis
- `unreachable` always traps at runtime and reports an error during comptime evaluation
49. unify catch fallback value sources (implemented)
- `expr catch [|error|] value_source` uses the same ordinary expression, value block, and
value-producing control-flow forms with or without an error capture
- void fallthrough and diverging `noreturn` fallbacks remain valid
50. add `constcast!` and immutable deallocation (implemented)
- `constcast!` restores mutability only for pointers, optional pointers, and slices while
preserving child type, pointer kind, optionality, length, and sentinel shape
- `std/mem.free` accepts immutable slices and restores mutability only at the allocator boundary
## A word on unchecked casts
For casts that bypass safety checks, Honey provides builtin functions:
| Builtin | Purpose | Traps when... |
| -- | -- | -- |
| `truncate(x, T)` | Keep low bits, discard rest | Never |
| `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) |
| `ptrcast!(T, p)` | Change a pointer's child type while preserving its shape | Invalid child or non-pointer operand (compile error) |
| `constcast!(p)` | Restore pointer or slice mutability | Non-pointer/slice operand (compile error) |
```honey
# truncation
a: u32 = 0xDEADBEEF
b := truncate(a, u8) # b == 0xEF (low byte)
# bit reinterpretation
n: i32 = -1
m := bitcast(n, u32) # m == 0xFFFFFFFF (same bits)
f: f32 = 3.14
bits := bitcast(f, u32) # IEEE 754 representation
# pointer casts
buf *u8 := get_buffer()
ints *u32 := ptrcast!(u32, buf) # element type change, same pointer shape
writable *mut u8 := constcast!(buf) # explicit unsafe mutability restoration
```
## A word on multi-unwrap
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is none, subsequent expressions are not evaluated.
Unwrap multiple optionals with `and`. This **short-circuits**: if the first optional is null, subsequent expressions are not evaluated.
```
name: ?[]u8 = get_name()
age: ?u8 = get_age()
if name and age |n, a| {
# both n and a are guaranteed non-none here
# both n and a are guaranteed non-null here
print("{s} is {d} years old", n, a)
}
```
@@ -673,7 +1030,7 @@ The `and` in multi-unwrap short-circuits left-to-right:
```
if get_name() and get_hat() |n, h| {
# get_hat() is only called if get_name() returned non-none
# get_hat() is only called if get_name() returned non-null
}
```
@@ -704,16 +1061,30 @@ For-loop captures are immutable and scoped to the loop body. Sequence index capt
## A word on distinct types
Distinct types are considered distinct from their backing type. They do not implicitly coerce to their backing type.
Distinct declarations are nominal even when they share a backing type. A constructor for a
numeric scalar-backed distinct type first performs the backing's explicit scalar cast, while
implicit conversion remains forbidden in either direction. Explicit scalar casts extract one
layer:
```
# distinct type
```bro
UserID :: distinct u32
# instantiate distinct type
my_id UserID :: UserID(42) # value must have the exact backing type
OuterID :: distinct UserID
index usize := 42
id UserID :: UserID(index)
raw u32 :: u32(id)
outer OuterID :: OuterID(id)
inner UserID :: UserID(outer)
```
Scalar-backed distinct values retain their nominal type across the operations supported by the
backing scalar. Integer forms support checked arithmetic, bitwise operations, shifts, comparisons,
compound assignments, indexing, slicing, and `minval!` / `maxval!`; float forms support arithmetic
and comparisons; boolean forms support equality and inequality. Separate distinct identities and
typed backing operands never mix implicitly or in ordinary operations; crossing between numeric
representations requires an explicit constructor or scalar cast. Runtime and
comptime rules are identical. Reflection reports the immediate backing, while standard formatting
recursively follows nested distinct backings to the final scalar.
## A word on enums
```
@@ -850,7 +1221,7 @@ data :: {
}
# match arms
label []u8 = match p {
label []u8 := match p {
.high: "HIGH", # single expression: implicit yield
.low: {
log("low priority")
@@ -859,7 +1230,7 @@ label []u8 = match p {
}
# catch handlers (planned; block form deferred in milestone 23 v1)
data []u8 = read(path) catch |e| {
data []u8 := read(path) catch |e| {
log(e)
yield fallback_data # block: explicit yield
}
@@ -880,7 +1251,7 @@ result :: if a {
}
# yielding to a variable
result int = if a {
result int := if a {
yield 1
} else if b {
yield 2
@@ -902,7 +1273,7 @@ Yielding is also possible from loops with the same constraint.
# get active entity
active_ent_idx :: for 0..10 |i| blk: {
if is_active(some_entity, i) yield :blk i
yield none # fall-through: no active ent was found (this should imply a return type matching both the index value and `none`, meaning it should resolve to an optional in this case)
yield null # fall-through: no active ent was found (this should imply a return type matching both the index value and `null`, meaning it should resolve to an optional in this case)
# note that in this case, we have to use the `blk` label to yield from the correct scope.
# otherwise, the yield should return directly from the if-statement's scope (which would be incorrect in this case).
@@ -911,13 +1282,13 @@ active_ent_idx :: for 0..10 |i| blk: {
# BAD: yield returned from if-statement, but no name binds it: should miscompile similar to unused return values from functions.
active_ent_idx :: for 0..10 |i| {
if is_active(some_entity, i) yield i # bad
yield none
yield null
}
# BAD: likewise for loops
for 0..10 |i| blk: { # bad, no name binds returned value
if is_active(some_entity, i) yield :blk i
yield none
yield null
}
```
@@ -1110,7 +1481,7 @@ Fallible functions use ordinary `return` for both channels. If the returned expr
```
parse_section func(p: @mut Parser) void ! ParseError {
start_line Line = p.line
start_line Line := p.line
p.advance()
# ... parsing logic ...
@@ -1125,7 +1496,7 @@ Since errors are just union values, you can also construct them separately:
```
# Construct error value (it's just a union)
e ParseError = .timeout{500}
e ParseError := .timeout{500}
# Return it via error channel later
return e
@@ -1221,10 +1592,30 @@ data :: read_file(path) catch |e| match e {
| `yield :label value` | Provide value from labeled block |
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
## A word on comptime
```
make_array func($N usize) [N]u8 { ... } # implemented: integer comptime value params
max func($T type, a, b T) T { ... } # implemented: comptime type params
x :: $32 # implemented: force comptime expression evaluation
n :: $sum(1, 2) # implemented: ordinary functions can run at comptime
res :: ${ yield 4 } # implemented: comptime value block
p :: $Point { x = 1, y = 2 } # implemented: typed aggregate comptime values
total :: $sum_loop(4) # implemented: mutable locals/loops/defer/match/try/catch
```
## A word on memory allocation
(NOTE THAT SYNTAX MAY NOT MATCH BROLANG EXACTLY AND SHOULD BE TAKEN WITH A GRAIN OF SALT - INSPIRATION ONLY)
(FURTHER, EXAMPLES ASSUME ARGUMENTS WITH DEFAULT VALUES AND COMPTIME POLYMORPHISM IN THE FORM OF GENERIC TYPE PARAMETERS - MONOMORPHISED)
Current v1 is intentionally byte-oriented and plain data:
```
mem :: import "@std/mem"
bytes := mem.alloc(mem.c_allocator, 128, 1)
defer mem.free(mem.c_allocator, bytes, 128, 1)
```
Typed allocation helpers, arenas, pools, build-mode heap policy, and escaping-allocation diagnostics are future work. Older examples below are design sketches where noted, not committed syntax.
Memory allocation in Brolang is designed to be **explicit but not verbose**. We reject the dogma that global state is inherently evil — allocators are a cross-cutting concern that nearly every function needs, making them a perfect candidate for sensible defaults.
@@ -1243,28 +1634,28 @@ Memory allocation in Brolang is designed to be **explicit but not verbose**. We
└─────────────────────────────────────────────────────────────┘
```
### The Default Heap Allocator
### The Default C Allocator
Brolang provides a **thread-local global heap allocator** that is:
Brolang provides a libc-backed allocator value:
* Determined at compile time by build mode
* Available as `mem.c_allocator`
* Passed explicitly to `mem.alloc`, `mem.realloc`, and `mem.free`
* **Immutable at runtime** — cannot be reconfigured
```
import "std/mem/heap"
mem :: import "@std/mem"
process func(input []u8) u64 {
# heap used for internal temporary work — does not escape
temp := heap.alloc(u8, size: input.len * 2)
defer heap.free(temp)
temp ?*mut u8 := mem.alloc(mem.c_allocator, input.len * 2, 1)
defer mem.free(mem.c_allocator, temp, input.len * 2, 1)
# ... work with temp ...
return compute_hash(temp) # only the result escapes, not the allocation
return compute_hash(temp)
}
```
The behavior of `heap` depends on build mode:
Future build modes can add other allocator values without changing the allocator contract:
| Build Mode | Allocator Behavior |
| -- | -- |
@@ -1272,7 +1663,7 @@ The behavior of `heap` depends on build mode:
| Release | Fast allocator, zero overhead |
| ReleaseSafe | Bounds-checking allocator |
This is configured at compile time. You cannot change which allocator `heap` uses at runtime. This is intentional — it prevents bugs where memory allocated with one allocator is freed with another.
Allocator policy is configured at compile time. You cannot change which allocator value an allocation used after the fact. This is intentional — it prevents bugs where memory allocated with one allocator is freed with another.
### The Escaping Allocation Rule
@@ -1281,42 +1672,41 @@ This is configured at compile time. You cannot change which allocator `heap` use
This rule makes ownership transfer visible at the function signature level. The caller never needs to read the function's implementation to know whether heap cleanup is involved:
```
import "std/mem"
import "std/mem/heap"
mem :: import "@std/mem"
# Allocation escapes via return value — requires allocator
duplicate func(input []u8, allocator @mem.Allocator) []u8 {
result := allocator.alloc(u8, size: input.len)
duplicate func(input []u8, allocator mem.Allocator) ?*mut u8 {
result ?*mut u8 := mem.alloc(allocator, input.len, 1)
mem.copy(result, input)
return result # caller manages this memory
}
# Allocation escapes via mutable parameter — requires allocator
init func(obj: @mut MyStruct, allocator: @mem.Allocator) void {
obj.buffer = allocator.alloc(u8, size: 100)
init func(obj @mut MyStruct, allocator mem.Allocator) void {
obj.buffer = mem.alloc(allocator, 100, 1)
# caller now knows heap memory was written into obj
}
# No allocation escapes — no allocator needed
process func(input: []u8) u64 {
temp := heap.alloc(u8, size: input.len)
defer heap.free(temp)
process func(input []u8) u64 {
temp ?*mut u8 := mem.alloc(mem.c_allocator, input.len, 1)
defer mem.free(mem.c_allocator, temp, input.len, 1)
# ... work with temp ...
return compute_hash(temp)
}
# No heap allocation at all — no allocator needed
reset func(obj: @mut MyStruct) void {
reset func(obj @mut MyStruct) void {
obj.count = 0
}
main func() void {
data := duplicate("hello", heap)
defer heap.free(data)
data := duplicate("hello", mem.c_allocator)
defer mem.free(mem.c_allocator, data, 5, 1)
mut obj := MyStruct{ ... }
init(&obj, heap)
defer heap.free(obj.buffer)
init(&obj, mem.c_allocator)
defer mem.free(mem.c_allocator, obj.buffer, 100, 1)
}
```
@@ -1324,29 +1714,29 @@ main func() void {
* Without the rule, a function like `init(obj: @mut MyStruct) void` is ambiguous — did it heap-allocate into `obj`, or just set some fields to stack/static data? The caller has no way to know without reading the implementation.
* With the rule, the allocator parameter is a clear signal: "this function produces heap memory that outlives its scope, and you are responsible for cleaning it up."
* Internal allocations (temporary buffers, scratch space) use `heap` directly and are freed before the function returns. No allocator parameter needed, no burden on the caller.
* Internal allocations (temporary buffers, scratch space) use an explicit allocator value directly and are freed before the function returns. No allocator parameter needed, no burden on the caller.
**The compiler enforces this rule.** If a function heap-allocates memory that escapes without accepting an allocator parameter, the compiler emits an error.
The compiler should eventually enforce this rule. If a function heap-allocates memory that escapes without accepting an allocator parameter, the compiler should emit an error.
### Why Immutable Defaults?
Consider what would happen if you could reconfigure the default allocator:
```
# ❌ THIS IS NOT ALLOWED (and doesn't exist in Brolang)
mem.heap_set(my_custom_heap)
# This is not allowed and does not exist in Brolang.
mem.default_allocator_set(my_custom_allocator)
# Somewhere else in the codebase...
data := heap.alloc(u8, size: 100)
data := mem.alloc(mem.default_allocator, 100, 1)
# Later, someone changes it again...
mem.heap_set(different_heap)
mem.default_allocator_set(different_allocator)
# Now who frees `data`? With which allocator?
heap.free(data) # 💥 Wrong allocator - undefined behavior!
mem.free(mem.default_allocator, data, 100, 1) # wrong allocator - undefined behavior
```
This is "action at a distance" — the behavior of `heap.free()` depends on what some unrelated code did earlier. By making `heap` immutable, Brolang guarantees:
This is "action at a distance" — the behavior of `mem.free(mem.default_allocator, ...)` depends on what some unrelated code did earlier. By making allocator values explicit and immutable, Brolang guarantees:
**Whatever you allocate with, you free with.**
@@ -1354,15 +1744,16 @@ This is "action at a distance" — the behavior of `heap.free()` depends on what
For specialized needs, you create explicit allocator instances. These are not global — you manage their lifetime and pass them where needed.
The examples in this section are future typed API sketches. The v1 allocator contract is still `mem.Allocator` plus byte-oriented `mem.alloc`/`mem.free`.
**Arena Allocator**: Fast bump allocation, bulk deallocation:
```
import "std/mem"
import "std/mem/heap"
mem :: import "@std/mem"
process_file func(path: []u8, allocator: @mem.Allocator) !Data {
# arena manages its own backing memory via heap
arena := mem.Arena.init(heap, capacity: mem.megabytes(1))
process_file func(path []u8, allocator mem.Allocator) !Data {
# arena manages its own backing memory via the supplied allocator
arena := mem.Arena.init(mem.c_allocator, capacity: mem.megabytes(1))
defer arena.deinit()
# all temporary allocations from arena (fast bump allocation)
@@ -1384,14 +1775,13 @@ process_file func(path: []u8, allocator: @mem.Allocator) !Data {
**Pool Allocator**: O(1) fixed-size allocation, no fragmentation:
```
import "std/mem"
import "std/mem/heap"
mem :: import "@std/mem"
EntitySystem :: struct {
pool: mem.Pool(Entity),
}
init_entities func(allocator: @mem.Allocator) EntitySystem {
init_entities func(allocator mem.Allocator) EntitySystem {
return EntitySystem{
pool = mem.Pool(Entity).init(allocator, capacity: 10_000),
}
@@ -1411,23 +1801,23 @@ despawn func(sys: @mut EntitySystem, entity: @Entity) void {
As described in the escaping allocation rule, when a function heap-allocates memory that escapes its scope, it must accept an allocator parameter. The caller decides which allocator to use:
```
import "std/mem"
mem :: import "@std/mem"
# Function that uses caller's allocator
parse func(input: []u8, allocator: @mem.Allocator) !ParseResult {
buffer := allocator.alloc(u8, size: input.len)
defer allocator.free(buffer)
parse func(input []u8, allocator mem.Allocator) !ParseResult {
buffer := mem.alloc(allocator, input.len, 1)
defer mem.free(allocator, buffer, input.len, 1)
# ... parse into buffer ...
result := allocator.alloc(ParseResult) # size defaults to 1
result := mem.alloc(allocator, parse_result_size, parse_result_alignment)
return result
}
# Caller decides which allocator to use
main func() void {
# use an arena for this parsing work
arena := mem.Arena.init(heap, capacity: mem.kilobytes(64))
arena := mem.Arena.init(mem.c_allocator, capacity: mem.kilobytes(64))
defer arena.deinit()
result := parse(input, &arena) catch |err| {
# handle error
@@ -1446,15 +1836,15 @@ main func() void {
| What | How | When to Use |
| -- | -- | -- |
| `heap.alloc(T, size: n)` | Thread-local global | General purpose, 90% of cases |
| `heap.create(T)` | Thread-local global | Allocate single item |
| `allocator.alloc(T, size: n)` | Caller-provided | Escaping allocations (returned or written to caller's data) |
| `arena.alloc(T, size: n)` | Explicit instance | Temporary/scoped work, bulk free |
| `pool.alloc()` | Explicit instance | Many same-sized objects, O(1) |
| `mem.alloc(mem.c_allocator, n, a)` | Libc-backed allocator | General purpose byte allocation |
| `mem.realloc(mem.c_allocator, ptr, old_n, new_n, a)` | Libc-backed allocator | Resize while preserving alignment and up to `min(old_n, new_n)` bytes |
| `mem.free(mem.c_allocator, ptr, n, a)` | Libc-backed allocator | Free byte allocation with original size/alignment |
| `mem.alloc(allocator, n, a)` | Caller-provided allocator | Escaping allocations (returned or written to caller's data) |
| typed helpers / arenas / pools | Future APIs | Higher-level allocation patterns |
Note that `heap` satisfies the `Allocator` interface, so callers can pass `heap` as the allocator argument when they don't need a specialized allocator — which is most of the time.
Note that `mem.c_allocator` is a `mem.Allocator`, so callers can pass it as the allocator argument when they don't need a specialized allocator — which is most of the time.
**The golden rule:** Allocate and free with the same allocator. The type system helps enforce this — memory from `heap` can only be freed with `heap`, memory from your arena can only be freed with that arena. The escaping allocation rule ensures the caller always knows which allocator was used.
**The golden rule:** Allocate and free with the same allocator. In v1 this is explicit in the call sites; future diagnostics should use the escaping allocation rule to ensure the caller always knows which allocator was used.
### Compared to Other Languages
+118 -11
View File
@@ -63,6 +63,14 @@ index :: proc(id: $T, invalid: T, count: int) -> (int, bool) {
Type_Syntax :: types.Type
Type_Use :: struct {
type: Type_Syntax,
span: source.Span,
pkg: Package_Id,
file: File_Id,
diagnostic: source.Diagnostic_Id,
}
Expr_Kind :: enum u8 {
Invalid,
Integer,
@@ -70,8 +78,11 @@ Expr_Kind :: enum u8 {
String,
Bool,
Array,
None,
Null,
Unreachable,
Undefined,
Inference_Hole,
Type,
Name,
Enum_Literal,
Address,
@@ -84,12 +95,20 @@ Expr_Kind :: enum u8 {
Struct_Literal,
Keyed,
Cast,
Comptime,
Negate,
Not,
Bit_Not,
Add,
Sub,
Mul,
Div,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
Eq,
Ne,
Lt,
@@ -102,6 +121,8 @@ Expr_Kind :: enum u8 {
Call,
Try,
Catch,
Function_Literal,
Anonymous_Struct_Type,
}
Expr :: struct {
@@ -116,6 +137,8 @@ Expr :: struct {
body: []Stmt_Id,
diagnostic: source.Diagnostic_Id,
parenthesized: bool,
intrinsic: bool,
tuple: bool,
kind: Expr_Kind,
}
@@ -123,6 +146,7 @@ Param :: struct {
name: symbol.Id,
span: source.Span,
type: Type_Syntax,
comptime_value: bool,
}
Stmt_Kind :: enum u8 {
@@ -149,6 +173,12 @@ Assignment_Op :: enum u8 {
Sub,
Mul,
Div,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
}
Stmt :: struct {
@@ -164,31 +194,34 @@ Stmt :: struct {
immutable: bool,
value_control_flow: bool,
pointer_capture: bool,
expand: bool,
error_only: bool,
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
// the remaining variants preserve their corresponding compound operator.
assignment_op: Assignment_Op,
target: Expr_Id,
expr: Expr_Id,
// `If` statements use `expr` as the condition, `captures` as optional
// unwrap binding names, `guard` as the optional post-unwrap boolean
// condition, `body` as the then-block, and `else_body` as the else-block.
// An `else if` chain is represented as an `else_body` holding a single
// nested `If` statement.
// `While` statements use `expr` as the condition, `body` as the loop body,
// and `update` as the optional post-iteration statement.
// `If` and `While` statements use `expr` as the condition, `captures` as
// optional unwrap binding names, and `guard` as the optional post-unwrap
// boolean condition. `If` uses `body` as the then-block and `else_body` as
// the else-block; an `else if` chain is represented as an `else_body`
// holding a single nested `If` statement. `While` uses `body` as the loop
// body and `update` as the optional post-iteration statement.
// `For` statements use `expr` as the iterable, `name` as the item capture,
// `index_name` as the optional index capture, and `pointer_capture` to
// distinguish `|@item|` from copy capture.
// `Block` statements (a bare `{ ... }` scope) use `body` as their statements.
// `Defer` statements use `update` as the deferred statement (which may itself
// be a `Block`).
// be a `Block`), `error_only` for `errdefer`, and `captures` for its optional
// error capture.
// `Match` statements use `expr` as the subject and `body` as the list of arm
// statements (each a `Match_Arm`). A `Match_Arm` uses `patterns` as its pattern
// list (empty marks the `else` arm; more than one is a multi-pattern arm),
// `captures` for the optional payload capture (0 or 1 name, tagged-union variants
// only) with `pointer_capture` distinguishing `|@cap|` from `|cap|`, and `body`
// as the arm body.
// as the arm body. An `inline` arm has `expand` set, no patterns, and one or two
// captures for its specialized value/payload and optional tagged-union tag.
captures: []symbol.Id,
// `Match_Arm` pattern list; empty ⇒ the `else` arm.
patterns: []Expr_Id,
@@ -206,11 +239,16 @@ Function :: struct {
file: File_Id,
c_abi: bool,
imported: bool,
generated: bool,
analysis_root: bool,
test: bool,
visibility: types.Visibility,
has_body: bool,
variadic: bool,
params: []Param,
result: Type_Syntax,
error: Type_Syntax,
infer_error: bool,
body: []Stmt_Id,
link_name: string,
unsupported_reason: string,
@@ -227,10 +265,24 @@ Global :: struct {
immutable: bool,
external: bool,
writable: bool,
visibility: types.Visibility,
expr: Expr_Id,
diagnostic: source.Diagnostic_Id,
}
Enum_Value :: struct {
expr: Expr_Id,
span: source.Span,
explicit: bool,
}
Enum_Declaration :: struct {
type: types.Type,
pkg: Package_Id,
file: File_Id,
values: []Enum_Value,
}
Import :: struct {
span: source.Span,
alias: symbol.Id,
@@ -240,9 +292,40 @@ Import :: struct {
target: Package_Id,
valid: bool,
used: bool,
test_only: bool,
diagnostic: source.Diagnostic_Id,
}
Declaration_Alias_Kind :: enum u8 {
Invalid,
Function,
Global,
Type,
}
Declaration_Alias :: struct {
span: source.Span,
name: symbol.Id,
qualifier: symbol.Id,
member: symbol.Id,
pkg: Package_Id,
file: File_Id,
target_pkg: Package_Id,
target: u32,
kind: Declaration_Alias_Kind,
visibility: types.Visibility,
valid: bool,
diagnostic: source.Diagnostic_Id,
}
Struct_Field_Default :: struct {
record: Type_Syntax,
field: symbol.Id,
expr: Expr_Id,
pkg: Package_Id,
file: File_Id,
}
File :: struct {
source: source.Source_Id,
pkg: Package_Id,
@@ -252,9 +335,15 @@ Package :: struct {
path: string,
name: symbol.Id,
available: bool,
test: bool,
kind: Package_Kind,
}
Compile_Mode :: enum u8 {
Executable,
Test,
}
Package_Kind :: enum u8 {
Native,
C_Header,
@@ -281,12 +370,17 @@ Module :: struct {
statements: [dynamic]Stmt,
functions: [dynamic]Function,
globals: [dynamic]Global,
enum_declarations: [dynamic]Enum_Declaration,
imports: [dynamic]Import,
aliases: [dynamic]Declaration_Alias,
files: [dynamic]File,
packages: [dynamic]Package,
unsupported: [dynamic]Unsupported,
c_trampolines: [dynamic]Trampoline,
strings: [dynamic]string,
type_fields: [dynamic]types.Field,
struct_field_defaults: [dynamic]Struct_Field_Default,
type_uses: [dynamic]Type_Use,
type_store: types.Store,
allocator: mem.Allocator,
}
@@ -299,12 +393,17 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.statements.allocator = allocator
module.functions.allocator = allocator
module.globals.allocator = allocator
module.enum_declarations.allocator = allocator
module.imports.allocator = allocator
module.aliases.allocator = allocator
module.files.allocator = allocator
module.packages.allocator = allocator
module.unsupported.allocator = allocator
module.c_trampolines.allocator = allocator
module.strings.allocator = allocator
module.type_fields.allocator = allocator
module.struct_field_defaults.allocator = allocator
module.type_uses.allocator = allocator
return module
}
@@ -331,6 +430,9 @@ destroy_module :: proc(module: ^Module) {
for global in module.globals {
delete(global.link_name, module.allocator)
}
for declaration in module.enum_declarations {
delete(declaration.values, module.allocator)
}
for pkg in module.packages {
delete(pkg.path, module.allocator)
}
@@ -349,11 +451,16 @@ destroy_module :: proc(module: ^Module) {
delete(module.statements)
delete(module.functions)
delete(module.globals)
delete(module.enum_declarations)
delete(module.imports)
delete(module.aliases)
delete(module.files)
delete(module.packages)
delete(module.unsupported)
delete(module.c_trampolines)
delete(module.strings)
delete(module.type_fields)
delete(module.struct_field_defaults)
delete(module.type_uses)
types.destroy_store(&module.type_store)
}
+27
View File
@@ -13,6 +13,32 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al
append(command, strings.clone(value, allocator))
}
macos_sdk_root :: proc(allocator := context.allocator) -> (string, bool) {
candidates := []string{
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
}
for path in candidates {
if os.exists(path) {
return strings.clone(path, allocator), true
}
}
return "", false
}
append_macos_sdk_paths :: proc(command: ^[dynamic]string, selected: target.Target, allocator: mem.Allocator) {
if selected.kind != .Aarch64_Macos {
return
}
sdk, ok := macos_sdk_root(allocator)
if !ok {
return
}
defer delete(sdk, allocator)
append(command, fmt.aprintf("-F%s/System/Library/Frameworks", sdk, allocator=allocator))
append(command, fmt.aprintf("-L%s/usr/lib", sdk, allocator=allocator))
}
build_command :: proc(
llvm_path, output_path: string,
link_arguments: []linker.Argument,
@@ -29,6 +55,7 @@ build_command :: proc(
append_owned(&command, target.name(selected), allocator)
append_owned(&command, "-Wno-override-module", allocator)
append_owned(&command, "-Wno-unused-command-line-argument", allocator)
append_macos_sdk_paths(&command, selected, allocator)
append_owned(&command, llvm_path, allocator)
for path in c_options.include_paths {
append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
+394
View File
@@ -0,0 +1,394 @@
package compiler
import "./checker"
import "./cimport"
import "./hir"
import "./linker"
import "./loader"
import "./source"
import "./symbol"
import "./target"
import "./types"
import "core:fmt"
import vmem "core:mem/virtual"
import "core:os"
import "core:os/os2"
import "core:path/filepath"
import "core:strings"
// BuildConfig is the native, extracted form of std/build's BuildConfig: all
// strings are cloned into context.allocator so they outlive the build module's
// arena (freed at the end of run_build). Free with destroy_build_config.
BuildConfig :: struct {
output_name: string,
source_dir: string,
link_arguments: []linker.Argument,
c_options: cimport.Options,
}
destroy_build_config :: proc(cfg: ^BuildConfig) {
delete(cfg.output_name)
delete(cfg.source_dir)
for arg in cfg.link_arguments {
delete(arg.value)
}
delete(cfg.link_arguments)
for path in cfg.c_options.include_paths {
delete(path)
}
delete(cfg.c_options.include_paths)
for define in cfg.c_options.defines {
delete(define)
}
delete(cfg.c_options.defines)
}
load_build_config :: proc(project_root: string) -> (BuildConfig, bool) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
arena: vmem.Arena
if err := vmem.arena_init_growing(&arena); err != nil {
fmt.eprintln("failed to initialize build arena:", err)
return {}, false
}
defer vmem.arena_destroy(&arena)
a := vmem.arena_allocator(&arena)
ast_module, loaded := loader.load(project_root, &sources, &diagnostics, &symbols, a, a, cimport.Options{}, target.DEFAULT, project_root)
if !loaded {
source.print_all(&diagnostics)
fmt.eprintln("failed to load build root:", project_root)
return {}, false
}
// check needs no `main`: it synthesizes a trap main and emits one benign
// "missing main" diagnostic, which is expected for a build config. Suppress
// that one but surface any real errors in the build config (and fail on them).
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
if build_config_has_errors(&diagnostics) {
source.print_all(&diagnostics)
return {}, false
}
return extract_build_config(&hir_module, &symbols)
}
project_root_for_command :: proc(root, command: string) -> (string, bool, bool) {
if len(root) > 0 {
return root, false, true
}
project_root, found := find_build_root()
if !found {
fmt.eprintfln("brolang %s: could not find build.bro or build.hon in the current directory or any parent", command)
return "", false, false
}
return project_root, true, true
}
// run_build implements `brolang build [root]`: it reads the build config and
// compiles the configured program package.
run_build :: proc(root: string) -> int {
project_root, owned, found := project_root_for_command(root, "build")
if !found {
return 2
}
defer if owned {delete(project_root)}
cfg, ok := load_build_config(project_root)
if !ok {
return 2
}
defer destroy_build_config(&cfg)
program := filepath.join({project_root, cfg.source_dir})
defer delete(program)
output, output_ok := build_output_path(project_root, cfg.output_name)
if !output_ok {
return 2
}
defer delete(output)
return compile_package(program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root)
}
run_tests :: proc(root: string) -> int {
project_root, owned, found := project_root_for_command(root, "test")
if !found {
return 2
}
defer if owned {delete(project_root)}
cfg, ok := load_build_config(project_root)
if !ok {
return 2
}
defer destroy_build_config(&cfg)
program := filepath.join({project_root, cfg.source_dir})
defer delete(program)
test_name := fmt.aprintf("%s-test", cfg.output_name)
defer delete(test_name)
output, output_ok := build_output_path(project_root, test_name)
if !output_ok {
return 2
}
defer delete(output)
status := compile_package(
program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root, .Test,
)
if status != 0 {
return status
}
state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=[]string{output}},
context.allocator,
)
defer delete(stdout)
defer delete(stderr)
if len(stdout) > 0 {fmt.print(string(stdout))}
if len(stderr) > 0 {fmt.eprint(string(stderr))}
if err != nil {
fmt.eprintln("failed to run test executable:", err)
return 2
}
return 0 if state.exit_code == 0 else 1
}
valid_output_name :: proc(name: string) -> bool {
return len(name) > 0 && name != "." && name != ".." &&
!strings.contains(name, "/") && !strings.contains(name, "\\")
}
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
if !valid_output_name(name) {
fmt.eprintfln("build config: config 'name' must be a plain executable name, got '%s'", name)
return "", false
}
build_dir, dir_error := filepath.join({project_root, "build"}, allocator)
if dir_error != nil {
return "", false
}
if os.exists(build_dir) {
if !os.is_dir(build_dir) {
fmt.eprintfln("build path exists and is not a directory: %s", build_dir)
delete(build_dir, allocator)
return "", false
}
} else if err := os2.make_directory_all(build_dir); err != nil {
fmt.eprintfln("failed to create build directory '%s': %v", build_dir, err)
delete(build_dir, allocator)
return "", false
}
output, output_error := filepath.join({build_dir, name}, allocator)
delete(build_dir, allocator)
if output_error != nil {
return "", false
}
return output, true
}
find_build_root :: proc(allocator := context.allocator) -> (string, bool) {
current := os.get_current_directory(allocator)
if len(current) == 0 {
return "", false
}
defer delete(current, allocator)
return find_build_root_from(current, allocator)
}
find_build_root_from :: proc(start: string, allocator := context.allocator) -> (string, bool) {
current, current_ok := filepath.abs(start, allocator)
if !current_ok {
current = strings.clone(start, allocator)
}
for {
for build_file in ([?]string{"build.bro", "build.hon"}) {
build_path, build_error := filepath.join({current, build_file}, allocator)
if build_error != nil {
delete(current, allocator)
return "", false
}
found := os.exists(build_path)
delete(build_path, allocator)
if found {
return current, true
}
}
if current == "/" {
delete(current, allocator)
return "", false
}
parent := filepath.dir(current, allocator)
if parent == current {
delete(parent, allocator)
delete(current, allocator)
return "", false
}
delete(current, allocator)
current = parent
}
}
// build_config_has_errors reports whether checking a build config produced any
// diagnostic other than the benign "missing or unusable main function" (build
// configs have no main by design; that one is emitted with an empty span).
build_config_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
for item in diagnostics.items {
if item.span == (source.Span{}) && item.message == "missing or unusable main function" {
continue
}
return true
}
return false
}
// extract_build_config finds the top-level `config` constant and reads its
// BuildConfig{...} fields out of the HIR. All returned strings are cloned into
// context.allocator.
extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildConfig, bool) {
config_id := symbol.intern(symbols, "config")
config_expr := hir.INVALID_EXPR
found := false
for g in m.globals {
if g.name == config_id {
config_expr = g.expr
found = true
break
}
}
if !found {
fmt.eprintln("build config: missing top-level 'config' constant")
return {}, false
}
root := unwrap_coercions(m, config_expr)
if root == hir.INVALID_EXPR || m.exprs[root].kind != .Struct {
fmt.eprintln("build config: 'config' must be a BuildConfig{...} literal")
return {}, false
}
args := m.exprs[root].args
fields := types.fields_for(&m.types, m.exprs[root].type)
cfg: BuildConfig
links: [dynamic]linker.Argument
includes: [dynamic]string
defines: [dynamic]string
for field, i in fields {
if i >= len(args) {
break
}
switch symbol.resolve(symbols, symbol.Id(field.name)) {
case "name":
if s, sok := read_string(m, args[i]); sok {
cfg.output_name = strings.clone(s)
}
case "source":
if s, sok := read_string(m, args[i]); sok {
cfg.source_dir = strings.clone(s)
}
case "libraries":
list := read_string_list(m, args[i])
for v in list {
append(&links, linker.Argument{kind = .Library, value = strings.clone(v)})
}
delete(list)
case "lib_paths":
list := read_string_list(m, args[i])
for v in list {
append(&links, linker.Argument{kind = .Library_Path, value = strings.clone(v)})
}
delete(list)
case "links":
list := read_string_list(m, args[i])
for v in list {
append(&links, linker.Argument{kind = .Input, value = strings.clone(v)})
}
delete(list)
case "includes":
list := read_string_list(m, args[i])
for v in list {
append(&includes, strings.clone(v))
}
delete(list)
case "defines":
list := read_string_list(m, args[i])
for v in list {
append(&defines, strings.clone(v))
}
delete(list)
}
}
cfg.link_arguments = links[:]
cfg.c_options.include_paths = includes[:]
cfg.c_options.defines = defines[:]
if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 {
fmt.eprintln("build config: config requires non-empty 'name' and 'source'")
destroy_build_config(&cfg)
return {}, false
}
return cfg, true
}
// unwrap_coercions strips implicit coercion wrappers (each stores its inner
// expr in `.left`) to reach the underlying value expression.
unwrap_coercions :: proc(m: ^hir.Module, id: hir.Expr_Id) -> hir.Expr_Id {
cur := id
for cur != hir.INVALID_EXPR && int(cur) < len(m.exprs) {
#partial switch m.exprs[cur].kind {
case .Retype, .Pointer_Cast, .Weaken_Slice, .Weaken_Pointer, .Decay_Array_Pointer, .Slice_Ptr,
.Widen, .Sum_Widen, .Sum_Project, .Optional_Some, .C_Coerce, .Scalar_Cast:
cur = m.exprs[cur].left
case:
return cur
}
}
return cur
}
read_string :: proc(m: ^hir.Module, id: hir.Expr_Id) -> (string, bool) {
e := unwrap_coercions(m, id)
if e == hir.INVALID_EXPR || m.exprs[e].kind != .String {
return "", false
}
sid := m.exprs[e].integer
if sid < 0 || int(sid) >= len(m.strings) {
return "", false
}
return m.strings[int(sid)], true
}
// read_string_list reads a `&[...]` list field: the value is an address of an
// anonymous global array (see checker `&<array literal>` promotion), whose
// elements are strings. Returned strings alias m.strings; callers clone them.
// The returned slice is owned by the caller (delete it).
read_string_list :: proc(m: ^hir.Module, id: hir.Expr_Id) -> []string {
addr := unwrap_coercions(m, id)
if addr == hir.INVALID_EXPR || m.exprs[addr].kind != .Address {
return nil
}
g := unwrap_coercions(m, m.exprs[addr].left)
if g == hir.INVALID_EXPR || m.exprs[g].kind != .Global {
return nil
}
gid := hir.as_global(m.exprs[g].target)
if gid == hir.INVALID_GLOBAL || int(gid) >= len(m.globals) {
return nil
}
arr := m.globals[gid].expr
if arr == hir.INVALID_EXPR || m.exprs[arr].kind != .Array {
return nil
}
elems := m.exprs[arr].args
out := make([]string, len(elems))
for a, i in elems {
s, ok := read_string(m, a)
if !ok {
delete(out)
return nil
}
out[i] = s
}
return out
}
+8518 -1025
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+26 -2
View File
@@ -502,7 +502,7 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
params: [dynamic]Type_Id
params.allocator = ctx.allocator
for index in 0..<count {
param := translate_type(ctx, ctx.api.get_arg_type(value, u32(index)), "", depth+1)
param := translate_parameter_type(ctx, ctx.api.get_arg_type(value, u32(index)), depth+1)
append(&params, param)
if param == INVALID_TYPE {
delete(params)
@@ -537,6 +537,30 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
return INVALID_TYPE
}
translate_parameter_type :: proc(ctx: ^Context, value: CXType, depth := 0) -> Type_Id {
if depth > 64 {
return INVALID_TYPE
}
array := value
if value.kind != CXType_ConstantArray && value.kind != CXType_IncompleteArray {
canonical := ctx.api.get_canonical_type(value)
if canonical.kind != CXType_ConstantArray && canonical.kind != CXType_IncompleteArray {
return translate_type(ctx, value, "", depth+1)
}
array = canonical
}
element := ctx.api.get_array_element_type(array)
child := translate_type(ctx, element, "", depth+1)
if child == INVALID_TYPE {
return INVALID_TYPE
}
return add_type(ctx, Type{
kind=.Pointer,
child=child,
mutable=ctx.api.is_const_qualified_type(element) == 0,
})
}
has_named :: proc(items: []Unsupported, name: string) -> bool {
for item in items {
if item.name == name {
@@ -1385,7 +1409,7 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
reason = "function declaration has no prototype"
} else {
for index in 0..<count {
param := translate_type(ctx, ctx.api.get_arg_type(function_type, u32(index)))
param := translate_parameter_type(ctx, ctx.api.get_arg_type(function_type, u32(index)))
append(&params, param)
if param == INVALID_TYPE && len(reason) == 0 {
reason = "function parameter type is not supported"
+27
View File
@@ -1,5 +1,6 @@
package compiler
import "./ast"
import "./backend"
import "./cimport"
import "./checker"
@@ -28,11 +29,22 @@ write_escaped_c_string :: proc(builder: ^strings.Builder, value: string) {
}
}
has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
for diagnostic in diagnostics.items {
if diagnostic.severity == .Error {
return true
}
}
return false
}
compile_package :: proc(
input_path, output_path: string,
link_arguments: []linker.Argument = nil,
selected := target.DEFAULT,
c_options := cimport.Options{},
project_root := "",
mode := ast.Compile_Mode.Executable,
) -> int {
sources := source.init_store()
defer source.destroy_store(&sources)
@@ -75,12 +87,19 @@ compile_package :: proc(
vmem.arena_allocator(&parser_arena),
c_options,
selected,
project_root if len(project_root) > 0 else input_path,
mode,
)
if !loaded {
source.print_all(&diagnostics)
fmt.eprintln("failed to load root package directory:", input_path)
return 2
}
effective_root := project_root if len(project_root) > 0 else input_path
if !prepare_tests(&ast_module, &sources, &diagnostics, &symbols, mode, effective_root) {
source.print_all(&diagnostics)
return 1
}
// Generated C trampolines (for `static inline` imports) must be compiled and
// linked with the program. Write them out and add the source as a link input
@@ -132,6 +151,10 @@ compile_package :: proc(
vmem.arena_free_all(&lexer_arena)
hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena))
vmem.arena_free_all(&parser_arena)
if has_errors(&diagnostics) {
source.print_all(&diagnostics)
return 1
}
ir_module := lower.lower(&hir_module, vmem.arena_allocator(&lower_arena))
vmem.arena_free_all(&checker_arena)
opt.run(&ir_module)
@@ -139,6 +162,10 @@ compile_package :: proc(
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
vmem.arena_free_all(&lower_arena)
if has_errors(&diagnostics) {
source.print_all(&diagnostics)
return 1
}
llvm_path := fmt.tprintf("%s.brolang-%d.ll", output_path, os2.get_pid())
defer _ = os.remove(llvm_path)
if err := os.write_entire_file_or_err(llvm_path, transmute([]byte)llvm_text); err != nil {
+49 -9
View File
@@ -74,13 +74,16 @@ Linkage :: enum u8 {
Expr_Kind :: enum u8 {
Invalid,
Void,
Integer,
Float,
String,
Bool,
Undefined,
Array,
Struct,
None,
Null,
Unreachable,
Optional_Some,
Local,
Global,
@@ -99,20 +102,36 @@ Expr_Kind :: enum u8 {
Catch,
Widen,
Sum_Widen,
Sum_Project,
C_Coerce,
C_Vararg_Promote,
Retype,
Scalar_Cast,
Pointer_Cast,
Const_Cast,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
Negate,
Not,
Bit_Not,
Add,
Sub,
Mul,
Div,
Div_Trunc,
Div_Floor,
Div_Exact,
Div_Ceil,
Rem,
Mod,
Pointer_Add,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
Eq,
Ne,
Lt,
@@ -122,16 +141,26 @@ Expr_Kind :: enum u8 {
And,
Or,
Range,
Mem_Copy,
Mem_Set,
Call,
}
CATCH_EXPRESSION :: i64(0)
CATCH_BLOCK :: i64(1)
CATCH_VOID_FALLTHROUGH :: i64(2)
Expr :: struct {
span: source.Span,
type: types.Type,
integer: i64,
// Aggregate/call children normally; Try stores errdefer capture local IDs
// encoded as Expr_Id because it otherwise has no args.
args: []Expr_Id,
// `Catch` block handlers use `body` for the handler statements and `target`
// for the optional captured error local.
// for the optional captured error local. `integer` selects the catch mode;
// a missing `right` means the block exits or completes with void. `Try` uses
// `body` for active error-exit cleanup.
body: []Stmt_Id,
target: Ref,
left: Expr_Id,
@@ -176,6 +205,12 @@ Assignment_Op :: enum u8 {
Mul,
Div,
Pointer_Add,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
}
Stmt :: struct {
@@ -190,16 +225,16 @@ Stmt :: struct {
expr: Expr_Id,
iterator_type: types.Type,
pointer_capture: bool,
// Assignments carry their operation explicitly. Arithmetic operations lower
// Assignments carry their operation explicitly. Compound operations lower
// by computing the target address once, loading its current value, applying
// the operation to `expr`, and storing through the original address.
assignment_op: Assignment_Op,
// Boolean `If` statements use `expr` as the condition. Conditional unwraps
// use `unwraps` for the ordered optional expressions and capture locals, and
// `guard` for the optional boolean checked after every unwrap succeeds.
// Both forms use `then_body`/`else_body` as the branch statement lists.
// `While` statements use `expr` as the condition, `then_body` as the loop
// body, and `update` as the optional post-iteration statement.
// Boolean `If` and `While` statements use `expr` as the condition.
// Conditional unwraps use `unwraps` for the ordered optional expressions
// and capture locals, and `guard` for the optional boolean checked after
// every unwrap succeeds. `If` uses `then_body`/`else_body` as its branches.
// `While` uses `then_body` as its loop body and `update` as the optional
// post-iteration statement.
// `For` statements use `expr` as the iterable, `local` as the item capture,
// `index_local` as the optional sequence index, and `iterator_type` as the
// normalized many-item pointer type for sequence iteration.
@@ -236,6 +271,7 @@ Global :: struct {
expr: Expr_Id,
static_value: i64,
is_static: bool,
eager: bool,
external: bool,
writable: bool,
dependencies: [dynamic]Global_Id,
@@ -251,6 +287,8 @@ Module :: struct {
functions: [dynamic]Function,
globals: [dynamic]Global,
strings: [dynamic]string,
injected_main: Function_Id,
io_provider: Function_Id,
types: types.Store,
target: target.Target,
allocator: mem.Allocator,
@@ -259,6 +297,8 @@ Module :: struct {
init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module {
module: Module
module.target = selected
module.injected_main = INVALID_FUNCTION
module.io_provider = INVALID_FUNCTION
module.types = types.init_store(allocator)
module.types.selected = selected
module.allocator = allocator
+21 -1
View File
@@ -68,9 +68,10 @@ Linkage :: enum u8 {
Opcode :: enum u8 {
Param,
Const,
Poison,
String,
Aggregate,
None,
Null,
Optional_Some,
Load_Global,
Function_Address,
@@ -96,10 +97,13 @@ Opcode :: enum u8 {
Orelse,
Widen,
Sum_Widen,
Sum_Project,
C_Coerce,
C_Vararg_Promote,
Retype,
Scalar_Cast,
Pointer_Cast,
Const_Cast,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
@@ -108,9 +112,24 @@ Opcode :: enum u8 {
Sub_Checked,
Mul_Checked,
Div_Checked,
Div_Trunc_Checked,
Div_Floor_Checked,
Div_Exact_Checked,
Div_Ceil_Checked,
Rem_Checked,
Mod_Checked,
Pointer_Add,
Not,
Bit_Not,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
Compare,
Mem_Copy,
Mem_Set,
Label,
Br,
Cond_Br,
@@ -160,6 +179,7 @@ Global :: struct {
link_name: string,
type: types.Type,
is_static: bool,
eager: bool,
external: bool,
writable: bool,
static_value: i64,
+89 -13
View File
@@ -8,16 +8,23 @@ is_identifier_start :: proc(value: byte) -> bool {
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z'
}
is_hex_digit :: proc(value: byte) -> bool {
return value >= '0' && value <= '9' || value >= 'a' && value <= 'f' || value >= 'A' && value <= 'F'
}
is_identifier_continue :: proc(value: byte) -> bool {
return is_identifier_start(value) || value >= '0' && value <= '9'
}
keyword_kind :: proc(text: string) -> token.Kind {
switch text {
case "test": return .Keyword_Test
case "func": return .Keyword_Func
case "proc": return .Keyword_Func
case "c_func": return .Keyword_C_Func
case "struct": return .Keyword_Struct
case "c_struct": return .Keyword_C_Struct
case "opaque": return .Keyword_Opaque
case "union": return .Keyword_Union
case "enum": return .Keyword_Enum
case "distinct": return .Keyword_Distinct
@@ -27,25 +34,32 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "try": return .Keyword_Try
case "catch": return .Keyword_Catch
case "mut": return .Keyword_Mut
case "none": return .Keyword_None
case "null": return .Keyword_Null
case "undefined": return .Keyword_Undefined
case "orelse": return .Keyword_Orelse
case "and": return .Keyword_And
case "or": return .Keyword_Or
case "xor": return .Keyword_Xor
case "if": return .Keyword_If
case "while": return .Keyword_While
case "for": return .Keyword_For
case "inline": return .Keyword_Inline
case "break": return .Keyword_Break
case "continue": return .Keyword_Continue
case "defer": return .Keyword_Defer
case "errdefer": return .Keyword_Errdefer
case "yield": return .Keyword_Yield
case "match": return .Keyword_Match
case "else": return .Keyword_Else
case "true": return .Keyword_True
case "false": return .Keyword_False
case "void": return .Keyword_Void
case "noreturn": return .Keyword_Noreturn
case "anyopaque": return .Keyword_Anyopaque
case "unreachable": return .Keyword_Unreachable
case "bool": return .Keyword_Bool
case "int": return .Keyword_Int
case "uint": return .Keyword_Uint
case "float": return .Keyword_Float
case "range": return .Keyword_Range
case "i8": return .Keyword_I8
@@ -103,6 +117,7 @@ lex :: proc(
) -> token.Stream {
stream: token.Stream
stream.items.allocator = allocator
stream.symbols = symbols
bytes := transmute([]byte)source_file.text
cursor := 0
@@ -121,9 +136,16 @@ lex :: proc(
case ':':
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == ':' {
cursor += 1
append_token(&stream, source_file, .Colon_Colon, start, cursor)
if cursor < len(bytes) {
if bytes[cursor] == ':' {
cursor += 1
append_token(&stream, source_file, .Colon_Colon, start, cursor)
} else if bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Colon_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Colon, start, cursor)
}
} else {
append_token(&stream, source_file, .Colon, start, cursor)
}
@@ -148,7 +170,23 @@ lex :: proc(
case '<':
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
if cursor < len(bytes) && bytes[cursor] == '<' {
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '|' {
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Less_Less_Pipe_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Less_Less_Pipe, start, cursor)
}
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Less_Less_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Less_Less, start, cursor)
}
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Less_Equal, start, cursor)
} else {
@@ -157,7 +195,15 @@ lex :: proc(
case '>':
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
if cursor < len(bytes) && bytes[cursor] == '>' {
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Greater_Greater_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Greater_Greater, start, cursor)
}
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Greater_Equal, start, cursor)
} else {
@@ -201,6 +247,9 @@ lex :: proc(
case '@':
append_token(&stream, source_file, .At, cursor, cursor+1)
cursor += 1
case '$':
append_token(&stream, source_file, .Dollar, cursor, cursor+1)
cursor += 1
case '*':
start := cursor
cursor += 1
@@ -220,11 +269,20 @@ lex :: proc(
append_token(&stream, source_file, .Slash, start, cursor)
}
case '&':
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Ampersand_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Ampersand, start, cursor)
}
case '^':
append_token(&stream, source_file, .Caret, cursor, cursor+1)
cursor += 1
case '~':
append_token(&stream, source_file, .Tilde, cursor, cursor+1)
cursor += 1
case '?':
append_token(&stream, source_file, .Question, cursor, cursor+1)
cursor += 1
@@ -250,8 +308,14 @@ lex :: proc(
append_token(&stream, source_file, .Comma, cursor, cursor+1)
cursor += 1
case '|':
append_token(&stream, source_file, .Pipe, cursor, cursor+1)
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Pipe_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Pipe, start, cursor)
}
case '"':
start := cursor
cursor += 1
@@ -259,13 +323,21 @@ lex :: proc(
for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' {
if bytes[cursor] == '\\' {
cursor += 1
if cursor >= len(bytes) ||
(bytes[cursor] != '\\' && bytes[cursor] != '"' && bytes[cursor] != 'n' &&
bytes[cursor] != 'r' && bytes[cursor] != 't' && bytes[cursor] != '0') {
valid_escape := cursor < len(bytes) &&
(bytes[cursor] == '\\' || bytes[cursor] == '"' || bytes[cursor] == 'n' ||
bytes[cursor] == 'r' || bytes[cursor] == 't' || bytes[cursor] == '0')
if cursor < len(bytes) && bytes[cursor] == 'x' {
valid_escape = cursor+2 < len(bytes) &&
is_hex_digit(bytes[cursor+1]) && is_hex_digit(bytes[cursor+2])
if valid_escape {
cursor += 2
}
}
if !valid_escape {
source.add(
diagnostics,
source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+1, len(bytes)))},
"strings only support '\\\\', '\\\"', '\\n', '\\r', '\\t', and '\\0' escapes",
source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+3, len(bytes)))},
"strings only support '\\\\', '\\\"', '\\n', '\\r', '\\t', '\\0', and '\\xNN' escapes",
)
valid = false
}
@@ -359,6 +431,10 @@ lex :: proc(
}
text := source_file.text[start:cursor]
kind := keyword_kind(text)
if kind == .Keyword_Xor && cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
kind = .Xor_Equal
}
id := symbol.INVALID
if kind == .Identifier || kind == .Underscore {
id = symbol.intern(symbols, text)
+670 -90
View File
File diff suppressed because it is too large Load Diff
+568 -33
View File
@@ -26,6 +26,7 @@ State :: struct {
c_options: cimport.Options,
selected: target.Target,
project_root: string,
mode: ast.Compile_Mode,
record_identities: [dynamic]string,
record_types: [dynamic]types.Type,
root_failed: bool,
@@ -87,7 +88,8 @@ read_package_files :: proc(state: ^State, path: string) -> ([]os.File_Info, bool
files: [dynamic]os.File_Info
files.allocator = state.allocator
for entry in entries {
if !entry.is_dir && filepath.ext(entry.name) == ".bro" {
extension := filepath.ext(entry.name)
if !entry.is_dir && (extension == ".bro" || extension == ".hon") {
append(&files, entry)
} else {
os.file_info_delete(entry, state.allocator)
@@ -173,6 +175,9 @@ translate_c_type :: proc(
case .C_Longdouble: translated = types.C_LONGDOUBLE
case .Pointer:
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if child == types.VOID {
child = types.ANYOPAQUE
}
if types.is_valid(child) {
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
translated = types.optional(&state.module.type_store, pointer)
@@ -432,7 +437,7 @@ add_macro_zero_expr :: proc(
return ast.INVALID_EXPR, false
}
return add_import_expr(state, ast.Expr{
kind=.None, span=span,
kind=.Null, span=span,
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
}), true
@@ -675,7 +680,7 @@ add_converted_macro_value_expr :: proc(
}, span), true
case .Null:
return add_import_expr(state, ast.Expr{
kind=.None, span=span,
kind=.Null, span=span,
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
}), true
@@ -964,7 +969,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
name = fmt.tprintf("__c_record_%d", len(state.record_types))
}
record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name)))
_ = types.define_record(&state.module.type_store, record_type, nil, true, true, record.kind == .Union)
_ = types.define_record(&state.module.type_store, record_type, nil, false, true, record.kind == .Union)
append(&state.record_identities, strings.clone(record.identity, state.allocator))
append(&state.record_types, record_type)
}
@@ -1126,7 +1131,42 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
return pkg_id
}
load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id {
resolve_package_imports :: proc(state: ^State, pkg_id: ast.Package_Id) {
if int(pkg_id) >= len(state.module.packages) {
return
}
canonical := state.module.packages[pkg_id].path
import_count := len(state.module.imports)
for import_id in 0..<import_count {
import_item := state.module.imports[import_id]
if import_item.pkg != pkg_id || import_item.target != ast.INVALID_PACKAGE ||
import_item.test_only && (state.mode != .Test || !state.module.packages[pkg_id].test) {
continue
}
if filepath.is_abs(import_item.path) {
state.module.imports[import_id].diagnostic = source.add(state.diagnostics, import_item.span, "absolute import paths are invalid")
state.module.imports[import_id].valid = false
state.module.imports[import_id].target = add_placeholder(state, import_item.path)
continue
}
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else
load_package(state, target_path, import_item.span, include_tests=import_item.test_only)
state.module.imports[import_id].target = target
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
state.module.imports[import_id].valid = false
}
delete(target_path, state.allocator)
}
}
load_package :: proc(
state: ^State,
path: string,
import_span: source.Span,
is_root := false,
include_tests := false,
) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator)
if !ok || !os.is_dir(path) {
if is_root {
@@ -1148,6 +1188,10 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
return id
}
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
if include_tests && !state.module.packages[existing].test {
state.module.packages[existing].test = true
resolve_package_imports(state, existing)
}
delete(canonical, state.allocator)
return existing
}
@@ -1157,6 +1201,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
path=canonical,
name=symbol.intern(state.symbols, filepath.base(canonical)),
available=true,
test=state.mode == .Test && is_root || include_tests,
})
files, files_ok := read_package_files(state, canonical)
if !files_ok {
@@ -1167,7 +1212,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
if is_root {
state.root_failed = true
} else {
source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro files", canonical)
source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro or .hon files", canonical)
state.module.packages[pkg_id].available = false
}
os.file_info_slice_delete(files, state.allocator)
@@ -1200,45 +1245,49 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
}
os.file_info_slice_delete(files, state.allocator)
import_count := len(state.module.imports)
for import_id in 0..<import_count {
import_item := state.module.imports[import_id]
if import_item.pkg != pkg_id || import_item.target != ast.INVALID_PACKAGE {
continue
}
if filepath.is_abs(import_item.path) {
state.module.imports[import_id].diagnostic = source.add(state.diagnostics, import_item.span, "absolute import paths are invalid")
state.module.imports[import_id].valid = false
state.module.imports[import_id].target = add_placeholder(state, import_item.path)
continue
}
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else load_package(state, target_path, import_item.span)
state.module.imports[import_id].target = target
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
state.module.imports[import_id].valid = false
}
delete(target_path, state.allocator)
}
resolve_package_imports(state, pkg_id)
return pkg_id
}
declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
declaration_scopes_overlap :: proc(
left_visibility: types.Visibility,
left_file: ast.File_Id,
right_visibility: types.Visibility,
right_file: ast.File_Id,
) -> bool {
return left_visibility != .File || right_visibility != .File || left_file == right_file
}
declaration_visible_in_file :: proc(visibility: types.Visibility, declaration_file, file: ast.File_Id) -> bool {
return visibility != .File || declaration_file == file
}
declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, file: ast.File_Id, name: symbol.Id) -> bool {
for function in module.functions {
if function.pkg == pkg && function.name == name {
if function.pkg == pkg && function.name == name &&
declaration_visible_in_file(function.visibility, function.file, file) {
return true
}
}
for global in module.globals {
if global.pkg == pkg && global.name == name {
if global.pkg == pkg && global.name == name &&
declaration_visible_in_file(global.visibility, global.file, file) {
return true
}
}
type_id := types.find_named(&module.type_store, u32(pkg), u32(name), file=u32(file))
type_item, type_ok := types.node(&module.type_store, type_id)
if type_ok && type_item.declared {
return true
}
return false
}
validate_imports :: proc(state: ^State) {
for import_item, import_id in state.module.imports {
if import_item.test_only {
continue
}
if !symbol.is_valid(import_item.alias) && import_item.target != ast.INVALID_PACKAGE {
state.module.imports[import_id].alias = state.module.packages[import_item.target].name
}
@@ -1252,7 +1301,7 @@ validate_imports :: proc(state: ^State) {
)
state.module.imports[import_id].valid = false
}
if declaration_conflicts(state.module, import_item.pkg, alias) {
if declaration_conflicts(state.module, import_item.pkg, import_item.file, alias) {
state.module.imports[import_id].diagnostic = source.addf(
state.diagnostics,
import_item.span,
@@ -1278,13 +1327,370 @@ validate_imports :: proc(state: ^State) {
find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id {
for import_item, index in module.imports {
if import_item.file == file && import_item.alias == alias {
if !import_item.test_only && import_item.file == file && import_item.alias == alias {
return ast.import_id(index)
}
}
return ast.INVALID_IMPORT
}
find_visible_enum_global :: proc(
module: ^ast.Module,
pkg: ast.Package_Id,
file: ast.File_Id,
name: symbol.Id,
public_only := false,
) -> ast.Global_Id {
for global, index in module.globals {
if global.pkg == pkg && global.name == name &&
((public_only && global.visibility == .Public) ||
(!public_only && declaration_visible_in_file(global.visibility, global.file, file))) {
return ast.global_id(index)
}
}
return ast.INVALID_GLOBAL
}
eval_enum_global :: proc(
state: ^State,
id: ast.Global_Id,
visiting: []bool,
depth: int,
) -> (i128, bool) {
index := int(id)
if id == ast.INVALID_GLOBAL || index < 0 || index >= len(state.module.globals) ||
depth > 64 || visiting[index] {
return 0, false
}
global := state.module.globals[index]
if !global.immutable || global.external || global.expr == ast.INVALID_EXPR {
return 0, false
}
if types.is_valid(global.type) && !types.is_concrete_integer(global.type) {
return 0, false
}
visiting[index] = true
defer visiting[index] = false
return eval_enum_constant(state, global.expr, global.pkg, global.file, visiting, depth+1)
}
eval_enum_constant :: proc(
state: ^State,
id: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
visiting: []bool,
depth: int,
) -> (i128, bool) {
index := int(id)
if id == ast.INVALID_EXPR || index < 0 || index >= len(state.module.exprs) || depth > 64 {
return 0, false
}
expr := state.module.exprs[index]
#partial switch expr.kind {
case .Integer:
return i128(expr.integer), true
case .Negate:
value, ok := eval_enum_constant(state, expr.left, pkg, file, visiting, depth+1)
return -value, ok
case .Name:
if symbol.is_valid(expr.qualifier) {
import_id := find_type_import(state.module, file, expr.qualifier)
if import_id == ast.INVALID_IMPORT {
return 0, false
}
state.module.imports[import_id].used = true
import_item := state.module.imports[import_id]
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
int(import_item.target) >= len(state.module.packages) ||
!state.module.packages[import_item.target].available {
return 0, false
}
global := find_visible_enum_global(
state.module,
import_item.target,
ast.INVALID_FILE,
expr.name,
public_only=true,
)
return eval_enum_global(state, global, visiting, depth+1)
}
global := find_visible_enum_global(state.module, pkg, file, expr.name)
return eval_enum_global(state, global, visiting, depth+1)
}
return 0, false
}
resolve_enum_values :: proc(state: ^State) {
visiting := make([]bool, len(state.module.globals), state.allocator)
defer delete(visiting, state.allocator)
for declaration in state.module.enum_declarations {
members := types.enum_members_for(&state.module.type_store, declaration.type)
if len(members) != len(declaration.values) {
continue
}
next_value: i128
previous: i128
has_previous := false
previous_known := true
for &member, index in members {
spec := declaration.values[index]
value := next_value
known := true
if spec.explicit {
if spec.expr == ast.INVALID_EXPR {
value = member.value
} else if resolved, ok := eval_enum_constant(
state, spec.expr, declaration.pkg, declaration.file, visiting, 0,
); ok {
value = resolved
} else {
source.add(state.diagnostics, spec.span, "enum value must be an immutable integer constant")
known = false
}
}
if known {
member.value = value
if has_previous && previous_known && value <= previous {
source.add(state.diagnostics, spec.span, "enum values must be strictly increasing")
}
next_value = value+1
} else {
next_value = member.value+1
}
previous = value
previous_known = known
has_previous = true
}
}
}
alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool {
for function in module.functions {
if function.pkg == alias.pkg && function.name == alias.name &&
declaration_scopes_overlap(function.visibility, function.file, alias.visibility, alias.file) {
return true
}
}
for global in module.globals {
if global.pkg == alias.pkg && global.name == alias.name &&
declaration_scopes_overlap(global.visibility, global.file, alias.visibility, alias.file) {
return true
}
}
for item in module.type_store.nodes {
if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) &&
declaration_scopes_overlap(item.visibility, ast.File_Id(item.file), alias.visibility, alias.file) {
return true
}
}
return false
}
direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Declaration_Alias_Kind, u32, int) {
kind := ast.Declaration_Alias_Kind.Invalid
target: u32
kinds := 0
for function, index in module.functions {
if function.pkg == pkg && function.name == name && !function.generated && function.visibility == .Public {
kind = .Function
target = u32(ast.function_id(index))
kinds += 1
break
}
}
for global, index in module.globals {
if global.pkg == pkg && global.name == name && global.visibility == .Public {
kind = .Global
target = u32(ast.global_id(index))
kinds += 1
break
}
}
if value := types.find_named(&module.type_store, u32(pkg), u32(name)); types.is_valid(value) {
if item, ok := types.node(&module.type_store, value); ok && item.declared {
kind = .Type
target = u32(value)
kinds += 1
}
}
return kind, target, kinds
}
non_public_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
for function in module.functions {
if function.pkg == pkg && function.name == name && function.visibility != .Public {
return true
}
}
for global in module.globals {
if global.pkg == pkg && global.name == name && global.visibility != .Public {
return true
}
}
for item in module.type_store.nodes {
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.visibility != .Public {
return true
}
}
for alias in module.aliases {
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility != .Public {
return true
}
}
return false
}
find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int {
for alias, index in module.aliases {
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility == .Public {
return index
}
}
return -1
}
resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bool {
alias := &state.module.aliases[index]
if !alias.valid {
return false
}
if states[index] == 2 {
return alias.kind != .Invalid
}
if states[index] == 1 {
alias.diagnostic = source.addf(
state.diagnostics,
alias.span,
"declaration alias cycle involving '%s'",
symbol.resolve(state.symbols, alias.name),
)
alias.valid = false
return false
}
states[index] = 1
defer states[index] = 2
kind, target, kinds := direct_alias_target(state.module, alias.target_pkg, alias.member)
if kinds > 1 {
alias.diagnostic = source.addf(
state.diagnostics,
alias.span,
"package member '%s.%s' is ambiguous",
symbol.resolve(state.symbols, alias.qualifier),
symbol.resolve(state.symbols, alias.member),
)
alias.valid = false
return false
}
if kinds == 1 {
alias.kind = kind
alias.target = target
return true
}
if target_alias := find_public_alias(state.module, alias.target_pkg, alias.member); target_alias >= 0 {
if resolve_declaration_alias(state, target_alias, states) {
resolved := state.module.aliases[target_alias]
alias.kind = resolved.kind
alias.target = resolved.target
return true
}
alias.valid = false
return false
}
if non_public_alias_target_exists(state.module, alias.target_pkg, alias.member) {
alias.diagnostic = source.addf(
state.diagnostics,
alias.span,
"package member '%s.%s' is not public",
symbol.resolve(state.symbols, alias.qualifier),
symbol.resolve(state.symbols, alias.member),
)
} else {
alias.diagnostic = source.addf(
state.diagnostics,
alias.span,
"package '%s' has no member '%s'",
symbol.resolve(state.symbols, alias.qualifier),
symbol.resolve(state.symbols, alias.member),
)
}
alias.valid = false
return false
}
validate_declaration_aliases :: proc(state: ^State) {
for &alias, index in state.module.aliases {
name := symbol.resolve(state.symbols, alias.name)
if alias_conflicts_with_declaration(state.module, alias) {
alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with a package declaration", name)
alias.valid = false
continue
}
for previous in state.module.aliases[:index] {
if previous.pkg == alias.pkg && previous.name == alias.name &&
declaration_scopes_overlap(previous.visibility, previous.file, alias.visibility, alias.file) {
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name)
alias.valid = false
break
}
}
if !alias.valid {
continue
}
for import_item in state.module.imports {
if import_item.file == alias.file && import_item.alias == alias.name {
alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with an import", name)
alias.valid = false
break
}
}
if !alias.valid {
continue
}
import_id := find_type_import(state.module, alias.file, alias.qualifier)
if import_id == ast.INVALID_IMPORT {
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown symbol '%s'", symbol.resolve(state.symbols, alias.qualifier))
alias.valid = false
continue
}
state.module.imports[import_id].used = true
import_item := state.module.imports[import_id]
alias.target_pkg = import_item.target
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
int(import_item.target) >= len(state.module.packages) || !state.module.packages[import_item.target].available {
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unavailable imported package '%s'", symbol.resolve(state.symbols, alias.qualifier))
alias.valid = false
}
}
states := make([]u8, len(state.module.aliases), state.allocator)
defer delete(states, state.allocator)
for _, index in state.module.aliases {
_ = resolve_declaration_alias(state, index, states)
}
for &alias in state.module.aliases {
if !alias.valid || alias.kind != .Type {
continue
}
id := types.named(
&state.module.type_store,
u32(alias.pkg),
u32(alias.name),
file=u32(alias.file),
visibility=alias.visibility,
)
if !types.define_alias(&state.module.type_store, id, types.Type(alias.target), alias.visibility) {
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name))
alias.valid = false
}
}
}
canonical_type :: proc(
module: ^ast.Module,
value: types.Type,
@@ -1354,6 +1760,19 @@ canonical_type :: proc(
mapping[index] = resolved
return resolved
}
if item.kind == .Sum {
left := canonical_type(module, item.child, mapping, visiting)
right := canonical_type(module, item.extra, mapping, visiting)
if resolved, compose_error := types.compose_sum(&module.type_store, left, right); compose_error == .None {
mapping[index] = resolved
return resolved
}
item.child = left
item.extra = right
resolved := types.intern(&module.type_store, item)
mapping[index] = resolved
return resolved
}
if item.kind == .Function {
params := make([]types.Type, int(item.field_count), context.temp_allocator)
for param, param_index in types.params_for(&module.type_store, value) {
@@ -1375,6 +1794,81 @@ canonical_type :: proc(
return resolved
}
diagnose_qualified_type_uses :: proc(state: ^State) {
module := state.module
for &type_use in module.type_uses {
item, ok := types.node(&module.type_store, type_use.type)
if !ok || item.qualifier == 0 || item.kind != .Named {
continue
}
qualifier := symbol.Id(item.qualifier)
name := symbol.Id(item.name)
import_id := find_type_import(module, type_use.file, qualifier)
if import_id == ast.INVALID_IMPORT {
type_use.diagnostic = source.addf(
state.diagnostics,
type_use.span,
"unknown symbol '%s'",
symbol.resolve(state.symbols, qualifier),
)
source.set_primary_label(state.diagnostics, type_use.diagnostic, "unknown symbol")
continue
}
import_item := module.imports[import_id]
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
int(import_item.target) >= len(module.packages) || !module.packages[import_item.target].available {
type_use.diagnostic = source.addf(
state.diagnostics,
type_use.span,
"unavailable imported package '%s'",
symbol.resolve(state.symbols, qualifier),
)
continue
}
if !types.is_valid(types.find_named(&module.type_store, u32(import_item.target), u32(name))) {
type_use.diagnostic = source.addf(
state.diagnostics,
type_use.span,
"package '%s' has no member '%s'",
symbol.resolve(state.symbols, qualifier),
symbol.resolve(state.symbols, name),
)
}
}
}
type_resolution_diagnostic :: proc(module: ^ast.Module, value: types.Type, file: ast.File_Id, depth := 0) -> source.Diagnostic_Id {
if depth > 64 {
return source.INVALID_DIAGNOSTIC
}
for type_use in module.type_uses {
if type_use.file == file && type_use.type == value && type_use.diagnostic != source.INVALID_DIAGNOSTIC {
return type_use.diagnostic
}
}
item, ok := types.node(&module.type_store, value)
if !ok {
return source.INVALID_DIAGNOSTIC
}
if diagnostic := type_resolution_diagnostic(module, item.child, file, depth+1);
diagnostic != source.INVALID_DIAGNOSTIC {
return diagnostic
}
if diagnostic := type_resolution_diagnostic(module, item.extra, file, depth+1);
diagnostic != source.INVALID_DIAGNOSTIC {
return diagnostic
}
if item.kind == .Function {
for param in types.params_for(&module.type_store, value) {
if diagnostic := type_resolution_diagnostic(module, param.type, file, depth+1);
diagnostic != source.INVALID_DIAGNOSTIC {
return diagnostic
}
}
}
return source.INVALID_DIAGNOSTIC
}
canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
original_count := len(module.type_store.nodes)
mapping := make([]types.Type, original_count, allocator)
@@ -1382,6 +1876,19 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
defer delete(mapping, allocator)
defer delete(visiting, allocator)
for &function in module.functions {
for param in function.params {
if diagnostic := type_resolution_diagnostic(module, param.type, function.file);
diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC {
function.diagnostic = diagnostic
}
}
signature_results := [2]types.Type{function.result, function.error}
for value in signature_results {
if diagnostic := type_resolution_diagnostic(module, value, function.file);
diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC {
function.diagnostic = diagnostic
}
}
for &param in function.params {
param.type = canonical_type(module, param.type, mapping, visiting)
}
@@ -1389,11 +1896,25 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
function.error = canonical_type(module, function.error, mapping, visiting)
}
for &global in module.globals {
if diagnostic := type_resolution_diagnostic(module, global.type, global.file);
diagnostic != source.INVALID_DIAGNOSTIC && global.diagnostic == source.INVALID_DIAGNOSTIC {
global.diagnostic = diagnostic
}
global.type = canonical_type(module, global.type, mapping, visiting)
}
for &statement in module.statements {
if diagnostic := type_resolution_diagnostic(module, statement.type, ast.File_Id(statement.span.file));
diagnostic != source.INVALID_DIAGNOSTIC && statement.diagnostic == source.INVALID_DIAGNOSTIC {
statement.diagnostic = diagnostic
}
statement.type = canonical_type(module, statement.type, mapping, visiting)
}
for &field in module.type_fields {
field.type = canonical_type(module, field.type, mapping, visiting)
}
for &field_default in module.struct_field_defaults {
field_default.record = canonical_type(module, field_default.record, mapping, visiting)
}
for index := 0; index < original_count; index += 1 {
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
}
@@ -1411,11 +1932,14 @@ load :: proc(
allocator := context.allocator,
c_options := cimport.Options{},
selected := target.DEFAULT,
project_root_path := "",
mode := ast.Compile_Mode.Executable,
) -> (ast.Module, bool) {
module := ast.init_module(allocator)
project_root, project_root_ok := filepath.abs(".", allocator)
project_root_source := project_root_path if len(project_root_path) > 0 else root_path
project_root, project_root_ok := filepath.abs(project_root_source, allocator)
if !project_root_ok {
project_root = strings.clone(".", allocator)
project_root = strings.clone(project_root_source, allocator)
}
state := State{
module=&module,
@@ -1427,6 +1951,7 @@ load :: proc(
c_options=c_options,
selected=selected,
project_root=project_root,
mode=mode,
}
state.record_identities.allocator = allocator
state.record_types.allocator = allocator
@@ -1442,7 +1967,17 @@ load :: proc(
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
state.root_failed = true
}
if mode == .Test {
testing_path, testing_error := filepath.join({project_root, "std", "testing"}, allocator)
if testing_error == nil {
_ = load_package(&state, testing_path, source.Span{})
delete(testing_path, allocator)
}
}
validate_imports(&state)
validate_declaration_aliases(&state)
resolve_enum_values(&state)
diagnose_qualified_type_uses(&state)
canonicalize_types(&module, allocator)
return module, !state.root_failed
}
+475 -138
View File
@@ -244,9 +244,9 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .None:
case .Null:
return append_instruction(state, ir.Instruction{
op=.None, span=expr.span, type=expr.type,
op=.Null, span=expr.span, type=expr.type,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -396,11 +396,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
success_lbl := fresh_label(state)
error_lbl := fresh_label(state)
merge_lbl := fresh_label(state)
slot := append_instruction(state, ir.Instruction{
op=.Alloca, span=expr.span, type=success,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
slot := ir.INVALID_INSTRUCTION
if !types.is_void(success) {
slot = append_instruction(state, ir.Instruction{
op=.Alloca, span=expr.span, type=success,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=expr.span, type=types.VOID,
integer=success_lbl, target=ir.Ref(u32(error_lbl)), a=ok,
@@ -413,10 +416,11 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
})
if expr.kind == .Try {
result := channel
if !types.equal(channel_type, state.func_result) {
error_value := ir.INVALID_INSTRUCTION
if !types.equal(channel_type, state.func_result) || len(expr.args) > 0 {
error_type := types.fallible_error(channel_type, &state.hir_module.types)
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
error_value := append_instruction(state, ir.Instruction{
error_value = append_instruction(state, ir.Instruction{
op=.Fallible_Error, span=expr.span, type=error_type,
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -428,23 +432,44 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
args := make([]ir.Instruction_Id, 1, state.allocator)
args[0] = error_value
result = append_instruction(state, ir.Instruction{
op=.Aggregate, span=expr.span, type=state.func_result, integer=1,
args=args, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
if !types.equal(channel_type, state.func_result) {
args := make([]ir.Instruction_Id, 1, state.allocator)
args[0] = error_value
result = append_instruction(state, ir.Instruction{
op=.Aggregate, span=expr.span, type=state.func_result, integer=1,
args=args, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
for encoded_capture in expr.args {
capture := hir.Local_Id(encoded_capture)
if capture == hir.INVALID_LOCAL || int(capture) >= len(state.func_locals) {
continue
}
error_type := state.func_locals[capture].type
capture_slot := append_instruction(state, ir.Instruction{
op=.Alloca, span=expr.span, type=error_type,
target=ir.local_ref(ir.Local_Id(capture)),
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
state.local_slots[capture] = capture_slot
append_instruction(state, ir.Instruction{
op=.Store, span=expr.span, type=error_type,
target=ir.INVALID_REF, a=capture_slot, b=error_value,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
lower_statements(state, expr.body)
append_instruction(state, ir.Instruction{
op=.Return, span=expr.span, type=state.func_result,
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
if expr.integer != 0 {
capture := hir.as_local(expr.target)
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
capture := hir.as_local(expr.target)
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
error_type := state.func_locals[capture].type
error_value := append_instruction(state, ir.Instruction{
op=.Fallible_Error, span=expr.span, type=error_type,
@@ -463,19 +488,40 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
target=ir.INVALID_REF, a=capture_slot, b=error_value,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
lower_statements(state, expr.body)
}
fallback := lower_nested_expr(state, expr.right)
append_instruction(state, ir.Instruction{
op=.Store, span=expr.span, type=success,
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if expr.integer != hir.CATCH_EXPRESSION {
lower_statements(state, expr.body)
if expr.integer == hir.CATCH_VOID_FALLTHROUGH {
append_instruction(state, ir.Instruction{
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else if expr.right == hir.INVALID_EXPR {
// A terminating handler can still end in a synthetic match/if merge label
// after its returns. Seal that unreachable continuation before the success
// label so LLVM never sees adjacent basic-block labels.
append_instruction(state, ir.Instruction{
op=.Trap, span=expr.span, type=types.NORETURN, integer=1,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if expr.right != hir.INVALID_EXPR {
fallback := lower_nested_expr(state, expr.right)
if !types.is_void(success) {
append_instruction(state, ir.Instruction{
op=.Store, span=expr.span, type=success,
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append_instruction(state, ir.Instruction{
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
append_instruction(state, ir.Instruction{
op=.Label, span=expr.span, type=types.VOID, integer=success_lbl,
@@ -503,11 +549,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
merge := append_instruction(state, ir.Instruction{
op=.Label, span=expr.span, type=types.VOID, integer=merge_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.is_void(success) {
return merge
}
return append_instruction(state, ir.Instruction{
op=.Load, span=expr.span, type=success,
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
@@ -521,6 +570,25 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
left_expr := state.hir_module.exprs[expr.left]
right_expr := state.hir_module.exprs[expr.right]
if left_expr.kind == .Null || right_expr.kind == .Null {
optional_expr := expr.right if left_expr.kind == .Null else expr.left
optional := lower_nested_expr(state, optional_expr)
present := append_instruction(state, ir.Instruction{
op=.Optional_Is_Some, span=expr.span, type=types.BOOL,
target=ir.INVALID_REF, a=optional, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if expr.kind == .Ne {
return present
}
return append_instruction(state, ir.Instruction{
op=.Not, span=expr.span, type=types.BOOL,
target=ir.INVALID_REF, a=present, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
left := lower_nested_expr(state, expr.left)
right := lower_nested_expr(state, expr.right)
predicate := ir.Compare_Predicate.Eq
@@ -588,15 +656,38 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
}
memory_region_element_type :: proc(value: types.Type, store: ^types.Store) -> types.Type {
resolved := types.resolve_alias(value, store)
item, ok := types.node(store, resolved)
if !ok {
return types.INVALID
}
if item.kind == .Slice {
return item.child
}
if item.kind == .Pointer && !item.many {
array, array_ok := types.node(store, types.resolve_alias(item.child, store))
if array_ok && array.kind == .Array {
return array.child
}
}
return types.INVALID
}
lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
stack := state.expr_stack
state.expr_stack = nil
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.args, state.allocator)
}
clear_dynamic_array(&stack)
state.expr_stack = stack
if state.expr_stack == nil {
state.expr_stack = stack
} else {
delete(stack)
}
}
append(&stack, Lower_Expr_Frame{expr=expr_id})
last := ir.INVALID_INSTRUCTION
@@ -614,13 +705,34 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Invalid:
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
_ = pop(&stack)
case .Void:
last = append_instruction(state, ir.Instruction{
op=.Const, span=expr.span, type=types.VOID,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
case .Integer, .Float, .Bool:
last = append_instruction(state, ir.Instruction{
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
case .String, .Array, .Struct, .Range, .None, .Optional_Some, .Address, .Deref,
case .Undefined:
last = append_instruction(state, ir.Instruction{
op=.Poison, span=expr.span, type=expr.type,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
case .Unreachable:
last = append_instruction(state, ir.Instruction{
op=.Trap, span=expr.span, type=types.NORETURN, integer=1,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
case .String, .Array, .Struct, .Range, .Null, .Optional_Some, .Address, .Deref,
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Try, .Catch, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
last = lower_compound_expr(state, frame.expr)
@@ -665,13 +777,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
case .Widen, .Sum_Widen, .Sum_Project, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Const_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
case .Negate, .Bit_Not:
stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add, .Sub, .Mul, .Div, .Pointer_Add:
case .Add, .Sub, .Mul, .Div, .Div_Trunc, .Div_Floor, .Div_Exact, .Div_Ceil,
.Rem, .Mod, .Pointer_Add, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
.Shift_Right, .Shift_Left_Saturating, .Mem_Copy, .Mem_Set:
stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call:
@@ -709,8 +823,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
continue
}
if frame.stage == 5 {
op := ir.Opcode.Neg_Checked
if expr.kind == .Bit_Not {
op = .Bit_Not
}
last = append_instruction(state, ir.Instruction{
op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF,
op=op, span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
@@ -720,6 +838,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
op := ir.Opcode.Widen
#partial switch expr.kind {
case .Sum_Widen: op = .Sum_Widen
case .Sum_Project: op = .Sum_Project
case .Weaken_Pointer: op = .Weaken_Pointer
case .Weaken_Slice: op = .Weaken_Slice
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
@@ -727,6 +846,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .C_Vararg_Promote: op = .C_Vararg_Promote
case .Retype: op = .Retype
case .Scalar_Cast: op = .Scalar_Cast
case .Pointer_Cast: op = .Pointer_Cast
case .Const_Cast: op = .Const_Cast
case: op = .Widen
}
last = append_instruction(state, ir.Instruction{
@@ -745,15 +866,34 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}
if frame.stage == 3 {
op := ir.Opcode.Add_Checked
result_type := expr.type
#partial switch expr.kind {
case .Sub: op = .Sub_Checked
case .Mul: op = .Mul_Checked
case .Div: op = .Div_Checked
case .Div_Trunc: op = .Div_Trunc_Checked
case .Div_Floor: op = .Div_Floor_Checked
case .Div_Exact: op = .Div_Exact_Checked
case .Div_Ceil: op = .Div_Ceil_Checked
case .Rem: op = .Rem_Checked
case .Mod: op = .Mod_Checked
case .Pointer_Add: op = .Pointer_Add
case .Bit_And: op = .Bit_And
case .Bit_Or: op = .Bit_Or
case .Bit_Xor: op = .Bit_Xor
case .Shift_Left: op = .Shift_Left
case .Shift_Right: op = .Shift_Right
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
case .Mem_Copy:
op = .Mem_Copy
result_type = memory_region_element_type(state.hir_module.exprs[expr.left].type, &state.hir_module.types)
case .Mem_Set:
op = .Mem_Set
result_type = memory_region_element_type(state.hir_module.exprs[expr.left].type, &state.hir_module.types)
}
last = append_instruction(state, ir.Instruction{
op=op,
span=expr.span, type=expr.type, target=ir.INVALID_REF,
span=expr.span, type=result_type, target=ir.INVALID_REF,
a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
@@ -784,6 +924,101 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
return last
}
lower_conditional_unwrap_header :: proc(
state: ^State,
statement: hir.Stmt,
success_lbl, false_lbl: i64,
) {
for unwrap in statement.unwraps {
optional := lower_expr(state, unwrap.expr)
present := append_instruction(state, ir.Instruction{
op=.Optional_Is_Some,
span=statement.span,
type=types.BOOL,
target=ir.INVALID_REF,
a=optional,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
next_lbl := fresh_label(state)
append_instruction(state, ir.Instruction{
op=.Cond_Br,
span=statement.span,
type=types.VOID,
a=present,
integer=next_lbl,
target=ir.Ref(u32(false_lbl)),
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label,
span=statement.span,
type=types.VOID,
integer=next_lbl,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if unwrap.local != hir.INVALID_LOCAL && int(unwrap.local) < len(state.func_locals) {
local := state.func_locals[unwrap.local]
slot := append_instruction(state, ir.Instruction{
op=.Alloca,
span=statement.span,
type=local.type,
target=ir.local_ref(ir.Local_Id(unwrap.local)),
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
state.local_slots[unwrap.local] = slot
inner := append_instruction(state, ir.Instruction{
op=.Optional_Value,
span=statement.span,
type=local.type,
target=ir.INVALID_REF,
a=optional,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Store,
span=statement.span,
type=local.type,
target=ir.INVALID_REF,
a=slot,
b=inner,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if statement.guard != hir.INVALID_EXPR {
guard := lower_expr(state, statement.guard)
append_instruction(state, ir.Instruction{
op=.Cond_Br,
span=statement.span,
type=types.VOID,
a=guard,
integer=success_lbl,
target=ir.Ref(u32(false_lbl)),
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
append_instruction(state, ir.Instruction{
op=.Br,
span=statement.span,
type=types.VOID,
integer=success_lbl,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
hir_module := state.hir_module
for statement_id in statements {
@@ -856,6 +1091,12 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
case .Mul: op = .Mul_Checked
case .Div: op = .Div_Checked
case .Pointer_Add: op = .Pointer_Add
case .Bit_And: op = .Bit_And
case .Bit_Or: op = .Bit_Or
case .Bit_Xor: op = .Bit_Xor
case .Shift_Left: op = .Shift_Left
case .Shift_Right: op = .Shift_Right
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
}
value := append_instruction(state, ir.Instruction{
op=op, span=statement.span, type=target_type,
@@ -909,6 +1150,9 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
})
} else {
value := lower_expr(state, statement.expr)
if types.is_noreturn(state.hir_module.exprs[statement.expr].type) {
continue
}
append_instruction(state, ir.Instruction{
op=.Return,
span=statement.span,
@@ -987,97 +1231,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
false_target := else_lbl if has_else else merge_lbl
if len(statement.unwraps) > 0 {
// Evaluate each optional exactly once, entering the next operand only
// after the previous one is present. Capture storage is initialized in
// these success blocks so the optional guard can use every binding.
for unwrap in statement.unwraps {
optional := lower_expr(state, unwrap.expr)
present := append_instruction(state, ir.Instruction{
op=.Optional_Is_Some,
span=statement.span,
type=types.BOOL,
target=ir.INVALID_REF,
a=optional,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
success_lbl := fresh_label(state)
append_instruction(state, ir.Instruction{
op=.Cond_Br,
span=statement.span,
type=types.VOID,
a=present,
integer=success_lbl,
target=ir.Ref(u32(false_target)),
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Label,
span=statement.span,
type=types.VOID,
integer=success_lbl,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if unwrap.local != hir.INVALID_LOCAL && int(unwrap.local) < len(state.func_locals) {
local := state.func_locals[unwrap.local]
slot := append_instruction(state, ir.Instruction{
op=.Alloca,
span=statement.span,
type=local.type,
target=ir.local_ref(ir.Local_Id(unwrap.local)),
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
state.local_slots[unwrap.local] = slot
inner := append_instruction(state, ir.Instruction{
op=.Optional_Value,
span=statement.span,
type=local.type,
target=ir.INVALID_REF,
a=optional,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append_instruction(state, ir.Instruction{
op=.Store,
span=statement.span,
type=local.type,
target=ir.INVALID_REF,
a=slot,
b=inner,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if statement.guard != hir.INVALID_EXPR {
guard := lower_expr(state, statement.guard)
append_instruction(state, ir.Instruction{
op=.Cond_Br,
span=statement.span,
type=types.VOID,
a=guard,
integer=then_lbl,
target=ir.Ref(u32(false_target)),
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
append_instruction(state, ir.Instruction{
op=.Br,
span=statement.span,
type=types.VOID,
integer=then_lbl,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
lower_conditional_unwrap_header(state, statement, then_lbl, false_target)
} else {
cond := lower_expr(state, statement.expr)
append_instruction(state, ir.Instruction{
@@ -1133,12 +1287,16 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
condition := lower_expr(state, statement.expr)
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=statement.span, type=types.VOID,
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
if len(statement.unwraps) > 0 {
lower_conditional_unwrap_header(state, statement, body_lbl, exit_lbl)
} else {
condition := lower_expr(state, statement.expr)
append_instruction(state, ir.Instruction{
op=.Cond_Br, span=statement.span, type=types.VOID,
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
@@ -1552,11 +1710,22 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
}
lower_statements(&state, function.body)
if len(state.instructions) == 0 ||
(state.instructions[len(state.instructions)-1].op != .Return &&
state.instructions[len(state.instructions)-1].op != .Return_Void) {
last_terminates := false
if len(state.instructions) > 0 {
last_instruction := state.instructions[len(state.instructions)-1]
last_terminates = last_instruction.op == .Return || last_instruction.op == .Return_Void ||
last_instruction.op == .Trap ||
last_instruction.op == .Call && types.is_noreturn(last_instruction.type)
}
if !last_terminates {
if types.is_void(function.result) {
append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC})
} else if types.is_noreturn(function.result) {
append_instruction(&state, ir.Instruction{
op=.Trap, type=types.NORETURN, integer=1,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
value := append_instruction(&state, ir.Instruction{
op=.Const,
@@ -1590,6 +1759,172 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al
return state.instructions[:]
}
append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, allocator: mem.Allocator) {
main_index, main_ok := hir.index(hir_module.injected_main, hir.INVALID_FUNCTION, len(hir_module.functions))
if !main_ok {
return
}
main_function := &hir_module.functions[main_index]
provider_index, provider_ok := hir.index(hir_module.io_provider, hir.INVALID_FUNCTION, len(hir_module.functions))
param_index := -1
if len(main_function.params) == 1 {
param_index, main_ok = hir.index(main_function.params[0], hir.INVALID_LOCAL, len(main_function.locals))
if !main_ok || !provider_ok {
return
}
} else if len(main_function.params) != 0 {
return
}
instructions: [dynamic]ir.Instruction
instructions.allocator = allocator
args: []ir.Instruction_Id
if param_index >= 0 {
provider_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Call,
type=hir_module.functions[provider_index].result,
target=ir.function_ref(ir.Function_Id(provider_index)),
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
init_args := make([]ir.Instruction_Id, 1, allocator)
init_args[0] = provider_call
init_value := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Aggregate,
type=main_function.locals[param_index].type,
args=init_args,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
args = make([]ir.Instruction_Id, 1, allocator)
args[0] = init_value
}
main_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Call,
type=main_function.result,
args=args,
target=ir.function_ref(ir.Function_Id(main_index)),
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.kind(main_function.result, &hir_module.types) == .Fallible {
success := types.fallible_success(main_function.result, &hir_module.types)
channel_slot := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Alloca, type=main_function.result, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append(&instructions, ir.Instruction{
op=.Store, type=main_function.result, target=ir.INVALID_REF,
a=channel_slot, b=main_call, diagnostic=source.INVALID_DIAGNOSTIC,
})
code := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Union_Tag, type=types.U16, target=ir.INVALID_REF,
a=channel_slot, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
zero_tag := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Const, type=types.U16, integer=0, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
ok := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Compare, type=types.BOOL, integer=i64(ir.Compare_Predicate.Eq),
target=ir.INVALID_REF, a=code, b=zero_tag,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append(&instructions, ir.Instruction{
op=.Cond_Br, type=types.VOID, integer=1, target=ir.Ref(0), a=ok,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
append(&instructions, ir.Instruction{
op=.Label, type=types.VOID, integer=0, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
failure := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Const, type=types.I32, integer=1, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
append(&instructions, ir.Instruction{
op=.Return, type=types.I32, target=ir.INVALID_REF, a=failure,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
append(&instructions, ir.Instruction{
op=.Label, type=types.VOID, integer=1, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
success_value := ir.INVALID_INSTRUCTION
if types.is_void(success) {
success_value = ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Const, type=types.I32, integer=0, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
payload := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Field_Address, type=success, integer=0, target=ir.INVALID_REF,
a=channel_slot, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
success_value = ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Load, type=success, target=ir.INVALID_REF, a=payload,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append(&instructions, ir.Instruction{
op=.Return, type=types.I32, target=ir.INVALID_REF, a=success_value,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
exit_value := main_call
if types.is_void(main_function.result) {
exit_value = ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Const, type=types.I32, integer=0, target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append(&instructions, ir.Instruction{
op=.Return, type=types.I32, target=ir.INVALID_REF, a=exit_value,
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
problematic := main_function.problematic
if provider_ok {
problematic = problematic || hir_module.functions[provider_index].problematic
}
append(&module.functions, ir.Function{
link_name=fmt.aprintf("main", allocator=allocator),
calling_convention=.C,
implementation=.Definition,
linkage=.External,
is_main=true,
result=types.I32,
instructions=instructions[:],
problematic=problematic,
})
}
lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module {
module := ir.init_module(hir_module.target, allocator)
types.destroy_store(&module.types)
@@ -1604,6 +1939,7 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
link_name=fmt.aprintf("%s", global.link_name, allocator=allocator),
type=global.type,
is_static=global.is_static,
eager=global.eager,
external=global.external,
writable=global.writable,
static_value=global.static_value,
@@ -1631,5 +1967,6 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
problematic=function.problematic,
})
}
append_injected_main(&module, hir_module, allocator)
return module
}
+1109 -156
View File
File diff suppressed because it is too large Load Diff
+269 -21
View File
@@ -2,6 +2,7 @@ package source
import "core:fmt"
import "core:mem"
import "core:strings"
Source_Id :: distinct u32
Diagnostic_Id :: distinct u32
@@ -53,21 +54,43 @@ Store :: struct {
}
Diagnostic :: struct {
span: Span,
message: string,
severity: Severity,
}
Annotation_Kind :: enum u8 {
Primary,
Secondary,
Note,
Help,
}
Annotation :: struct {
owner: Diagnostic_Id,
span: Span,
message: string,
kind: Annotation_Kind,
}
Severity :: enum u8 {
Error,
Warning,
}
Diagnostics :: struct {
source: ^Source,
store: ^Store,
items: [dynamic]Diagnostic,
annotations: [dynamic]Annotation,
index: map[Diagnostic_Key]Diagnostic_Id,
allocator: mem.Allocator,
}
Diagnostic_Key :: struct {
span: Span,
message: string,
span: Span,
message: string,
severity: Severity,
}
init_store :: proc(allocator := context.allocator) -> Store {
@@ -123,6 +146,7 @@ init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -
result.source = source_file
result.allocator = allocator
result.items.allocator = allocator
result.annotations.allocator = allocator
result.index.allocator = allocator
return result
}
@@ -132,6 +156,7 @@ init_store_diagnostics :: proc(store: ^Store, allocator := context.allocator) ->
result.store = store
result.allocator = allocator
result.items.allocator = allocator
result.annotations.allocator = allocator
result.index.allocator = allocator
return result
}
@@ -141,34 +166,98 @@ destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
for diagnostic in diagnostics.items {
delete(diagnostic.message, diagnostics.allocator)
}
for annotation in diagnostics.annotations {
delete(annotation.message, diagnostics.allocator)
}
delete(diagnostics.items)
delete(diagnostics.annotations)
}
add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> Diagnostic_Id {
key := Diagnostic_Key{span=span, message=message}
add_with_severity :: proc(diagnostics: ^Diagnostics, span: Span, message: string, severity: Severity) -> Diagnostic_Id {
key := Diagnostic_Key{span=span, message=message, severity=severity}
if id, ok := diagnostics.index[key]; ok {
return id
}
id := diagnostic_id(len(diagnostics.items))
cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator)
append(&diagnostics.items, Diagnostic{span=span, message=cloned})
diagnostics.index[Diagnostic_Key{span=span, message=cloned}] = id
append(&diagnostics.items, Diagnostic{span=span, message=cloned, severity=severity})
diagnostics.index[Diagnostic_Key{span=span, message=cloned, severity=severity}] = id
return id
}
addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> Diagnostic_Id {
add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> Diagnostic_Id {
return add_with_severity(diagnostics, span, message, .Error)
}
add_warning :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> Diagnostic_Id {
return add_with_severity(diagnostics, span, message, .Warning)
}
addf_with_severity :: proc(diagnostics: ^Diagnostics, span: Span, severity: Severity, format: string, args: ..any) -> Diagnostic_Id {
message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator)
key := Diagnostic_Key{span=span, message=message}
key := Diagnostic_Key{span=span, message=message, severity=severity}
if id, ok := diagnostics.index[key]; ok {
delete(message, diagnostics.allocator)
return id
}
id := diagnostic_id(len(diagnostics.items))
append(&diagnostics.items, Diagnostic{span=span, message=message})
diagnostics.index[Diagnostic_Key{span=span, message=message}] = id
append(&diagnostics.items, Diagnostic{span=span, message=message, severity=severity})
diagnostics.index[Diagnostic_Key{span=span, message=message, severity=severity}] = id
return id
}
addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> Diagnostic_Id {
return addf_with_severity(diagnostics, span, .Error, format, ..args)
}
addf_warning :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> Diagnostic_Id {
return addf_with_severity(diagnostics, span, .Warning, format, ..args)
}
add_annotation :: proc(
diagnostics: ^Diagnostics,
owner: Diagnostic_Id,
kind: Annotation_Kind,
span: Span,
message: string,
) {
if _, ok := diagnostic_index(owner, len(diagnostics.items)); !ok {
return
}
for annotation in diagnostics.annotations {
if annotation.owner == owner && annotation.kind == kind && annotation.span == span &&
annotation.message == message {
return
}
}
append(&diagnostics.annotations, Annotation{
owner=owner,
kind=kind,
span=span,
message=fmt.aprintf("%s", message, allocator=diagnostics.allocator),
})
}
set_primary_label :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, message: string) {
index, ok := diagnostic_index(owner, len(diagnostics.items))
if !ok {
return
}
add_annotation(diagnostics, owner, .Primary, diagnostics.items[index].span, message)
}
add_secondary_label :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, span: Span, message: string) {
add_annotation(diagnostics, owner, .Secondary, span, message)
}
add_note :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, message: string) {
add_annotation(diagnostics, owner, .Note, {}, message)
}
add_help :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, message: string) {
add_annotation(diagnostics, owner, .Help, {}, message)
}
line_and_column :: proc(source_file: ^Source, offset: Offset) -> (line, column: int) {
if len(source_file.line_starts) > 0 {
limit := min(int(offset), len(source_file.text))
@@ -209,6 +298,140 @@ source_for_span :: proc(diagnostics: ^Diagnostics, span: Span) -> ^Source {
return diagnostics.source
}
annotation_for :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, kind: Annotation_Kind) -> (Annotation, bool) {
for annotation in diagnostics.annotations {
if annotation.owner == owner && annotation.kind == kind {
return annotation, true
}
}
return {}, false
}
line_bounds :: proc(source_file: ^Source, line: int) -> (start, end: int, ok: bool) {
if line <= 0 {
return 0, 0, false
}
if len(source_file.line_starts) == 0 {
current := 1
start = 0
for value, index in transmute([]byte)source_file.text {
if current == line && value == '\n' {
end = index
if end > start && source_file.text[end-1] == '\r' {
end -= 1
}
return start, end, true
}
if value == '\n' {
current += 1
start = index+1
}
}
if current == line {
return start, len(source_file.text), true
}
return 0, 0, false
}
if line > len(source_file.line_starts) {
return 0, 0, false
}
start = int(source_file.line_starts[line-1])
end = len(source_file.text)
if line < len(source_file.line_starts) {
end = int(source_file.line_starts[line])-1
}
if end > start && source_file.text[end-1] == '\r' {
end -= 1
}
return start, end, true
}
write_spaces :: proc(builder: ^strings.Builder, count: int) {
for _ in 0..<max(count, 0) {
strings.write_byte(builder, ' ')
}
}
write_expanded :: proc(builder: ^strings.Builder, text: string, start_column := 0) -> int {
column := start_column
for value in transmute([]byte)text {
if value == '\t' {
width := 4-column%4
write_spaces(builder, width)
column += width
} else {
strings.write_byte(builder, value)
column += 1
}
}
return column
}
display_width :: proc(text: string, start_column := 0) -> int {
column := start_column
for value in transmute([]byte)text {
column += 4-column%4 if value == '\t' else 1
}
return column-start_column
}
decimal_width :: proc(value: int) -> int {
width := 1
for remaining := value; remaining >= 10; remaining /= 10 {
width += 1
}
return width
}
write_excerpt :: proc(
builder: ^strings.Builder,
diagnostics: ^Diagnostics,
span: Span,
label: string,
primary: bool,
) -> bool {
if span == (Span{}) {
return false
}
source_file := source_for_span(diagnostics, span)
if source_file == nil {
return false
}
line, column := line_and_column(source_file, span.start)
line_start, line_end, ok := line_bounds(source_file, line)
if !ok {
return false
}
prefix := " -->" if primary else " :::"
fmt.sbprintf(builder, "%s %s:%d:%d\n", prefix, source_file.path, line, column)
gutter := decimal_width(line)
write_spaces(builder, gutter+1)
strings.write_string(builder, "|\n")
fmt.sbprintf(builder, "%*d | ", gutter, line)
_ = write_expanded(builder, source_file.text[line_start:line_end])
strings.write_byte(builder, '\n')
write_spaces(builder, gutter+1)
strings.write_string(builder, "| ")
start := clamp(int(span.start), line_start, line_end)
indent := display_width(source_file.text[line_start:start])
width := 1
if start < line_end {
end := clamp(int(span.end), start+1, line_end)
width = max(display_width(source_file.text[start:end], indent), 1)
}
write_spaces(builder, indent)
marker := u8('^') if primary else u8('-')
for _ in 0..<width {
strings.write_byte(builder, marker)
}
if len(label) > 0 {
strings.write_byte(builder, ' ')
strings.write_string(builder, label)
}
strings.write_byte(builder, '\n')
return true
}
format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := context.allocator) -> string {
index, ok := diagnostic_index(id, len(diagnostics.items))
if !ok {
@@ -222,18 +445,43 @@ format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := contex
}
diagnostic := diagnostics.items[index]
source_file := source_for_span(diagnostics, diagnostic.span)
if source_file == nil {
return fmt.aprintf("<unknown>: error: %s", diagnostic.message, allocator=allocator)
severity := "warning" if diagnostic.severity == .Warning else "error"
if diagnostic.span == (Span{}) {
path := source_file.path if source_file != nil else "<unknown>"
return fmt.aprintf("%s: %s: %s", path, severity, diagnostic.message, allocator=allocator)
}
line, column := line_and_column(source_file, diagnostic.span.start)
return fmt.aprintf(
"%s:%d:%d: error: %s",
source_file.path,
line,
column,
diagnostic.message,
allocator=allocator,
)
if source_file == nil {
return fmt.aprintf("<unknown>: %s: %s", severity, diagnostic.message, allocator=allocator)
}
builder := strings.builder_make(allocator)
fmt.sbprintf(&builder, "%s: %s\n", severity, diagnostic.message)
primary_label := ""
if annotation, found := annotation_for(diagnostics, id, .Primary); found {
primary_label = annotation.message
}
_ = write_excerpt(&builder, diagnostics, diagnostic.span, primary_label, true)
for annotation in diagnostics.annotations {
if annotation.owner == id && annotation.kind == .Secondary {
_ = write_excerpt(&builder, diagnostics, annotation.span, annotation.message, false)
}
}
for annotation in diagnostics.annotations {
if annotation.owner != id {
continue
}
#partial switch annotation.kind {
case .Note:
fmt.sbprintf(&builder, "note: %s\n", annotation.message)
case .Help:
fmt.sbprintf(&builder, "help: %s\n", annotation.message)
case:
}
}
result := strings.to_string(builder)
if len(result) > 0 && result[len(result)-1] == '\n' {
return result[:len(result)-1]
}
return result
}
print_all :: proc(diagnostics: ^Diagnostics) {
+296
View File
@@ -0,0 +1,296 @@
package compiler
import "./ast"
import "./lexer"
import "./parser"
import "./source"
import "./symbol"
import "./types"
import "core:fmt"
import "core:path/filepath"
import "core:slice"
import "core:strings"
Test_Entry :: struct {
function: ast.Function_Id,
package_path: string,
source_path: string,
offset: source.Offset,
}
test_entry_less :: proc(a, b: Test_Entry) -> bool {
if a.package_path != b.package_path {
return a.package_path < b.package_path
}
if a.source_path != b.source_path {
return a.source_path < b.source_path
}
return a.offset < b.offset
}
testing_package :: proc(module: ^ast.Module, project_root: string) -> ast.Package_Id {
path, err := filepath.join({project_root, "std", "testing"}, module.allocator)
if err != nil {
return ast.INVALID_PACKAGE
}
defer delete(path, module.allocator)
canonical, ok := filepath.abs(path, module.allocator)
if !ok {
return ast.INVALID_PACKAGE
}
defer delete(canonical, module.allocator)
for pkg, index in module.packages {
if pkg.path == canonical {
return ast.package_id(index)
}
}
return ast.INVALID_PACKAGE
}
source_file_id :: proc(module: ^ast.Module, id: source.Source_Id) -> ast.File_Id {
for file, index in module.files {
if file.source == id {
return ast.file_id(index)
}
}
return ast.INVALID_FILE
}
append_ast_expr :: proc(module: ^ast.Module, expr: ast.Expr) -> ast.Expr_Id {
id := ast.expr_id(len(module.exprs))
append(&module.exprs, expr)
return id
}
location_expr :: proc(
module: ^ast.Module,
sources: ^source.Store,
symbols: ^symbol.Table,
span: source.Span,
qualifier: symbol.Id,
) -> ast.Expr_Id {
line, column := 1, 1
path := "<unknown>"
if int(span.file) < len(sources.items) {
file := &sources.items[span.file]
line, column = source.line_and_column(file, span.start)
path = file.path
}
string_index := u64(len(module.strings))
append(&module.strings, strings.clone(path, module.allocator))
file_expr := append_ast_expr(module, ast.Expr{
kind=.String, span=span, integer=string_index,
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
line_expr := append_ast_expr(module, ast.Expr{
kind=.Integer, span=span, integer=u64(line),
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
column_expr := append_ast_expr(module, ast.Expr{
kind=.Integer, span=span, integer=u64(column),
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
values := []ast.Expr_Id{file_expr, line_expr, column_expr}
names := []string{"file", "line", "column"}
fields := make([]ast.Expr_Id, 3, module.allocator)
for index in 0..<3 {
fields[index] = append_ast_expr(module, ast.Expr{
kind=.Keyed, span=span,
name=symbol.intern(symbols, names[index]),
left=values[index], right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
return append_ast_expr(module, ast.Expr{
kind=.Struct_Literal, span=span,
qualifier=qualifier,
name=symbol.intern(symbols, "SourceLocation"),
args=fields,
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
inject_assertion_locations :: proc(
module: ^ast.Module,
sources: ^source.Store,
symbols: ^symbol.Table,
testing_pkg: ast.Package_Id,
) {
expect := symbol.intern(symbols, "expect")
expect_equal := symbol.intern(symbols, "expect_equal")
expect_type := symbol.intern(symbols, "expect_type")
original_count := len(module.exprs)
for index in 0..<original_count {
expr := &module.exprs[index]
if expr.kind != .Call || expr.intrinsic || !symbol.is_valid(expr.qualifier) ||
(expr.name != expect && expr.name != expect_equal && expr.name != expect_type) {
continue
}
file := source_file_id(module, expr.span.file)
matched := false
for import_item in module.imports {
if !import_item.test_only && import_item.file == file &&
import_item.alias == expr.qualifier && import_item.target == testing_pkg {
matched = true
break
}
}
if !matched {
continue
}
location := location_expr(module, sources, symbols, expr.span, expr.qualifier)
expr = &module.exprs[index]
args := make([]ast.Expr_Id, len(expr.args)+1, module.allocator)
copy(args, expr.args)
args[len(expr.args)] = location
delete(expr.args, module.allocator)
expr.args = args
}
}
write_brolang_string :: proc(builder: ^strings.Builder, value: string) {
for byte_value in transmute([]byte)value {
if byte_value == '\\' || byte_value == '"' {
strings.write_byte(builder, '\\')
}
strings.write_byte(builder, byte_value)
}
}
append_runner :: proc(
module: ^ast.Module,
sources: ^source.Store,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
testing_pkg: ast.Package_Id,
testing_error: types.Type,
tests: []Test_Entry,
) {
builder := strings.builder_make(module.allocator)
defer strings.builder_destroy(&builder)
for entry, index in tests {
test := module.functions[entry.function]
alias := fmt.tprintf("__brolang_test_%d", index)
fmt.sbprintf(&builder, "@hide __brolang_test_adapter_%d func() void ! __brolang_testing.Error ", index)
strings.write_string(&builder, "{\n\t")
fmt.sbprintf(&builder, "%s.%s() catch |_| ", alias, symbol.resolve(symbols, test.name))
strings.write_string(&builder, "{\n\t\treturn .expectation_failed\n\t}\n}\n\n")
}
strings.write_string(&builder, "main func() i32 {\n\tfailed i32 := 0\n")
for entry, index in tests {
test_id := entry.function
test := module.functions[test_id]
pkg := module.packages[test.pkg]
name := fmt.tprintf("%s.%s", filepath.base(pkg.path), symbol.resolve(symbols, test.name))
strings.write_string(&builder, "\tif (!__brolang_testing.run(\"")
write_brolang_string(&builder, name)
fmt.sbprintf(&builder, "\", __brolang_test_adapter_%d)) ", index)
strings.write_string(&builder, "{\n\t\tfailed += 1\n\t}\n")
}
fmt.sbprintf(&builder, "\t__brolang_testing.summary(%d - failed, failed)\n", len(tests))
strings.write_string(&builder, "\tif (failed != 0) return 1\n\treturn 0\n}\n")
runner_text := strings.to_string(builder)
source_id := source.add_source(sources, "<brolang-test-runner>", runner_text)
file_id := ast.file_id(len(module.files))
append(&module.files, ast.File{source=source_id, pkg=0})
stream := lexer.lex(&sources.items[source_id], diagnostics, symbols, module.allocator)
defer delete(stream.items)
function_start := len(module.functions)
parser.parse_into(&stream, &sources.items[source_id], diagnostics, module, 0, file_id)
for index in 0..<len(tests) {
module.functions[function_start+index].error = testing_error
}
append(&module.imports, ast.Import{
alias=symbol.intern(symbols, "__brolang_testing"),
path=strings.clone("@std/testing", module.allocator),
pkg=0, file=file_id, target=testing_pkg,
valid=true, used=true,
diagnostic=source.INVALID_DIAGNOSTIC,
})
for entry, index in tests {
test_id := entry.function
test := module.functions[test_id]
append(&module.imports, ast.Import{
alias=symbol.intern(symbols, fmt.tprintf("__brolang_test_%d", index)),
path=strings.clone(module.packages[test.pkg].path, module.allocator),
pkg=0, file=file_id, target=test.pkg,
valid=true, used=true,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
prepare_tests :: proc(
module: ^ast.Module,
sources: ^source.Store,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
mode: ast.Compile_Mode,
project_root: string,
) -> bool {
if mode == .Executable {
for &function in module.functions {
if !function.test {
continue
}
function.generated = true
for &import_item in module.imports {
if !import_item.test_only && import_item.file == function.file {
import_item.used = true
}
}
}
return true
}
testing_pkg := testing_package(module, project_root)
if testing_pkg == ast.INVALID_PACKAGE {
source.add(diagnostics, source.Span{}, "test builds require @std/testing")
return false
}
error_type := types.find_named(
&module.type_store,
u32(testing_pkg),
u32(symbol.intern(symbols, "Error")),
)
if !types.is_enum(error_type, &module.type_store) {
source.add(diagnostics, source.Span{}, "@std/testing must declare Error as an enum")
return false
}
tests: [dynamic]Test_Entry
tests.allocator = module.allocator
defer delete(tests)
main_name := symbol.intern(symbols, "main")
for &function, index in module.functions {
if function.test {
if int(function.pkg) < len(module.packages) && module.packages[function.pkg].test {
function.result = types.VOID
function.error = error_type
file := module.files[function.file]
append(&tests, Test_Entry{
function=ast.function_id(index),
package_path=module.packages[function.pkg].path,
source_path=sources.items[file.source].path,
offset=function.span.start,
})
} else {
function.generated = true
}
} else if function.pkg == 0 && function.name == main_name {
function.generated = true
function.analysis_root = true
}
}
slice.sort_by(tests[:], test_entry_less)
inject_assertion_locations(module, sources, symbols, testing_pkg)
append_runner(module, sources, diagnostics, symbols, testing_pkg, error_type, tests[:])
return true
}
+28 -2
View File
@@ -16,14 +16,21 @@ Kind :: enum u8 {
Underscore,
Colon,
Colon_Colon,
Colon_Equal,
Equal,
Equal_Equal,
Bang,
Bang_Equal,
Less,
Less_Equal,
Less_Less,
Less_Less_Equal,
Less_Less_Pipe,
Less_Less_Pipe_Equal,
Greater,
Greater_Equal,
Greater_Greater,
Greater_Greater_Equal,
Plus,
Minus,
Slash,
@@ -36,9 +43,13 @@ Kind :: enum u8 {
Range_Inclusive,
Ellipsis,
At,
Dollar,
Star,
Ampersand,
Ampersand_Equal,
Caret,
Tilde,
Xor_Equal,
Question,
Semicolon,
Left_Bracket,
@@ -49,10 +60,13 @@ Kind :: enum u8 {
Right_Brace,
Comma,
Pipe,
Pipe_Equal,
Keyword_Test,
Keyword_Func,
Keyword_C_Func,
Keyword_Struct,
Keyword_C_Struct,
Keyword_Opaque,
Keyword_Union,
Keyword_Enum,
Keyword_Distinct,
@@ -62,25 +76,32 @@ Kind :: enum u8 {
Keyword_Try,
Keyword_Catch,
Keyword_Mut,
Keyword_None,
Keyword_Null,
Keyword_Undefined,
Keyword_Orelse,
Keyword_And,
Keyword_Or,
Keyword_Xor,
Keyword_If,
Keyword_While,
Keyword_For,
Keyword_Inline,
Keyword_Break,
Keyword_Continue,
Keyword_Defer,
Keyword_Errdefer,
Keyword_Yield,
Keyword_Match,
Keyword_Else,
Keyword_True,
Keyword_False,
Keyword_Void,
Keyword_Noreturn,
Keyword_Anyopaque,
Keyword_Unreachable,
Keyword_Bool,
Keyword_Int,
Keyword_Uint,
Keyword_Float,
Keyword_Range,
Keyword_I8,
@@ -111,6 +132,10 @@ Kind :: enum u8 {
Keyword_C_Longdouble,
}
is_keyword :: proc(kind: Kind) -> bool {
return kind >= .Keyword_Test && kind <= .Keyword_C_Longdouble
}
Token :: struct {
span: source.Span,
symbol: symbol.Id,
@@ -119,5 +144,6 @@ Token :: struct {
}
Stream :: struct {
items: [dynamic]Token,
items: [dynamic]Token,
symbols: ^symbol.Table,
}
+454 -52
View File
@@ -17,32 +17,206 @@ import "core:fmt"
import "core:mem"
import "core:strings"
Package_Input :: struct {
result: ^cimport.Result,
header: string,
name: string,
}
Package_Output :: struct {
name: string,
source: string,
}
Declaration_Kind :: enum u8 {
Record,
Alias,
Macro,
Function,
}
Declaration :: struct {
name: string,
canonical: string,
kind: Declaration_Kind,
}
Declaration_Registry :: struct {
lookup: map[string]int,
declarations: [dynamic]Declaration,
error: string,
allocator: mem.Allocator,
}
init_declaration_registry :: proc(allocator: mem.Allocator) -> Declaration_Registry {
registry := Declaration_Registry{allocator=allocator}
registry.lookup.allocator = allocator
registry.declarations.allocator = allocator
return registry
}
destroy_declaration_registry :: proc(registry: ^Declaration_Registry) {
for declaration in registry.declarations {
delete(declaration.name, registry.allocator)
delete(declaration.canonical, registry.allocator)
}
delete(registry.error, registry.allocator)
delete(registry.lookup)
delete(registry.declarations)
}
write_declaration :: proc(
b: ^strings.Builder,
registry: ^Declaration_Registry,
name: string,
kind: Declaration_Kind,
actual: string,
canonical := "",
) -> (emitted, ok: bool) {
if registry == nil {
strings.write_string(b, actual)
return true, true
}
comparison := canonical if len(canonical) > 0 else actual
if index, found := registry.lookup[name]; found {
previous := registry.declarations[index]
if previous.kind == kind && previous.canonical == comparison {
return false, true
}
if len(registry.error) == 0 {
registry.error = fmt.aprintf(
"conflicting generated C declaration '%s'",
name,
allocator=registry.allocator,
)
}
return false, false
}
owned_name := strings.clone(name, registry.allocator)
owned_canonical := strings.clone(comparison, registry.allocator)
registry.lookup[owned_name] = len(registry.declarations)
append(&registry.declarations, Declaration{
name=owned_name,
canonical=owned_canonical,
kind=kind,
})
strings.write_string(b, actual)
return true, true
}
emit :: proc(result: ^cimport.Result, header: string, allocator := context.allocator) -> string {
record_names := record_name_table(result, allocator)
defer destroy_record_name_table(record_names, allocator)
output, ok := emit_with_record_names(result, header, record_names, nil, allocator)
assert(ok)
return output
}
destroy_package_outputs :: proc(outputs: []Package_Output, allocator := context.allocator) {
for output in outputs {
delete(output.name, allocator)
delete(output.source, allocator)
}
delete(outputs, allocator)
}
emit_package :: proc(inputs: []Package_Input, allocator := context.allocator) -> ([]Package_Output, string) {
registry := init_declaration_registry(allocator)
defer destroy_declaration_registry(&registry)
tables := make([][]string, len(inputs), allocator)
defer {
for table in tables {
destroy_record_name_table(table, allocator)
}
delete(tables, allocator)
}
for input, index in inputs {
tables[index] = record_name_table(input.result, allocator, input.name)
}
identities: map[string]string
identities.allocator = allocator
defer delete(identities)
for input, input_index in inputs {
for record, record_index in input.result.records {
if len(record.identity) == 0 {
continue
}
if canonical, found := identities[record.identity]; found {
delete(tables[input_index][record_index], allocator)
tables[input_index][record_index] = strings.clone(canonical, allocator)
} else {
identities[record.identity] = tables[input_index][record_index]
}
}
}
outputs: [dynamic]Package_Output
outputs.allocator = allocator
for input, index in inputs {
source, ok := emit_with_record_names(
input.result,
input.header,
tables[index],
&registry,
allocator,
)
if !ok {
delete(source, allocator)
destroy_package_outputs(outputs[:], allocator)
return nil, strings.clone(registry.error, allocator)
}
append(&outputs, Package_Output{
name=strings.clone(input.name, allocator),
source=source,
})
}
return outputs[:], ""
}
emit_with_record_names :: proc(
result: ^cimport.Result,
header: string,
record_names: []string,
registry: ^Declaration_Registry,
allocator: mem.Allocator,
) -> (string, bool) {
b := strings.builder_make(allocator)
fmt.sbprintf(&b, "# generated by brolang translate-c from %s\n\n", header)
record_names := record_name_table(result, allocator)
defer delete(record_names, allocator)
emit_records(&b, result, record_names)
emit_aliases(&b, result, record_names)
emit_macros(&b, result, record_names)
emit_functions(&b, result, record_names)
if !emit_records(&b, result, record_names, registry) ||
!emit_aliases(&b, result, record_names, registry) ||
!emit_macros(&b, result, record_names, registry) ||
!emit_functions(&b, result, record_names, registry) {
return strings.to_string(b), false
}
emit_variables(&b, result)
emit_unsupported(&b, result)
return strings.to_string(b)
return strings.to_string(b), true
}
// record_name_table maps each record index to the brolang identifier it is
// emitted under: its C tag name, or for an anonymous record named only by a
// typedef that typedef's name (the typedef is then skipped). Truly anonymous
// records fall back to the loader's synthetic `__c_record_N`.
record_name_table :: proc(result: ^cimport.Result, allocator: mem.Allocator) -> []string {
record_name_table :: proc(result: ^cimport.Result, allocator: mem.Allocator, prefix := "") -> []string {
names := make([]string, len(result.records), allocator)
for record, idx in result.records {
if len(record.name) > 0 {
names[idx] = record.name
if binding_identifier(record.name) {
if record_name_conflicts(result, idx, record.name) {
fragment := safe_name_fragment(prefix, context.temp_allocator)
if len(fragment) > 0 {
names[idx] = fmt.aprintf("__c_%s_%s_record", fragment, record.name, allocator=allocator)
} else {
names[idx] = fmt.aprintf("__c_%s_record", record.name, allocator=allocator)
}
} else {
names[idx] = strings.clone(record.name, allocator)
}
} else if len(prefix) > 0 {
fragment := safe_name_fragment(prefix, context.temp_allocator)
names[idx] = fmt.aprintf("__c_%s_record_%d", fragment, idx, allocator=allocator)
} else {
names[idx] = fmt.aprintf("__c_record_%d", idx, allocator=allocator)
}
@@ -59,13 +233,73 @@ record_name_table :: proc(result: ^cimport.Result, allocator: mem.Allocator) ->
if target.kind != .Record || int(target.record) >= len(result.records) {
continue
}
if len(result.records[target.record].name) == 0 {
names[target.record] = alias.name
if !binding_identifier(result.records[target.record].name) {
delete(names[target.record], allocator)
names[target.record] = strings.clone(alias.name, allocator)
}
}
return names
}
binding_identifier :: proc(value: string) -> bool {
if len(value) == 0 || lexer.keyword_kind(value) != .Identifier {
return false
}
for byte, index in transmute([]byte)value {
letter := byte == '_' || byte >= 'a' && byte <= 'z' || byte >= 'A' && byte <= 'Z'
if !letter && (index == 0 || byte < '0' || byte > '9') {
return false
}
}
return true
}
record_name_conflicts :: proc(result: ^cimport.Result, record_index: int, name: string) -> bool {
for function in result.functions {
if function.name == name && len(function.reason) == 0 &&
(len(function.link_name) == 0 || function.link_name == function.name) {
return true
}
}
for macro in result.macros {
if macro.name == name && len(macro.reason) == 0 &&
lexer.keyword_kind(macro.name) == .Identifier {
return true
}
}
for alias in result.aliases {
if alias.name != name || len(alias.reason) > 0 || alias.type == cimport.INVALID_TYPE ||
int(alias.type) < 0 || int(alias.type) >= len(result.types) {
continue
}
target := result.types[alias.type]
if target.kind != .Record || int(target.record) != record_index {
return true
}
}
return false
}
destroy_record_name_table :: proc(names: []string, allocator: mem.Allocator) {
for name in names {
delete(name, allocator)
}
delete(names, allocator)
}
safe_name_fragment :: proc(value: string, allocator: mem.Allocator) -> string {
b := strings.builder_make(allocator)
for byte in transmute([]byte)value {
if byte >= 'a' && byte <= 'z' || byte >= 'A' && byte <= 'Z' ||
byte >= '0' && byte <= '9' || byte == '_' {
strings.write_byte(&b, byte)
} else {
strings.write_byte(&b, '_')
}
}
return strings.to_string(b)
}
// render_type writes the brolang spelling of a C type. Mirror of
// loader.translate_c_type keep the two in lockstep.
render_type :: proc(b: ^strings.Builder, result: ^cimport.Result, id: cimport.Type_Id, record_names: []string) {
@@ -99,7 +333,11 @@ render_type :: proc(b: ^strings.Builder, result: ^cimport.Result, id: cimport.Ty
if item.mutable {
strings.write_string(b, "mut ")
}
render_type(b, result, item.child, record_names)
if int(item.child) >= 0 && int(item.child) < len(result.types) && result.types[item.child].kind == .Void {
strings.write_string(b, "anyopaque")
} else {
render_type(b, result, item.child, record_names)
}
case .Array:
fmt.sbprintf(b, "[%d]", item.count)
render_type(b, result, item.child, record_names)
@@ -146,36 +384,68 @@ render_c_func :: proc(
render_type(b, result, ret, record_names)
}
emit_records :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_records :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for record, idx in result.records {
name := record_names[idx]
representable := record_has_native_spelling(result, u32(idx))
if record.kind == .Union {
fmt.sbprintf(b, "# unsupported in bindings: C union '%s' has no native spelling\n", name)
wrote = true
continue
}
if !record.complete || len(record.reason) > 0 {
// opaque / pointer-only struct
fmt.sbprintf(b, "%s :: c_struct\n", name)
} else if record.complete && len(record.reason) == 0 && !representable {
fmt.sbprintf(b, "# unsupported in bindings: C record '%s' contains an unsupported field type\n", name)
wrote = true
}
if !representable {
// opaque / pointer-only struct
text := fmt.tprintf("%s :: opaque\n", name)
canonical := text
if len(record.identity) > 0 {
canonical = fmt.tprintf("%s\n%s", record.identity, text)
}
emitted, ok := write_declaration(b, registry, name, .Record, text, canonical)
if !ok {
return false
}
wrote = wrote || emitted
continue
}
fmt.sbprintf(b, "%s :: c_struct {{\n", name)
declaration := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&declaration, "%s :: c_struct {{\n", name)
for field in record.fields {
fmt.sbprintf(b, "\t%s ", field.name)
render_type(b, result, field.type, record_names)
strings.write_byte(b, '\n')
fmt.sbprintf(&declaration, "\t%s ", field.name)
render_type(&declaration, result, field.type, record_names)
strings.write_byte(&declaration, '\n')
}
strings.write_string(b, "}\n")
wrote = true
strings.write_string(&declaration, "}\n")
text := strings.to_string(declaration)
canonical := text
if len(record.identity) > 0 {
canonical = fmt.tprintf("%s\n%s", record.identity, text)
}
emitted, ok := write_declaration(b, registry, name, .Record, text, canonical)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_aliases :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_aliases :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for alias in result.aliases {
if len(alias.reason) > 0 {
@@ -183,27 +453,46 @@ emit_aliases :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names:
wrote = true
continue
}
if !type_has_native_spelling(result, alias.type) {
fmt.sbprintf(b, "# unsupported in bindings: typedef '%s' — underlying type has no native spelling\n", alias.name)
wrote = true
continue
}
// Skip a typedef that merely (re)names a record under the name we already
// emitted the record with (anonymous-struct collapse, or `typedef struct
// Foo Foo;`).
if ti := alias.type; int(ti) >= 0 && int(ti) < len(result.types) {
target := result.types[ti]
if target.kind == .Record && int(target.record) < len(record_names) &&
record_names[target.record] == alias.name {
continue
if target.kind == .Record && int(target.record) < len(record_names) {
record := result.records[target.record]
if record_names[target.record] == alias.name || record.name == alias.name {
continue
}
}
}
fmt.sbprintf(b, "%s :: alias ", alias.name)
render_type(b, result, alias.type, record_names)
strings.write_byte(b, '\n')
wrote = true
declaration := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&declaration, "%s :: alias ", alias.name)
render_type(&declaration, result, alias.type, record_names)
strings.write_byte(&declaration, '\n')
text := strings.to_string(declaration)
emitted, ok := write_declaration(b, registry, alias.name, .Alias, text)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_macros :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_macros :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for macro in result.macros {
if len(macro.reason) > 0 {
@@ -219,23 +508,41 @@ emit_macros :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names:
continue
}
if macro.aggregate {
emit_aggregate_macro(b, result, macro, record_names)
wrote = true
declaration := strings.builder_make(context.temp_allocator)
binding := emit_aggregate_macro(&declaration, result, macro, record_names)
text := strings.to_string(declaration)
if binding {
emitted, ok := write_declaration(b, registry, macro.name, .Macro, text)
if !ok {
return false
}
wrote = wrote || emitted
} else {
strings.write_string(b, text)
wrote = true
}
continue
}
strings.write_string(b, macro.name)
declaration := strings.builder_make(context.temp_allocator)
strings.write_string(&declaration, macro.name)
if macro_scalar_kind(result, macro.type) {
strings.write_byte(b, ' ')
render_type(b, result, macro.type, record_names)
strings.write_byte(&declaration, ' ')
render_type(&declaration, result, macro.type, record_names)
}
strings.write_string(b, " :: ")
render_macro_value(b, macro.value)
strings.write_byte(b, '\n')
wrote = true
strings.write_string(&declaration, " :: ")
render_macro_value(&declaration, macro.value)
strings.write_byte(&declaration, '\n')
text := strings.to_string(declaration)
emitted, ok := write_declaration(b, registry, macro.name, .Macro, text)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_aggregate_macro :: proc(
@@ -243,11 +550,11 @@ emit_aggregate_macro :: proc(
result: ^cimport.Result,
macro: cimport.Macro_Constant,
record_names: []string,
) {
) -> bool {
ti := macro.type
if int(ti) >= 0 && int(ti) < len(result.types) && result.types[ti].kind == .Record {
ridx := int(result.types[ti].record)
if ridx < len(result.records) {
if ridx < len(result.records) && record_has_native_spelling(result, u32(ridx)) {
record := result.records[ridx]
if len(record.fields) == len(macro.values) && !record_has_pointer_field(result, record) {
fmt.sbprintf(b, "%s :: %s {{", macro.name, record_names[ridx])
@@ -259,14 +566,20 @@ emit_aggregate_macro :: proc(
render_macro_value(b, macro.values[index])
}
strings.write_string(b, " }\n")
return
return true
}
}
}
fmt.sbprintf(b, "# unsupported in bindings: aggregate macro '%s' has no native spelling\n", macro.name)
return false
}
emit_functions :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names: []string) {
emit_functions :: proc(
b: ^strings.Builder,
result: ^cimport.Result,
record_names: []string,
registry: ^Declaration_Registry,
) -> bool {
wrote := false
for function in result.functions {
if len(function.reason) > 0 {
@@ -274,19 +587,41 @@ emit_functions :: proc(b: ^strings.Builder, result: ^cimport.Result, record_name
wrote = true
continue
}
if !function_has_native_spelling(result, function) {
fmt.sbprintf(b, "# unsupported in bindings: function '%s' — signature has no native spelling\n", function.name)
wrote = true
continue
}
if len(function.link_name) > 0 && function.link_name != function.name {
fmt.sbprintf(b, "# unsupported in bindings: function '%s' is a static inline function (needs trampoline)\n", function.name)
wrote = true
continue
}
fmt.sbprintf(b, "%s ", function.name)
render_c_func(b, result, function.params, function.param_names, function.result, function.variadic, record_names)
strings.write_byte(b, '\n')
wrote = true
declaration := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&declaration, "%s ", function.name)
render_c_func(&declaration, result, function.params, function.param_names, function.result, function.variadic, record_names)
strings.write_byte(&declaration, '\n')
canonical := strings.builder_make(context.temp_allocator)
fmt.sbprintf(&canonical, "%s ", function.name)
render_c_func(&canonical, result, function.params, nil, function.result, function.variadic, record_names)
strings.write_byte(&canonical, '\n')
emitted, ok := write_declaration(
b,
registry,
function.name,
.Function,
strings.to_string(declaration),
strings.to_string(canonical),
)
if !ok {
return false
}
wrote = wrote || emitted
}
if wrote {
strings.write_byte(b, '\n')
}
return true
}
emit_variables :: proc(b: ^strings.Builder, result: ^cimport.Result) {
@@ -346,6 +681,73 @@ macro_scalar_kind :: proc(result: ^cimport.Result, id: cimport.Type_Id) -> bool
return false
}
function_has_native_spelling :: proc(result: ^cimport.Result, function: cimport.Function) -> bool {
for param in function.params {
if !type_has_native_spelling(result, param) {
return false
}
}
return type_has_native_spelling(result, function.result, allow_void=true)
}
record_has_native_spelling :: proc(result: ^cimport.Result, index: u32, depth := 0) -> bool {
if depth > 64 || int(index) < 0 || int(index) >= len(result.records) {
return false
}
record := result.records[index]
if record.kind == .Union || !record.complete || len(record.reason) > 0 || len(record.fields) == 0 {
return false
}
for field in record.fields {
if !type_has_native_spelling(result, field.type, depth=depth+1) {
return false
}
}
return true
}
type_has_native_spelling :: proc(
result: ^cimport.Result,
id: cimport.Type_Id,
allow_void := false,
depth := 0,
) -> bool {
if depth > 64 || id == cimport.INVALID_TYPE || int(id) < 0 || int(id) >= len(result.types) {
return false
}
item := result.types[id]
switch item.kind {
case .Void:
return allow_void
case .C_Bool, .C_Char, .C_Schar, .C_Uchar, .C_Short, .C_Ushort, .C_Int, .C_Uint,
.C_Long, .C_Ulong, .C_Longlong, .C_Ulonglong, .C_Float, .C_Double, .C_Longdouble:
return true
case .Pointer:
if item.child == cimport.INVALID_TYPE || int(item.child) < 0 || int(item.child) >= len(result.types) {
return false
}
child := result.types[item.child]
if child.kind == .Void || child.kind == .Record {
return true
}
return type_has_native_spelling(result, item.child, allow_void=true, depth=depth+1)
case .Array:
return type_has_native_spelling(result, item.child, depth=depth+1)
case .Function:
for param in item.params {
if !type_has_native_spelling(result, param, depth=depth+1) {
return false
}
}
return type_has_native_spelling(result, item.child, allow_void=true, depth=depth+1)
case .Record:
return record_has_native_spelling(result, item.record, depth+1)
case .Invalid:
return false
}
return false
}
record_has_pointer_field :: proc(result: ^cimport.Result, record: cimport.Record) -> bool {
for field in record.fields {
if int(field.type) >= 0 && int(field.type) < len(result.types) &&
+418 -29
View File
@@ -43,6 +43,9 @@ C_LONGDOUBLE :: Type(28)
BOOL :: Type(29)
FLOAT :: Type(30)
RANGE :: Type(31)
ANYOPAQUE :: Type(32)
UINT :: Type(33)
NORETURN :: Type(34)
DYNAMIC_START :: Type(64)
@@ -53,10 +56,19 @@ Numeric_Category :: enum u8 {
Float,
}
Visibility :: enum u8 {
Public,
Package,
File,
}
Kind :: enum u8 {
Invalid,
Void,
Noreturn,
Anyopaque,
Int_Constraint,
Uint_Constraint,
Float_Constraint,
Range_Constraint,
Scalar,
@@ -73,6 +85,8 @@ Kind :: enum u8 {
Struct,
Union,
Fallible,
Sum,
Type_Call,
}
Node :: struct {
@@ -98,8 +112,10 @@ Node :: struct {
c_abi: bool,
variadic: bool,
c_layout: bool,
tuple: bool,
opaque: bool,
declared: bool,
visibility: Visibility,
explicit_backing: bool,
}
@@ -183,31 +199,65 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
return id
}
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
normalized_file := file if qualifier != 0 else u32(0)
named :: proc(
store: ^Store,
pkg, name: u32,
qualifier: u32 = 0,
file: u32 = 0xffff_ffff,
visibility := Visibility.Public,
) -> Type {
normalized_file := file if qualifier != 0 || visibility == .File else u32(0)
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file {
((qualifier != 0 && existing.file == normalized_file) ||
(qualifier == 0 && visibility == .File && existing.visibility == .File && existing.file == normalized_file) ||
(qualifier == 0 && visibility != .File && existing.visibility != .File)) {
return DYNAMIC_START+Type(index)
}
}
return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file})
return intern(store, Node{
kind=.Named,
pkg=pkg,
name=name,
qualifier=qualifier,
file=normalized_file,
visibility=visibility,
})
}
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
fallback := INVALID
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index)
if (existing.kind != .Named && existing.kind != .Alias && existing.kind != .Distinct &&
existing.kind != .Enum && existing.kind != .Struct && existing.kind != .Union) ||
existing.pkg != pkg || existing.name != name || existing.qualifier != qualifier {
continue
}
if qualifier != 0 {
if existing.file == file {
return DYNAMIC_START+Type(index)
}
continue
}
switch existing.visibility {
case .Public:
fallback = DYNAMIC_START+Type(index)
case .Package:
if file != 0xffff_ffff {
fallback = DYNAMIC_START+Type(index)
}
case .File:
if existing.file == file {
return DYNAMIC_START+Type(index)
}
}
}
return INVALID
return fallback
}
define_alias :: proc(store: ^Store, id, child: Type) -> bool {
define_alias :: proc(store: ^Store, id, child: Type, visibility := Visibility.Public) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
@@ -215,11 +265,12 @@ define_alias :: proc(store: ^Store, id, child: Type) -> bool {
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Alias
store.nodes[index].child = child
store.nodes[index].visibility = visibility
store.nodes[index].declared = true
return true
}
define_distinct :: proc(store: ^Store, id, child: Type) -> bool {
define_distinct :: proc(store: ^Store, id, child: Type, visibility := Visibility.Public) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
@@ -227,11 +278,12 @@ define_distinct :: proc(store: ^Store, id, child: Type) -> bool {
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Distinct
store.nodes[index].child = child
store.nodes[index].visibility = visibility
store.nodes[index].declared = true
return true
}
define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool) -> bool {
define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool, visibility := Visibility.Public) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
@@ -242,6 +294,7 @@ define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, ex
store.nodes[index].field_start = u32(len(store.enum_members))
store.nodes[index].field_count = u32(len(members))
store.nodes[index].explicit_backing = explicit_backing
store.nodes[index].visibility = visibility
store.nodes[index].declared = true
append(&store.enum_members, ..members)
return true
@@ -257,6 +310,8 @@ define_record :: proc(
explicit_alignment: u32 = 0,
tag: Type = INVALID,
declared_tag: Type = INVALID,
tuple := false,
visibility := Visibility.Public,
) -> bool {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
@@ -266,7 +321,9 @@ define_record :: proc(
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Union if is_union else .Struct
store.nodes[index].c_layout = c_layout
store.nodes[index].tuple = tuple
store.nodes[index].opaque = opaque
store.nodes[index].visibility = visibility
store.nodes[index].declared = true
store.nodes[index].explicit_size = explicit_size
store.nodes[index].explicit_alignment = explicit_alignment
@@ -326,10 +383,10 @@ anonymous_struct_fields_equal :: proc(store: ^Store, item: Node, fields: []Field
return true
}
struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
struct_anonymous :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
for existing, index in store.nodes {
if existing.kind == .Struct && existing.name == 0 && existing.declared &&
!existing.c_layout && !existing.opaque &&
!existing.c_layout && existing.tuple == tuple && !existing.opaque &&
anonymous_struct_fields_equal(store, existing, fields) {
return DYNAMIC_START+Type(index)
}
@@ -340,10 +397,28 @@ struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
declared=true,
})
}
// Generated structs are nominal per comptime type-expression specialization.
// The checker owns canonicalization; this routine deliberately creates a fresh node.
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false, c_layout := false) -> Type {
start := u32(len(store.fields))
append(&store.fields, ..fields)
id := DYNAMIC_START+Type(len(store.nodes))
append(&store.nodes, Node{
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
c_layout=c_layout,
declared=true,
})
return id
}
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
for variant in store.variants {
if variant.name == name && variant.payload == payload {
@@ -373,6 +448,10 @@ fallible_error :: proc(value: Type, store: ^Store) -> Type {
return item.extra if ok && item.kind == .Fallible else INVALID
}
sum_syntax :: proc(store: ^Store, left, right: Type) -> Type {
return intern(store, Node{kind=.Sum, child=left, extra=right})
}
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
item, ok := node(store, value)
if !ok {
@@ -506,6 +585,73 @@ can_sum_widen :: proc(from, to: Type, store: ^Store) -> bool {
return true
}
// Reports whether every selected variant from `from` is represented identically in
// `to`. The checker uses this only after control flow has proven that the source value
// is one of `selected`; it is not a general implicit narrowing rule.
selected_sum_fits :: proc(from, to: Type, selected: []u32, store: ^Store) -> bool {
if len(selected) == 0 {
return false
}
from_variants: [dynamic]Sum_Variant
from_variants.allocator = store.allocator
defer delete(from_variants)
to_variants: [dynamic]Sum_Variant
to_variants.allocator = store.allocator
defer delete(to_variants)
if !append_sum_variants(store, from, &from_variants) ||
!append_sum_variants(store, to, &to_variants) {
return false
}
for name in selected {
source_variant: Sum_Variant
source_found := false
for variant in from_variants {
if variant.name == name {
source_variant = variant
source_found = true
break
}
}
if !source_found {
return false
}
matched := false
for variant in to_variants {
if variant.id == source_variant.id && variant.payload == source_variant.payload {
matched = true
break
}
}
if !matched {
return false
}
}
return true
}
// Sum_Project is emitted only with a checker proof. This weaker structural predicate is
// retained by the backend as a defensive check that the two sums share a valid variant.
can_sum_project :: proc(from, to: Type, store: ^Store) -> bool {
from_variants: [dynamic]Sum_Variant
from_variants.allocator = store.allocator
defer delete(from_variants)
to_variants: [dynamic]Sum_Variant
to_variants.allocator = store.allocator
defer delete(to_variants)
if !append_sum_variants(store, from, &from_variants) ||
!append_sum_variants(store, to, &to_variants) {
return false
}
for source_variant in from_variants {
for target_variant in to_variants {
if target_variant.id == source_variant.id && target_variant.payload == source_variant.payload {
return true
}
}
}
return false
}
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
return define_record(store, id, fields, c_layout, opaque)
}
@@ -523,6 +669,43 @@ fields_for :: proc(store: ^Store, value: Type) -> []Field {
return store.fields[start:end]
}
field_layout_precedes :: proc(
store: ^Store,
value: Type,
left, right: int,
selected := target.DEFAULT,
) -> bool {
fields := fields_for(store, value)
item, ok := node(store, value)
if !ok || item.kind != .Struct || item.c_layout ||
left < 0 || left >= len(fields) || right < 0 || right >= len(fields) {
return left < right
}
left_alignment := alignment_of(fields[left].type, store, selected)
right_alignment := alignment_of(fields[right].type, store, selected)
return left_alignment > right_alignment ||
(left_alignment == right_alignment && left < right)
}
physical_field_index :: proc(
store: ^Store,
value: Type,
logical_index: int,
selected := target.DEFAULT,
) -> int {
fields := fields_for(store, value)
if logical_index < 0 || logical_index >= len(fields) {
return logical_index
}
result := 0
for _, other_index in fields {
if field_layout_precedes(store, value, other_index, logical_index, selected) {
result += 1
}
}
return result
}
params_for :: proc(store: ^Store, value: Type) -> []Field {
item, ok := node(store, value)
if !ok || item.kind != .Function {
@@ -555,8 +738,14 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
return .Invalid
case VOID:
return .Void
case NORETURN:
return .Noreturn
case ANYOPAQUE:
return .Anyopaque
case INT:
return .Int_Constraint
case UINT:
return .Uint_Constraint
case FLOAT:
return .Float_Constraint
case RANGE:
@@ -595,12 +784,20 @@ is_void :: proc(value: Type) -> bool {
return value == VOID
}
is_noreturn :: proc(value: Type) -> bool {
return value == NORETURN
}
is_anyopaque :: proc(value: Type) -> bool {
return value == ANYOPAQUE
}
is_bool :: proc(value: Type) -> bool {
return value == BOOL
}
is_constraint :: proc(value: Type) -> bool {
return value == INT || value == FLOAT || value == RANGE
return value == INT || value == UINT || value == FLOAT || value == RANGE
}
// constraint_target reports the concrete type a constraint binding (local, or a
@@ -614,6 +811,8 @@ constraint_target :: proc(constraint, inferred: Type, store: ^Store = nil) -> Ty
switch constraint {
case INT:
return inferred if is_concrete_integer(inferred) else INVALID
case UINT:
return inferred if is_unsigned(inferred) else INVALID
case FLOAT:
if is_float(inferred) {
return inferred
@@ -631,6 +830,8 @@ constraint_accepts :: proc(constraint, concrete: Type, store: ^Store = nil) -> b
switch constraint {
case INT:
return is_concrete_integer(concrete)
case UINT:
return is_unsigned(concrete)
case FLOAT:
return is_float(concrete)
case RANGE:
@@ -907,6 +1108,33 @@ is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
return ok && item.kind == .Optional && is_pointer(item.child, store)
}
is_comptime_only :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return false
}
item, ok := node(store, value)
if !ok {
return false
}
if item.kind == .Function {
return true
}
if item.kind == .Pointer || item.kind == .Slice || item.kind == .Range || item.kind == .Fallible {
return false
}
if item.kind == .Array || item.kind == .Optional || item.kind == .Distinct || item.kind == .Enum || item.kind == .Alias {
return is_comptime_only(item.child, store, depth+1)
}
if item.kind == .Struct || item.kind == .Union {
for field in fields_for(store, value) {
if is_comptime_only(field.type, store, depth+1) {
return true
}
}
}
return false
}
is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return false
@@ -915,12 +1143,24 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if value_kind == .Scalar || value_kind == .Pointer {
return true
}
if value_kind == .Slice || value_kind == .Array || value_kind == .Range || value_kind == .Optional {
return !contains_c_struct_by_value(value, store)
if value_kind == .Slice || value_kind == .Array || value_kind == .Range {
item, ok := node(store, value)
return ok && is_runtime_value(item.child, store, depth+1) && !contains_c_struct_by_value(value, store)
}
if value_kind == .Optional {
item, ok := node(store, value)
if !ok {
return false
}
if is_pointer(item.child, store) {
return true
}
return is_runtime_value(item.child, store, depth+1) && !contains_c_struct_by_value(value, store)
}
if value_kind == .Struct || value_kind == .Union {
item, ok := node(store, value)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) &&
!is_comptime_only(value, store)
}
if value_kind == .Fallible {
item, ok := node(store, value)
@@ -936,9 +1176,33 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
return false
}
can_construct_distinct :: proc(from, to: Type, store: ^Store) -> bool {
item, ok := node(store, to)
return ok && item.kind == .Distinct && item.declared && equal(from, item.child)
distinct_backing :: proc(value: Type, store: ^Store) -> (Type, bool) {
item, ok := node(store, value)
if !ok || item.kind != .Distinct || !item.declared {
return INVALID, false
}
return item.child, true
}
distinct_scalar_backing :: proc(value: Type, store: ^Store) -> (Type, bool) {
_, ok := distinct_backing(value, store)
if !ok {
return INVALID, false
}
backing := runtime_representation(value, store)
return backing, is_concrete_scalar(backing)
}
can_retype_distinct :: proc(from, to: Type, store: ^Store) -> bool {
if backing, ok := distinct_backing(to, store); ok && equal(from, backing) {
return true
}
backing, ok := distinct_backing(from, store)
if !ok || !equal(to, backing) {
return false
}
_, scalar := distinct_scalar_backing(from, store)
return scalar
}
runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
@@ -1204,6 +1468,95 @@ function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_
return pointer_node, function_node, pointer_node.child, true
}
callable_function :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) {
item, item_ok := node(store, value)
if item_ok && item.kind == .Function {
return {}, item, value, true
}
return function_pointer(value, store)
}
can_coerce_function_pointer :: proc(actual, expected: Type, store: ^Store) -> bool {
actual_item, actual_ok := node(store, actual)
_, _, expected_function, expected_ok := function_pointer(expected, store)
return actual_ok && actual_item.kind == .Function && expected_ok && equal(actual, expected_function)
}
replace_pointer_child :: proc(store: ^Store, value, child: Type) -> (Type, bool) {
item, ok := node(store, value)
if !ok {
return INVALID, false
}
if item.kind == .Optional {
replaced, replaced_ok := replace_pointer_child(store, item.child, child)
if !replaced_ok || !is_pointer(replaced, store) {
return INVALID, false
}
return optional(store, replaced), true
}
if item.kind != .Pointer {
return INVALID, false
}
item.child = child
return intern(store, item), true
}
restore_mutability :: proc(store: ^Store, value: Type) -> (Type, bool) {
item, ok := node(store, resolve_alias(value, store))
if !ok {
return INVALID, false
}
if item.kind == .Optional {
restored, restored_ok := restore_mutability(store, item.child)
if !restored_ok || !is_pointer(restored, store) {
return INVALID, false
}
return optional(store, restored), true
}
if item.kind != .Pointer && item.kind != .Slice {
return INVALID, false
}
item.mutable = true
return intern(store, item), true
}
same_constcast_shape :: proc(from, to: Type, store: ^Store) -> bool {
from_item, from_ok := node(store, resolve_alias(from, store))
to_item, to_ok := node(store, resolve_alias(to, store))
if !from_ok || !to_ok {
return false
}
if from_item.kind == .Optional || to_item.kind == .Optional {
return from_item.kind == .Optional && to_item.kind == .Optional &&
is_pointer(from_item.child, store) && is_pointer(to_item.child, store) &&
same_constcast_shape(from_item.child, to_item.child, store)
}
return (from_item.kind == .Pointer || from_item.kind == .Slice) &&
from_item.kind == to_item.kind &&
from_item.child == to_item.child &&
from_item.many == to_item.many &&
to_item.mutable &&
from_item.has_sentinel == to_item.has_sentinel &&
(!from_item.has_sentinel || from_item.sentinel == to_item.sentinel)
}
same_pointer_shape :: proc(left, right: Type, store: ^Store) -> bool {
left_item, left_ok := node(store, left)
right_item, right_ok := node(store, right)
if !left_ok || !right_ok {
return false
}
if left_item.kind == .Optional || right_item.kind == .Optional {
return left_item.kind == .Optional && right_item.kind == .Optional &&
same_pointer_shape(left_item.child, right_item.child, store)
}
return left_item.kind == .Pointer && right_item.kind == .Pointer &&
left_item.many == right_item.many &&
left_item.mutable == right_item.mutable &&
left_item.has_sentinel == right_item.has_sentinel &&
(!left_item.has_sentinel || left_item.sentinel == right_item.sentinel)
}
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && (item.kind == .Struct || item.kind == .Union) && item.c_layout
@@ -1318,6 +1671,10 @@ with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool {
from_node, from_ok := node(store, from)
to_node, to_ok := node(store, to)
if from_ok && to_ok && (from_node.kind == .Optional || to_node.kind == .Optional) {
return from_node.kind == .Optional && to_node.kind == .Optional &&
can_weaken_pointer(from_node.child, to_node.child, store)
}
if !from_ok || !to_ok || from_node.kind != .Pointer || to_node.kind != .Pointer ||
from_node.many != to_node.many || (to_node.mutable && !from_node.mutable) {
return false
@@ -1327,9 +1684,12 @@ can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool {
return false
}
same_child := from_node.child == to_node.child
anyopaque_erasure := to_node.child == ANYOPAQUE &&
(is_runtime_value(from_node.child, store) ||
is_opaque_struct(from_node.child, store))
c_string := from_node.many && from_node.child == U8 && to_node.child == C_CHAR &&
from_node.has_sentinel && from_node.sentinel == 0 && !to_node.mutable
return same_child || c_string
return same_child || anyopaque_erasure || c_string
}
can_weaken_slice :: proc(from, to: Type, store: ^Store) -> bool {
@@ -1387,7 +1747,7 @@ is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
#partial switch kind(value, store) {
case .Scalar:
return u64(bits(value, selected)/8)
return u64((bits(value, selected)+7)/8)
case .Pointer:
return u64(target.pointer_bits(selected)/8)
case .Slice:
@@ -1419,11 +1779,18 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
}
offset: u64
max_align: u64 = 1
for field in fields_for(store, value) {
field_align := u64(alignment_of(field.type, store, selected))
offset = (offset+field_align-1)/field_align*field_align
offset += size(field.type, store, selected)
max_align = max(max_align, field_align)
if !item.c_layout {
for field in fields_for(store, value) {
offset += size(field.type, store, selected)
max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
}
} else {
for field in fields_for(store, value) {
field_align := u64(alignment_of(field.type, store, selected))
offset = (offset+field_align-1)/field_align*field_align
offset += size(field.type, store, selected)
max_align = max(max_align, field_align)
}
}
return (offset+max_align-1)/max_align*max_align
case .Union:
@@ -1565,6 +1932,12 @@ can_coerce_c_scalar :: proc(from, to: Type, selected := target.DEFAULT) -> bool
}
widest :: proc(a, b: Type) -> Type {
if is_noreturn(a) {
return b
}
if is_noreturn(b) {
return a
}
if equal(a, b) && is_concrete_scalar(a) {
return a
}
@@ -1590,12 +1963,28 @@ smallest_signed_for_literal :: proc(value: i64) -> Type {
return I64
}
smallest_unsigned_for_literal :: proc(value: u64) -> Type {
if value <= 255 {
return U8
}
if value <= 65535 {
return U16
}
if value <= 4294967295 {
return U32
}
return U64
}
name :: proc(value: Type) -> string {
switch value {
case INVALID: return "<invalid>"
case VOID: return "void"
case NORETURN: return "noreturn"
case ANYOPAQUE: return "anyopaque"
case BOOL: return "bool"
case INT: return "int"
case UINT: return "uint"
case FLOAT: return "float"
case RANGE: return "range"
case I8: return "i8"
+8589 -300
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
b :: import "@std/build"
config :: b.BuildConfig{
name = "hello",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &[],
}
+3
View File
@@ -0,0 +1,3 @@
main func() i32 {
return 0
}
+10
View File
@@ -0,0 +1,10 @@
# Build configuration surface for `brolang build` (v0).
BuildConfig :: struct {
name []u8
source []u8
libraries [][]u8
lib_paths [][]u8
includes [][]u8
defines [][]u8
links [][]u8
}
+11
View File
@@ -0,0 +1,11 @@
b :: import "@std/build"
config :: b.BuildConfig{
name = "manual_build",
source = "src",
libraries = &[],
lib_paths = &[],
includes = &[],
defines = &[],
links = &["examples/build/manual/native.c"],
}
+3
View File
@@ -0,0 +1,3 @@
int foreign_add(int a, int b) {
return a + b;
}
+5
View File
@@ -0,0 +1,5 @@
foreign_add c_func(a, b i32) i32
main func() i32 {
return foreign_add(20, 22)
}
+10
View File
@@ -0,0 +1,10 @@
# Build configuration surface for `brolang build` (v0).
BuildConfig :: struct {
name []u8
source []u8
libraries [][]u8
lib_paths [][]u8
includes [][]u8
defines [][]u8
links [][]u8
}
+18
View File
@@ -0,0 +1,18 @@
# Illustrative build.bro for a raylib program (not run in CI: needs raylib
# installed).
#
# Generate the raylib bindings once with:
# brolang translate-c /opt/homebrew/Cellar/raylib/*/include/raylib.h > game/raylib.bro
# then:
# brolang build examples/build/raylib
b :: import "@std/build"
config :: b.BuildConfig{
name = "game",
source = "source",
libraries = &["raylib"],
lib_paths = &["/opt/homebrew/lib"],
includes = &["/opt/homebrew/include"],
defines = &[],
links = &["-framework", "Cocoa", "-framework", "IOKit", "-framework", "CoreVideo", "-framework", "OpenGL"],
}
+250
View File
@@ -0,0 +1,250 @@
# Bouncing-shapes sandbox — a tour of brolang on top of raylib.
#
# Click to spawn a shape under the cursor, WASD/arrows to blow them around,
# SPACE to clear. The shape nearest the cursor is highlighted with its stats.
#
# Feature tour: native structs, enums, tagged unions + match (value, statement,
# payload capture, void variants, contextual construction), optionals + unwrap,
# value-loops (`yield :blk`), fallible functions with try/catch, defer, ranged
# and pointer-capturing for-loops, while, break/continue, compound assignment,
# scalar C interop, and multi-line strings.
rl :: import "@vendor/raylib"
# --- screen and physics constants -------------------------------------------
W :: 900
H :: 540
CAP usize :: 64
GRAV :: 0.18 # downward pull per frame
DAMP :: 0.82 # energy kept on a wall bounce
FORCE :: 0.9 # wind impulse from a key press
SPINMAX :: 3.0 # spin magnitude cap
RING_PAD :: 6.0 # highlight ring spacing
# --- shapes -----------------------------------------------------------------
Kind :: enum {
circle
square
triangle
}
Ball :: struct {
x f32
y f32
dx f32
dy f32
radius f32
kind Kind
}
# A frame's worth of player intent, as a tagged union. Each arm carries exactly
# the data that action needs (or `void` when it needs null).
Command :: union(enum) {
spawn rl.Vector2 # spawn a shape at this point
push struct { dx f32, dy f32 } # blow every shape this way
clear void
idle void
}
# Spawning can fail when the backing array is full; the error carries the cap so
# the caller can report it.
SpawnError :: union(enum) {
full struct { cap usize }
}
# value-match used as an expression source: each arm yields a Color.
color_for func(k Kind) rl.Color {
col :: match k {
.circle: rl.Color{ r = 235, g = 90, b = 90, a = 255 }
.square: rl.Color{ r = 90, g = 205, b = 130, a = 255 }
.triangle: rl.Color{ r = 105, g = 160, b = 245, a = 255 }
}
return col
}
# value-match dispatching to a contextual return, used to cycle spawn kind.
next_kind func(k Kind) Kind {
return match k {
.circle: .square
.square: .triangle
.triangle: .circle
}
}
# Read this frame's input into a single Command (contextual union construction:
# `.clear`, `.spawn{...}`, `.push{...}` are built against the return type).
read_command func() Command {
if rl.IsMouseButtonPressed(rl.MOUSE_BUTTON_LEFT) return .spawn{ rl.GetMousePosition() }
if rl.IsKeyPressed(rl.KEY_SPACE) return .clear
fx f32 := 0.0
fy f32 := 0.0
if rl.IsKeyDown(rl.KEY_A) fx -= FORCE
if rl.IsKeyDown(rl.KEY_D) fx += FORCE
if rl.IsKeyDown(rl.KEY_W) fy -= FORCE
if rl.IsKeyDown(rl.KEY_S) fy += FORCE
if rl.IsKeyDown(rl.KEY_LEFT) fx -= FORCE
if rl.IsKeyDown(rl.KEY_RIGHT) fx += FORCE
if rl.IsKeyDown(rl.KEY_UP) fy -= FORCE
if rl.IsKeyDown(rl.KEY_DOWN) fy += FORCE
moved :: fx != 0.0 or fy != 0.0
if (moved) return .push{ dx = fx, dy = fy }
return .idle
}
# Fallible capacity check: returns the slot index to fill, or fails `.full`.
reserve func(used usize) usize ! SpawnError {
if (used >= CAP) return .full{ cap = CAP }
return used
}
# Advance one ball: gravity, integrate, bounce off the four walls with damping.
# `b` is a pointer into the array, so the writes land in place.
step func(b @mut Ball) void {
b.dy += GRAV
b.x += b.dx
b.y += b.dy
if (b.x < b.radius) {
b.x = b.radius
b.dx = -b.dx * DAMP
}
right :: f32(W) - b.radius
if (b.x > right) {
b.x = right
b.dx = -b.dx * DAMP
}
if (b.y < b.radius) {
b.y = b.radius
b.dy = -b.dy * DAMP
}
floor :: f32(H) - b.radius
if (b.y > floor) {
b.y = floor
b.dy = -b.dy * DAMP
}
}
draw_ball func(b @Ball, highlight bool) void {
col :: color_for(b.kind)
center rl.Vector2 := rl.Vector2{ x = b.x, y = b.y }
match b.kind {
.circle: rl.DrawCircleV(center, b.radius, col)
.square: rl.DrawPoly(center, 4, b.radius, 45.0, col)
.triangle: rl.DrawPoly(center, 3, b.radius, 0.0, col)
}
if highlight {
ring rl.Color := rl.Color{ r = 250, g = 245, b = 200, a = 255 }
rl.DrawPoly(center, 24, b.radius + RING_PAD, 0.0, ring)
}
}
main func() i32 {
rl.SetConfigFlags(rl.FLAG_MSAA_4X_HINT)
rl.InitWindow(W, H, "brolang — bouncing shapes")
defer rl.CloseWindow() # runs on every exit path out of main
rl.SetTargetFPS(60)
help ::
`[click] spawn a shape [WASD/arrows] blow wind
`[space] clear
balls [CAP]mut Ball := undefined
count usize := 0 # number of live balls, in slots 0..count
kc Kind := .circle # next kind to spawn
spin f32 := 1.0 # rotates spawn velocity for variety
at_cap bool := false # show the "at capacity" banner
bg :: rl.Color{ r = 24, g = 26, b = 34, a = 255 }
text :: rl.Color{ r = 225, g = 225, b = 230, a = 255 }
warn :: rl.Color{ r = 245, g = 180, b = 90, a = 255 }
while !rl.WindowShouldClose() {
# --- input -> command -----------------------------------------------
cmd :: read_command()
match cmd {
.spawn |at|: {
slot :: reserve(count) catch |e| {
match e {
.full |info|: at_cap = true
}
yield CAP # sentinel: >= CAP means "didn't fit"
}
if (slot < CAP) {
balls[slot] = Ball{
x = f32(at.x), y = f32(at.y),
dx = FORCE * 6.0 * spin,
dy = -FORCE * 5.0,
radius = 18.0,
kind = kc,
}
count += 1
kc = next_kind(kc)
spin = -spin * 1.2
if (spin > SPINMAX or spin < -SPINMAX) spin = 1.0
at_cap = false
}
}
.push |f|: {
for (&balls) |@b, i| {
if (i >= count) break
b.dx += f.dx
b.dy += f.dy
}
}
.clear: {
count = 0
at_cap = false
}
.idle: {}
}
# --- physics --------------------------------------------------------
for (&balls) |@b, i| {
if (i >= count) break
step(b)
}
# --- which shape is under the cursor? (optional via a value-loop) ---
mouse :: rl.GetMousePosition()
sel :: for 0..(count) |i| hover: {
c rl.Vector2 := rl.Vector2{ x = balls[i].x, y = balls[i].y }
if rl.CheckCollisionPointCircle(mouse, c, balls[i].radius) yield :hover i
yield null
}
# --- draw -----------------------------------------------------------
rl.BeginDrawing()
rl.ClearBackground(bg)
for (&balls) |@b, i| {
if (i >= count) break
hot bool := false
if sel |s| {
if (s == i) hot = true # true only for the hovered ball
}
draw_ball(b, hot)
}
rl.DrawText(help, 16, 16, 20, text)
if sel |s| {
label :: match balls[s].kind {
.circle: "circle"
.square: "square"
.triangle: "triangle"
}
rl.DrawText(label, 16, H - 36, 20, text)
}
if (at_cap) rl.DrawText("at capacity", W - 170, 16, 20, warn)
rl.DrawFPS(W - 90, H - 28)
rl.EndDrawing()
}
return 0
}
+10
View File
@@ -0,0 +1,10 @@
# Build configuration surface for `brolang build` (v0).
BuildConfig :: struct {
name []u8
source []u8
libraries [][]u8
lib_paths [][]u8
includes [][]u8
defines [][]u8
links [][]u8
}
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -19,7 +19,7 @@ native_pair func(value native.Pair) native.Pair {
global_pair native.Pair :: native.Pair { left = 1, right = 2 }
main func() i32 {
pair native.Pair = native_pair(global_pair)
pair native.Pair := native_pair(global_pair)
pair.left = 10
pair = native.echo_pair(pair)
pairs [1]native.Pair :: [pair]
@@ -33,7 +33,7 @@ main func() i32 {
tail = 13,
})
arrays native.Arrays :: native.echo_arrays(native.Arrays { values = [14, 15, 16] })
choice native.Choice = native.Choice { decimal = 1.0 }
choice native.Choice := native.Choice { decimal = 1.0 }
choice.integer = 17
choice = native.echo_choice(choice)
forward native.Forward :: native.echo_forward(native.Forward { value = 19 })
+1 -1
View File
@@ -5,7 +5,7 @@ native :: import "../include/native.h"
# self-referential `?*mut Node` field as C-layout-compatible.
main func() i32 {
node native.Node = native.Node { next = none, value = 7 }
node native.Node := native.Node { next = null, value = 7 }
if node.next |_| {
return 1
}
@@ -0,0 +1,23 @@
@hide sibling func() i32 {
return 1
}
@hide Sibling :: struct {
value i32
}
@hide sibling_value :: 1
@hide:file file_sibling func() i32 {
return 1
}
@hide:file File_Sibling :: struct {
value i32
}
@hide:file file_sibling_value :: 1
collision c_func() i32 {
return 1
}
@@ -0,0 +1,23 @@
import "../dep"
@hide collision func() i32 {
return 2
}
read_sibling func(value Sibling) i32 {
return value.value + sibling_value
}
read_sibling_value func() i32 {
return sibling_value
}
read_file_sibling func(value File_Sibling) i32 {
return value.value
}
main func() i32 {
return file_sibling() + file_sibling_value + sibling() + dep.secret() +
read_sibling(Sibling { value = 1 }) + read_sibling_value() +
read_file_sibling(File_Sibling { value = 1 })
}
@@ -0,0 +1,3 @@
@hide:package secret c_func() i32 {
return 1
}
+58
View File
@@ -0,0 +1,58 @@
@hide:package
helper func() i32 {
thing Thing := Thing { value = value }
return thing.value
}
@hide
Thing :: struct {
value i32
}
@hide Local_Union :: union {
value i32
}
@hide Local_Enum :: enum {
value
}
@hide Local_Opaque :: opaque
@hide Local_Distinct :: distinct i32
@hide Local_Alias :: alias i32
@hide value :: 1
@hide mutable_value i32 := 1
_foreign c_func() i32 {
return 1
}
_C_Record :: c_struct {
value c_int
}
@hide local_foreign c_func() i32 {
return 1
}
@hide Local_C_Record :: c_struct {
value c_int
}
@hide:file
file_helper func() i32 {
return 1
}
@hide:file File_Thing :: struct {
value i32
}
@hide:file file_value :: 1
from_a func() i32 {
record Local_C_Record := Local_C_Record { value = 0 }
thing File_Thing := File_Thing { value = file_value }
return helper() + local_foreign() + file_helper() + thing.value + i32(record.value)
}
+25
View File
@@ -0,0 +1,25 @@
import "../dep"
Box :: struct {
_value i32
}
@hide:file file_helper func() i32 {
return 2
}
@hide:file File_Thing :: struct {
value i32
}
@hide:file file_value :: 2
from_b func(_input i32) i32 {
_local Box := Box { _value = _input }
record _C_Record := _C_Record { value = 0 }
thing Thing := Thing { value = value }
file_thing File_Thing := File_Thing { value = file_value }
return helper() + thing.value + _local._value + _foreign() + i32(record.value) +
dep._visible() + file_helper() + file_thing.value
}
@@ -0,0 +1,3 @@
main func() i32 {
return from_a() + from_b(3)
}
@@ -0,0 +1,3 @@
_visible func() i32 {
return 1
}
@@ -1 +1 @@
bad = 1
bad := undefined
@@ -1,6 +1,6 @@
import "../math"
read func(value i8) int {
read func(_ i8) int {
return math.value
}
@@ -1,5 +1,5 @@
main func() i32 {
n usize = 4
items [n]mut i32 = undefined
n usize := 4
items [n]mut i32 := undefined
return 0
}
+95
View File
@@ -0,0 +1,95 @@
arraylist :: import "@std/arraylist"
mem :: import "@std/mem"
std :: import "@std"
Token :: struct { value i32 }
ScanDiagnostic :: struct { value i32 }
State :: struct {
tokens std.ArrayList(Token)
diagnostics std.ArrayList(ScanDiagnostic)
}
arrlist_test std.ArrayList(Token) := arraylist.init(mem.c_allocator)
init func(allocator mem.Allocator) State {
return State {
tokens = arraylist.init(allocator),
diagnostics = arraylist.init(allocator),
}
}
@hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
return null
}
@hide fail_realloc func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
return null
}
@hide fail_free func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
@hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
alloc = fail_alloc,
realloc = fail_realloc,
free = fail_free,
}
@hide fail_allocator mem.Allocator :: mem.Allocator {
context = null,
vtable = &fail_vtable,
}
run func() i32 ! mem.AllocError {
state State :: init(mem.c_allocator)
_ = state
values std.ArrayList(i32) := arraylist.init(mem.c_allocator)
defer arraylist.deinit(&values)
if (values.items.len != 0 or values.capacity != 0) return 1
i usize := 0
while i < 20 : i += 1 {
arraylist.append(&values, i32(i)) catch |_| {
return .out_of_memory
}
}
if (values.items.len != 20 or values.capacity < 20) return 2
if (values.items[0] != 0 or values.items[19] != 19) return 3
values.items[3] = 33
if (values.items[3] != 33) return 4
arraylist.reserve(&values, 50) catch |_| {
return .out_of_memory
}
if (values.capacity < 50 or values.items.len != 20 or values.items[19] != 19) return 5
capacity usize :: values.capacity
arraylist.clear(&values)
if (values.items.len != 0 or values.capacity != capacity) return 6
arraylist.append(&values, 7) catch |_| {
return .out_of_memory
}
if (values.items.len != 1 or values.items[0] != 7 or values.capacity != capacity) return 7
empty_values arraylist.ArrayList([0]u8) := arraylist.init(mem.c_allocator)
defer arraylist.deinit(&empty_values)
zero [0]u8 :: []
arraylist.append(&empty_values, zero) catch |_| {
return .out_of_memory
}
if (empty_values.items.len != 1) return 8
failed arraylist.ArrayList(i32) := arraylist.init(i32, fail_allocator)
failed_as_expected bool := false
arraylist.append(&failed, 1) catch |_| {
failed_as_expected = true
}
if (failed_as_expected == false or failed.items.len != 0 or failed.capacity != 0) return 9
arraylist.deinit(&failed)
return 0
}
main func() i32 {
return run() catch 100
}
+62
View File
@@ -0,0 +1,62 @@
fold_bits func(value u8) u8 {
return (~value & 255) xor 15
}
folded u8 :: fold_bits(240)
contextual u8 :: ~0 & 255
contextual_shift u8 :: 1 << 7
Buffer :: alias [u8(1) << 3]u8
runtime_left func(value u16, count u8) u16 {
return value << count
}
c_count_shift func(value u8, count c_uint) u8 {
return value << count
}
c_count_fold u8 :: c_count_shift(3, 2)
main func() i32 {
buffer Buffer := undefined
if (folded != 0) return 1
if (contextual != 255) return 28
if (contextual_shift != 128) return 29
if (buffer.len != 8) return 2
if ((u8(240) & u8(204)) != 192) return 3
if ((u8(240) | u8(15)) != 255) return 4
if ((u8(240) xor u8(255)) != 15) return 5
if ((u8(129) << 1) != 2) return 6
if ((i8(-4) >> 1) != -2) return 7
if ((u8(128) >> 1) != 64) return 8
if ((u8(64) <<| 2) != 255) return 9
if ((i8(64) <<| 2) != 127) return 10
if ((i8(-64) <<| 2) != -128) return 11
if ((u8(1) <<| 8) != 255) return 12
if ((u8(0) <<| 80) != 0) return 13
if (runtime_left(3, 4) != 48) return 14
if (c_count_shift(3, 2) != 12) return 30
if (c_count_fold != 12) return 31
if (~u16(0) != 65535) return 16
if ((i16(-2) >> 1) != -1) return 17
if ((u32(2147483648) >> 31) != 1) return 18
if ((i32(-2147483647) << 1) != 2) return 19
if ((~u64(0) >> 63) != 1) return 20
if ((i64(-2) >> 1) != -1) return 21
if ((c_uchar(128) >> 7) != 1) return 22
if ((c_int(-2) >> 1) != -1) return 23
if ((c_uint(3) << 4) != 48) return 24
if ((c_ulonglong(1) <<| 64) != ~c_ulonglong(0)) return 25
if ((c_ushort(240) xor c_ushort(255)) != 15) return 26
if ((c_longlong(64) <<| 60) != maxval!(c_longlong)) return 27
value u8 := 3
value <<= 2
value |= 1
value xor= 5
value &= 15
value >>= 1
value <<|= 7
if (value != 255) return 15
return 42
}
+6 -6
View File
@@ -7,8 +7,8 @@
main func() i32 {
# 1. `break` out of a `while` once i reaches 5.
i i32 = 0
a i32 = 0
i i32 := 0
a i32 := 0
while i < 100 : i += 1 {
if (i == 5) break
a += 1
@@ -16,7 +16,7 @@ main func() i32 {
if (a != 5) return 101
# 2. `continue` past n == 3 while summing 0..9 (45 - 3 = 42).
b i32 = 0
b i32 := 0
for 0..10 |n| {
if (n == 3) continue
b = b + n
@@ -25,7 +25,7 @@ main func() i32 {
# 3. Nested loops: the inner `break` exits only the inner loop, so the outer
# loop still runs all three iterations (each contributing one y == 0 pass).
c i32 = 0
c i32 := 0
for 0..3 |x| {
for 0..3 |y| {
if (y == 1) break
@@ -38,7 +38,7 @@ main func() i32 {
# 4. `continue` on the final element of an inclusive range bounded by the
# element type's maximum must exit cleanly, not overflow the increment.
hi u8 :: 255
d i32 = 0
d i32 := 0
for 0..=hi |v| {
if (v == 255) continue
d += 1
@@ -47,7 +47,7 @@ main func() i32 {
# 5. `while true` is exitable via `break` (so it is not an infinite loop and
# the code after it is reachable).
e i32 = 0
e i32 := 0
while true {
e += 1
if (e == 7) break
@@ -1,8 +1,7 @@
# Milestone 6: compound assignment (`+=`, `-=`, `*=`, `/=`) and the binary
# arithmetic operators `-`, `*`, `/` with multiplicative precedence.
# Compound assignment (`+=`, `-=`, `*=`, `/=`) and explicit integer division.
check_float func() i32 {
x f64 = 10.0
x f64 := 10.0
x /= 4.0 # 2.5
x *= 2.0 # 5.0
x -= 1.0 # 4.0
@@ -14,8 +13,8 @@ check_float func() i32 {
}
check_unsigned func() i32 {
n u32 = 100
n /= 7 # 14 (truncating integer division)
n u32 := 100
n = divtrunc!(n, 7) # 14
n -= 4 # 10
if n == 10 {
return 1
@@ -24,17 +23,17 @@ check_unsigned func() i32 {
}
main func() i32 {
total i32 = 0
total i32 := 0
total += 10 # 10
total -= 3 # 7
total *= 4 # 28
total /= 2 # 14
total = divtrunc!(total, 2) # 14
# binary operators honour precedence: 14 + (2 * 3) - 4 == 16
total = total + 2 * 3 - 4
# compound assignment as a while-loop update
i i32 = 0
i i32 := 0
while i < 5 : i += 1 {
total += 1 # +5 => 21
}
+49
View File
@@ -0,0 +1,49 @@
sum func(a, b int) int {
return a + b
}
max func(a, b int) int {
if a > b {
return a
}
return b
}
nested func(value int) int {
two :: 2
return sum(value, two)
}
make_array func($N usize) [N]u8 {
data [N]u8 := undefined
return data
}
forced :: $sum(1, 2)
main func() i32 {
value i32 :: $sum(20, 22)
choice i32 :: $max(9, 3)
blocked i32 :: ${
local :: 5
yield sum(local, 6)
}
bytes [_]u8 :: make_array($nested(2))
if forced != 3 {
return 1
}
if value != 42 {
return 2
}
if choice != 9 {
return 3
}
if blocked != 11 {
return 4
}
if bytes.len != 4 {
return 5
}
return 0
}
@@ -0,0 +1,102 @@
Point :: struct {
x i32
}
max func($T type, a, b T) T {
if a > b {
return a
}
return b
}
id func($T type, value T) T {
return value
}
buffer func($T type, $N usize, value T) [N]T {
data [N]T := undefined
_ = value
return data
}
zero func($T type) T {
value T := undefined
return value
}
array_len func($T type, $N usize, values [N]T) usize {
return values.len
}
Fixed func($T type, $N usize) type {
return struct {
values [N]T
}
}
fixed_len func($T type, $N usize, value @Fixed(T, N)) usize {
return value.values.len
}
same_type func($Expected, $Actual type, _ Actual) bool {
return $(Expected == Actual)
}
take_i32 func(value i32) i32 {
return value
}
return_zero func() i32 {
return zero()
}
main func() i32 {
a i32 :: 42
b i32 :: 27
if max(a, b) != 42 {
return 1
}
small_a u8 :: 3
small_b u8 :: 9
if max(u8, small_a, small_b) != 9 {
return 2
}
p Point :: Point { x = 11 }
q Point :: id(p)
if q.x != 11 {
return 3
}
bytes [4]u8 :: buffer(small_a)
if bytes.len != 4 {
return 4
}
if array_len(bytes) != 4 {
return 5
}
zero_value i32 :: zero()
_ = zero_value
literal :: id(7)
if literal != 7 {
return 6
}
fixed Fixed(u8, 3) :: Fixed(u8, 3) { values = [1, 2, 3] }
if fixed_len(&fixed) != 3 {
return 7
}
assigned i32 := 1
assigned = zero()
_ = assigned
_ = take_i32(zero())
_ = return_zero()
optional ?u32 := null
if !same_type(?u32, optional) {
return 8
}
if same_type(?u16, optional) {
return 9
}
return 0
}
+320
View File
@@ -0,0 +1,320 @@
State :: enum {
idle
ready
failed
}
Point :: struct {
x i32
y i32
}
Callback_Config :: struct {
call func(value i32) i32
}
Callback_Choice :: union(enum) {
call func(value i32) i32
empty void
}
Box :: union(enum) {
point Point
empty void
}
Error :: enum {
bad
}
make_point func() Point {
return Point { x = 3, y = 4 }
}
sum_loop func(limit i32) i32 {
total i32 := 0
i i32 := 0
while i < limit {
i += 1
if i == 2 {
continue
}
total += i
}
return total
}
sum_for func() i32 {
total i32 := 0
values [_]i32 := [1, 2, 3]
for values |value, index| {
total += value + index
}
return total
}
defer_value func() i32 {
value i32 := 1
{
defer value += 10
value += 1
}
return value
}
describe func(box Box) i32 {
return match box {
.point |p|: p.x + p.y
.empty: 0
}
}
maybe func(flag bool) ?i32 {
if flag {
return 9
}
return null
}
may_fail func(flag bool) i32 ! Error {
if flag {
return .bad
}
return 7
}
increment func(value i32) i32 {
return value + 1
}
double func(value i32) i32 {
return value * 2
}
decrement_c c_func(value i32) i32 {
return value - 1
}
apply_comptime func($callback func(value i32) i32, value i32) i32 {
return callback(value)
}
apply_comptime_c func($callback c_func(value i32) i32, value i32) i32 {
return callback(value)
}
apply_comptime_config func($config Callback_Config, value i32) i32 {
return config.call(value)
}
apply_comptime_array func($callbacks [2]func(value i32) i32, value i32) i32 {
return callbacks[0](value) + callbacks[1](value)
}
apply_comptime_optional func($callback ?func(value i32) i32, value i32) i32 {
return callback?(value)
}
apply_comptime_choice func($choice Callback_Choice, value i32) i32 {
return match choice {
.call |callback|: callback(value)
.empty: value
}
}
call_native func(callback @func(value i32) i32, value i32) i32 {
return callback(value)
}
materialize_callback func($callback func(value i32) i32, value i32) i32 {
return call_native(callback, value)
}
call_fallible func(callback @func(flag bool) i32 ! Error, flag bool) i32 ! Error {
return try callback(flag)
}
use_try func() i32 ! Error {
value :: try may_fail(false)
return value + 1
}
ct_errdefer func(fail bool) i32 ! Error {
trace i32 := 0
defer trace = trace * 10 + 1
errdefer |err| {
if (err == .bad) trace = trace * 10 + 2
}
defer trace = trace * 10 + 3
if (fail) return .bad
return 7
}
ct_try_errdefer func() i32 ! Error {
trace i32 := 0
defer trace = trace * 10 + 4
errdefer |err| {
if (err == .bad) trace = trace * 10 + 5
}
return try may_fail(true)
}
ct_errdefer_check func() i32 {
ok i32 :: ct_errdefer(false) catch 0
if (ok != 7) return 1
explicit_error i32 :: ct_errdefer(true) catch 9
if (explicit_error != 9) return 2
try_error i32 :: ct_try_errdefer() catch 9
if (try_error != 9) return 3
return 42
}
recover func() i32 {
return may_fail(true) catch |e| match e {
.bad: 5
}
}
bump_ptr func(value @mut i32) void {
value^ += 1
}
alias_add func(left @mut i32, right @mut i32) void {
left^ += 2
right^ += 3
}
storage_mutation func() i32 {
values [3]mut i32 := [1, 2, 3]
values[0] += 1
bump_ptr(&values[1])
view []mut i32 := values[..]
for view |@item| {
item^ += 1
}
pointer *mut i32 := view.ptr
pointer[2] += 1
alias_add(&values[0], &view[0])
box Box := Box { point = Point { x = 2, y = 3 } }
match box {
.point |@p|: p.x += values[1]
.empty: values[0] = values[0]
}
if view.len != 3 {
return 0
}
return values[0] + view[1] + pointer[2] + box.point.x
}
GLOBAL :: $sum_loop(4)
ERRDEFER :: $ct_errdefer_check()
main func() i32 {
point Point :: $make_point()
numbers [_]i32 :: $[4, 5, 6]
box Box :: $Box { point = Point { x = 8, y = 1 } }
state State :: $State.ready
name :: $"bro"
value i32 :: $sum_for()
deferred i32 :: $defer_value()
optional i32 :: $maybe(true)?
tried i32 :: $use_try() catch 0
recovered i32 :: $recover()
storage i32 :: $storage_mutation()
called i32 :: $call_native(increment, 11)
comptime_callback i32 :: $apply_comptime(increment, 12)
comptime_literal i32 :: $apply_comptime(func(value i32) i32 { return value + 2 }, 12)
comptime_c_callback i32 :: $apply_comptime_c(decrement_c, 15)
comptime_config i32 :: $apply_comptime_config(Callback_Config {call = increment}, 15)
comptime_array i32 :: $apply_comptime_array([increment, double], 3)
comptime_optional i32 :: $apply_comptime_optional(increment, 16)
comptime_choice i32 :: $apply_comptime_choice(Callback_Choice {call = double}, 9)
fallible_ok i32 :: $call_fallible(may_fail, false) catch 0
fallible_err i32 :: $call_fallible(may_fail, true) catch |e| {
result i32 :: match e {
.bad: 13
}
yield result
}
if point.x + point.y != 7 {
return 1
}
if numbers.len != 3 or numbers[2] != 6 {
return 2
}
if describe(box) != 9 {
return 3
}
if state != State.ready {
return 4
}
if name.len != 3 {
return 5
}
if value != 9 {
return 6
}
if deferred != 12 {
return 7
}
if optional != 9 {
return 8
}
if tried != 8 {
return 9
}
if recovered != 5 {
return 10
}
if GLOBAL != 8 {
return 11
}
if storage != 23 {
return 12
}
if called != 12 {
return 13
}
if call_native(increment, 20) != 21 {
return 14
}
if fallible_ok != 7 {
return 15
}
if fallible_err != 13 {
return 16
}
if ERRDEFER != 42 {
return 17
}
if comptime_callback != 13 or comptime_literal != 14 or
comptime_c_callback != 14 or comptime_config != 16 or
comptime_array != 10 or comptime_optional != 17 or comptime_choice != 18 {
return 18
}
if apply_comptime(increment, 20) != 21 or apply_comptime(increment, 21) != 22 or
apply_comptime(double, 20) != 40 {
return 19
}
if apply_comptime(func(value i32) i32 { return value + 3 }, 20) != 23 {
return 20
}
if apply_comptime_c(decrement_c, 20) != 19 or
apply_comptime_config(Callback_Config {call = increment}, 20) != 21 {
return 21
}
if apply_comptime_array([increment, double], 4) != 13 or
apply_comptime_optional(increment, 4) != 5 or
apply_comptime_choice(Callback_Choice {call = double}, 4) != 8 {
return 22
}
if materialize_callback(increment, 30) != 31 {
return 23
}
return 0
}
@@ -0,0 +1,32 @@
make_array func($N usize) [N]u8 {
data [N]u8 := undefined
return data
}
value func($N usize) usize {
return N
}
main func() i32 {
four [_]u8 :: make_array(4)
if four.len != 4 {
return 1
}
if value(4) != 4 {
return 2
}
eight [_]u8 :: make_array(8)
if eight.len != 8 {
return 3
}
if value(8) != 8 {
return 4
}
again [_]u8 :: [1, 2, 3, 4]
if again.len != 4 {
return 5
}
return 0
}
+12 -12
View File
@@ -6,18 +6,18 @@ observe func(counter @mut i32, value ?i32) ?i32 {
}
main func() i32 {
total i32 = 0
total i32 := 0
# present optional scalar -> binds v to the unwrapped value
a ?i32 = 40
a ?i32 := 40
if a |v| {
total = total + v # 40
} else {
total = total + 99
}
# none -> else branch taken; the binding is not in scope there
b ?i32 = none
# null -> else branch taken; the binding is not in scope there
b ?i32 := null
if b |v| {
total = total + v
} else {
@@ -25,20 +25,20 @@ main func() i32 {
}
# optional pointer present -> binds q to a non-null @i32; deref proves it
n i32 = 0
p ?@i32 = &n
n i32 := 0
p ?@i32 := &n
if p |q| {
total = total + q^ # +0
}
# optional pointer none -> skipped
z ?@i32 = none
if z |q| {
# optional pointer null -> skipped
z ?@i32 := null
if z |_| {
total = total + 1000
}
# guarded multi-unwrap exposes every capture to the guard and then-block
age ?i32 = 2
age ?i32 := 2
if a and age |value, years : value + years == 42| {
total = total
} else {
@@ -46,7 +46,7 @@ main func() i32 {
}
# parenthesized chains and three-value unwraps are equivalent
bonus ?i32 = 0
bonus ?i32 := 0
if (a and age and bonus) |value, years, extra : value + years + extra == 42| {
total = total
} else {
@@ -64,7 +64,7 @@ main func() i32 {
}
# a failed unwrap prevents later expressions from being evaluated
calls i32 = 0
calls i32 := 0
if b and observe(&calls, age) |missing, observed| {
total = total + missing + observed
}
+5 -5
View File
@@ -23,7 +23,7 @@ noisy func() bool {
}
main func() i32 {
total i32 = 0
total i32 := 0
# comparisons drive if / else if / else
total = total + classify(-5) # 1
@@ -44,11 +44,11 @@ main func() i32 {
total = total + 5 # 35
}
# block scoping: inner x shadows outer x, outer is unchanged after the block
x i32 = 1
# block scoping: inner bindings do not escape the block
x i32 := 1
if x == 1 {
x i32 = 100
if x == 100 {
inner_x i32 := 100
if inner_x == 100 {
total = total + 5 # 40
}
}
+95 -9
View File
@@ -1,4 +1,4 @@
# Milestone 19: `defer` and bare block statements.
# Milestone 19: `defer`, `errdefer`, and bare block statements.
#
# `defer <stmt>` runs the statement when the enclosing scope exits, in reverse
# (LIFO) order, on every exit path. A bare `{ ... }` introduces a scope. Each
@@ -7,7 +7,7 @@
# The return value is captured before defers run, so the mutation here does not
# change what is returned (Zig semantics).
spill_check func() i32 {
x i32 = 5
x i32 := 5
defer x = 999
return x
}
@@ -15,7 +15,7 @@ spill_check func() i32 {
# A function-scope defer runs only at function exit; a `break` runs the loop-body
# defer but NOT the enclosing function-scope defer.
enclosing_defer_check func() i32 {
v i32 = 0
v i32 := 0
defer v = v + 100
for 0..3 |i| {
defer v = v + 1
@@ -24,12 +24,72 @@ enclosing_defer_check func() i32 {
return v # 0->1 (i=0 fall-through), ->2 (i=1 break); the +100 runs after capture
}
CleanupError :: enum {
bad
}
ExtraError :: enum {
extra
}
CleanupErrors :: alias CleanupError | ExtraError
explicit_cleanup func(fail bool, trace *mut i32) i32 ! CleanupError {
defer trace^ = trace^ * 10 + 1
errdefer |err| {
if (err == .bad) {
trace^ = trace^ * 10 + 2
} else {
trace^ = 99
}
}
defer trace^ = trace^ * 10 + 3
if (fail) return .bad
return 7
}
fail_cleanup func() i32 ! CleanupError {
return .bad
}
try_cleanup func(trace *mut i32) i32 ! CleanupError {
defer trace^ = trace^ * 10 + 4
errdefer trace^ = trace^ * 10 + 5
return try fail_cleanup()
}
widen_cleanup func(trace *mut i32) i32 ! CleanupErrors {
errdefer |err| {
match err {
.bad: trace^ = trace^ * 10 + 6
.extra: trace^ = 99
}
}
return try fail_cleanup()
}
scoped_cleanup func(trace *mut i32) i32 ! CleanupError {
errdefer trace^ = trace^ * 10 + 7
{
errdefer trace^ = 99
}
return .bad
}
multi_exit_cleanup func(direct bool, trace *mut i32) i32 ! CleanupError {
errdefer |err| {
if (err == .bad) trace^ = trace^ + 8
}
if (direct) return .bad
return try fail_cleanup()
}
main func() i32 {
# 1. return value captured before defers run.
if (spill_check() != 5) return 101
# 2. LIFO ordering, run at end of each loop iteration.
r i32 = 0
r i32 := 0
for 0..1 |i| {
defer r = r * 2 + 1 # registered first -> runs last
defer r = r * 2 # registered second -> runs first
@@ -39,16 +99,16 @@ main func() i32 {
# 3. scoped bare block + scoped defer (defer fires at the closing brace, and
# the block-local is not visible afterwards).
a i32 = 1
a i32 := 1
{
defer a = 4
c i32 = 3
c i32 := 3
_ = c
}
if (a != 4) return 103
# 4. `defer { ... }` block: all its statements run (in order) at scope close.
s i32 = 0
s i32 := 0
{
defer {
s = s + 1
@@ -59,7 +119,7 @@ main func() i32 {
if (s != 60) return 104 # 5 -> 6 -> 60
# 5. `break` flushes the loop-body defer.
bc i32 = 0
bc i32 := 0
for 0..5 |i| {
defer bc = bc + 1
if (i == 2) break
@@ -67,7 +127,7 @@ main func() i32 {
if (bc != 3) return 105 # i=0,1 fall-through + i=2 break
# 6. `continue` flushes the loop-body defer.
cc i32 = 0
cc i32 := 0
for 0..3 |i| {
defer cc = cc + 1
if (i == 1) continue
@@ -78,5 +138,31 @@ main func() i32 {
# 7. break does not run an enclosing function-scope defer.
if (enclosing_defer_check() != 2) return 107
# 8. errdefer is skipped on success; ordinary defers stay interleaved.
trace i32 := 0
if ((explicit_cleanup(false, &trace) catch 0) != 7 or trace != 31) return 108
# 9. Explicit errors run errdefer and expose the captured error.
trace = 0
if ((explicit_cleanup(true, &trace) catch 9) != 9 or trace != 321) return 109
# 10. Propagated errors run both errdefer and ordinary defer.
trace = 0
if ((try_cleanup(&trace) catch 9) != 9 or trace != 54) return 110
# 11. Captures observe the widened enclosing error type.
trace = 0
if ((widen_cleanup(&trace) catch 9) != 9 or trace != 6) return 111
# 12. An errdefer expires when its block exits normally.
trace = 0
if ((scoped_cleanup(&trace) catch 9) != 9 or trace != 7) return 112
# 13. One captured errdefer can be replayed at several distinct error exits.
trace = 0
_ = multi_exit_cleanup(true, &trace) catch 0
_ = multi_exit_cleanup(false, &trace) catch 0
if (trace != 16) return 113
return 42
}
+75 -5
View File
@@ -9,8 +9,14 @@ LocalID :: distinct u32
PointID :: distinct Point
Bytes :: distinct [2]u8
WrappedID :: distinct LocalID
Signed :: distinct i32
Mask :: distinct u8
Real :: distinct f64
static_id LocalID :: LocalID(42)
static_expression LocalID :: (LocalID(3) + 5) * 2
take func(value LocalID) LocalID {
return value
@@ -18,20 +24,84 @@ take func(value LocalID) LocalID {
main func() i32 {
id LocalID :: LocalID(7)
copy LocalID = take(id)
maybe ?LocalID = copy
pointer @LocalID = &copy
copy LocalID := take(id)
maybe ?LocalID := copy
pointer @LocalID := &copy
point PointID :: PointID(Point { x = 1, y = 2 })
bytes Bytes :: Bytes([3, 4])
wrapped WrappedID :: WrappedID(id)
remote ids.UserID :: ids.UserID(8)
remote_copy ids.UserID :: ids.make(9)
_ = static_id
if id + 1 != 8 or 1 + id != 8 { return 1 }
if id - 2 != 5 or 2 * id != 14 or id * 2 != 14 { return 2 }
if -Signed(5) != -5 { return 3 }
real Real :: Real(1.5)
if real + 0.5 != 2.0 or 0.5 + real != 2.0 or real - 0.5 != 1.0 or
real * 2.0 != 3.0 or real / 0.5 != 3.0 {
return 4
}
if !(id == 7) or !(7 == id) or !(id != 8) or !(8 != id) or
!(id < 8) or !(6 < id) or !(id <= 7) or !(7 <= id) or
!(id > 6) or !(8 > id) or !(id >= 7) or !(7 >= id) {
return 5
}
signed Signed :: Signed(-7)
if divtrunc!(signed, 3) != -2 or divfloor!(signed, 3) != -3 or
divexact!(Signed(8), 2) != 4 or divceil!(signed, 3) != -2 or
rem!(signed, 3) != -1 or mod!(signed, 3) != 2 {
return 6
}
mask Mask :: Mask(10)
if ~mask != 245 or (mask & 6) != 2 or (mask | 5) != 15 or (mask xor 3) != 9 {
return 7
}
if mask << u8(1) != 20 or mask >> u8(1) != 5 or Mask(128) <<| u8(1) != 255 {
return 8
}
arithmetic Signed := Signed(4)
arithmetic += 3
arithmetic -= 2
arithmetic *= 5
fraction Real := Real(3.0)
fraction /= 2.0
if arithmetic != 25 or fraction != 1.5 { return 9 }
bits Mask := Mask(3)
bits |= 8
bits xor= 2
bits &= 9
bits <<= u8(1)
bits >>= u8(1)
bits <<|= u8(5)
if bits != 255 { return 10 }
values [10]u8 := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
if values[usize(LocalID(4))] != 4 { return 11 }
section []u8 := values[usize(LocalID(2))..usize(LocalID(5))]
if section.len != 3 or section[usize(0)] != 2 or section[usize(2)] != 4 { return 12 }
if u32(id) != 7 or usize(id) != 7 or f64(id) != 7.0 { return 13 }
extracted LocalID := LocalID(wrapped)
if extracted != id { return 14 }
minimum Signed := minval!(Signed)
maximum Mask := maxval!(Mask)
nested_max WrappedID := maxval!(WrappedID)
if i32(minimum) != minval!(i32) or u8(maximum) != 255 or u32(nested_max) != maxval!(u32) {
return 15
}
if static_expression != 16 { return 16 }
_ = maybe
_ = pointer
_ = point
_ = bytes
_ = wrapped
_ = remote
_ = remote_copy
return 0
@@ -3,6 +3,9 @@ Animal :: enum {
cat
}
STATE_STARTED c_int :: 10
STATE_STOPPED c_int :: 20
favorite func() Animal {
return .cat
}
+6 -3
View File
@@ -1,9 +1,9 @@
animals :: import "./animals"
State :: enum(u16) {
started = 10
started = animals.STATE_STARTED
running
stopped = 20
stopped = animals.STATE_STOPPED
}
initial State :: State.started
@@ -17,10 +17,13 @@ identity c_func(value State) State {
}
main func() i32 {
state State = identity(.running)
state State := identity(.running)
values [2]State :: [.started, State.stopped]
animal animals.Animal :: animals.Animal.dog
if same(state, .running) and
u16(State.started) == 10 and
u16(State.running) == 11 and
u16(State.stopped) == 20 and
values[0] != values[1] and
animal != animals.favorite() and
initial == State.started {
+4 -4
View File
@@ -125,7 +125,7 @@ inline_detail func(value i32) i32 ! union(enum) {
}
main func() i32 {
acc i32 = 0
acc i32 := 0
a :: maybe(0) catch 7
b :: maybe(4) catch 99
@@ -166,11 +166,11 @@ main func() i32 {
acc = acc + a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p
acc = acc + pick(.left)
r Right = .right
r Right := .right
acc = acc + pick(r)
box BoxA = .a{8}
box BoxA := .a{8}
acc = acc + payload(box)
empty BoxB = .b
empty BoxB := .b
acc = acc + payload(empty)
acc = acc + payload(.a{9})
+3 -3
View File
@@ -5,9 +5,9 @@ pass func(value range) range {
}
main func() i32 {
total i32 = 0
total i32 := 0
items [3]mut i32 = [1, 2, 3]
items [3]mut i32 := [1, 2, 3]
for items |item, index| {
total = total + item
_ = index
@@ -17,7 +17,7 @@ main func() i32 {
item^ = item^ + 1
}
view []mut i32 = items[..]
view []mut i32 := items[..]
for view |@item, index| {
item^ = item^ + 1
_ = index
+10 -10
View File
@@ -6,23 +6,23 @@ make_range func(calls @mut i32, end usize) range {
global_range :: 0..1
main func() i32 {
total i32 = 0
total i32 := 0
first u8 = 254
last u8 = 255
first u8 := 254
last u8 := 255
for first..=last |value| {
_ = value
total = total + 1
}
signed_start i8 = -2
signed_end i8 = 1
signed_start i8 := -2
signed_end i8 := 1
for signed_start..signed_end |value| {
_ = value
total = total + 1
}
limit usize = 3
limit usize := 3
for 0..(limit + 1) |value| {
_ = value
total = total + 1
@@ -41,19 +41,19 @@ main func() i32 {
total = total + 100
}
empty [0]i32 = []
empty [0]i32 := []
for empty |value| {
_ = value
total = total + 100
}
pointed i32 = 3
pointers [1]@mut i32 = [&pointed]
pointed i32 := 3
pointers [1]@mut i32 := [&pointed]
for pointers |pointer| {
total = total + pointer^
}
calls i32 = 0
calls i32 := 0
for make_range(&calls, 2) |value| {
_ = value
total = total + 1
@@ -1,4 +1,4 @@
bad int = 1
bad i32 :: undefined
read_bad func() int {
return bad
@@ -1,4 +1,4 @@
bad int = 1
bad i32 :: undefined
read_bad func() int {
return bad
-27
View File
@@ -1,27 +0,0 @@
heap :: import "@std/mem/heap"
#printf c_func(fmt *c_char, ...) c_int
main func() i32 {
memory ?*mut u8 = heap.alloc(4)
defer heap.free(memory)
if memory |bytes| {
bytes[0] = 10
bytes[1] = 20
bytes[2] = bytes[0] + bytes[1]
_ = printf("bytes[0]: %d\n", bytes[0])
_ = printf("bytes[1]: %d\n", bytes[1])
_ = printf("bytes[2]: %d\n", bytes[2])
_ = printf("bytes[3]: %d\n", bytes[3])
if (bytes[2] != 30) {
return 2
}
return 0
}
return 1
}
@@ -1,6 +1,6 @@
main func() i32 {
items [3]mut i32 = undefined
i i32 = 1
items [3]mut i32 := undefined
i i32 := 1
items[i] = 42
return 0
}
@@ -1,6 +1,6 @@
main func() i32 {
items [3]mut i32 = undefined
i int = 1
items [3]mut i32 := undefined
i u32 := 1
items[i] = 42
return 0
}
+94
View File
@@ -0,0 +1,94 @@
Kind :: enum {
first
second
third
}
Pair :: struct {
left i32
right i32
}
Value :: union(enum) {
number i32
pair Pair
empty void
}
enum_score func(kind Kind) i32 {
result :: match kind {
.first: 1
inline |value|: {
yield match value {
.second: 2
.third: 3
}
}
}
return result
}
equal_value func(a, b Value) bool {
if (tag!(a) != tag!(b)) return false
result :: match a {
inline |value, tag|: {
yield match tag {
.number: value == field!(b, tagname!(tag))
.pair: {
other :: field!(b, tagname!(tag))
yield value.left == other.left and value.right == other.right
}
.empty: {
yield true
}
}
}
}
return result
}
increment func(value @mut Value) void {
match value^ {
inline |@payload, tag|: match tag {
.number: payload^ += 1
.pair: payload.left += 1
.empty: _ = payload
}
}
}
main func() i32 {
if enum_score(.first) != 1 or enum_score(.second) != 2 or enum_score(.third) != 3 {
return 1
}
known :: $tag!(Value{number = 1})
known_direct :: tag!(Value{pair = Pair{left = 0, right = 0}})
if (known != .number) or (known_direct != .pair) {
return 2
}
a Value := .number{41}
b Value := .number{41}
if !equal_value(a, b) or equal_value(a, .pair{left = 41, right = 0}) {
return 3
}
increment(&a)
if a.number != 42 {
return 4
}
pair_a Value := .pair{left = 2, right = 3}
pair_b Value := .pair{left = 2, right = 3}
if !equal_value(pair_a, pair_b) or !equal_value(.empty, .empty) {
return 5
}
increment(&pair_a)
if pair_a.pair.left != 3 {
return 6
}
empty Value := .empty
increment(&empty)
return 0
}
@@ -1,11 +0,0 @@
bad int = 4
read_bad func() int {
return bad
}
derived int :: read_bad()
main func() void {
_ = 1
}
@@ -1,4 +1,4 @@
bad int = 4
bad int := 4
read_bad func() int {
return bad
@@ -1,5 +0,0 @@
bad int = 4
main func() void {
_ = 1
}
@@ -1,5 +0,0 @@
bad int = 4
main func() void {
_ = bad
}
+150
View File
@@ -0,0 +1,150 @@
io :: import "@std/io"
process :: import "@std/process"
@hide read_ok func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
if buffer.len == 0 {
return 0
}
buffer[0] = 'o'
if buffer.len == 1 {
return 1
}
buffer[1] = 'k'
return 2
}
@hide read_too_much func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
return buffer.len + 1
}
@hide read_eof func(_ ?@mut anyopaque, _ io.Handle, _ []mut u8) usize ! io.ReadError {
return 0
}
@hide write_short func(_ ?@mut anyopaque, _ io.Handle, bytes []u8) usize ! io.WriteError {
if bytes.len > 2 {
return 2
}
return bytes.len
}
@hide write_none func(_ ?@mut anyopaque, _ io.Handle, _ []u8) usize ! io.WriteError {
return 0
}
@hide write_too_much func(_ ?@mut anyopaque, _ io.Handle, bytes []u8) usize ! io.WriteError {
return bytes.len + 1
}
ok_reader func() io.Reader {
return io.Reader {
context = null,
handle = io.Handle {file_desc = 0},
read = read_ok,
}
}
bad_reader func() io.Reader {
return io.Reader {
context = null,
handle = io.Handle {file_desc = 0},
read = read_too_much,
}
}
eof_reader func() io.Reader {
return io.Reader {
context = null,
handle = io.Handle {file_desc = 0},
read = read_eof,
}
}
short_writer func() io.Writer {
return io.Writer {
context = null,
handle = io.Handle {file_desc = 0},
write = write_short,
}
}
none_writer func() io.Writer {
return io.Writer {
context = null,
handle = io.Handle {file_desc = 0},
write = write_none,
}
}
bad_writer func() io.Writer {
return io.Writer {
context = null,
handle = io.Handle {file_desc = 0},
write = write_too_much,
}
}
rejects_bad_read func() bool {
buffer [1]mut u8 := [0]
_ = io.read(bad_reader(), buffer[..]) catch |err| {
return err == .read_failed
}
return false
}
rejects_no_progress func() bool {
io.print(none_writer(), "{s}", {"x",}) catch |err| {
return err == .no_progress
}
return false
}
rejects_bad_write func() bool {
_ = io.write(bad_writer(), "x") catch |err| {
return err == .write_failed
}
return false
}
main func(init process.Init) i32 {
system io.Io :: init.io
buffer [2]mut u8 := [0, 0]
count usize :: io.read(ok_reader(), buffer[..]) catch 0
if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' {
return 1
}
eof usize :: io.read(eof_reader(), buffer[..]) catch 1
empty_read usize :: io.read(bad_reader(), buffer[0..0]) catch 1
empty_write usize :: io.write(bad_writer(), "") catch 1
if eof != 0 or empty_read != 0 or empty_write != 0 {
return 5
}
io.print(short_writer(), "{s}{d}", {"partial", 37}) catch |_| {
return 2
}
if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() {
return 3
}
io.print(io.stdout(system), "io-ok {d} {{bro}}\n", {37,}) catch |_| {
return 4
}
io.print(io.stdout(system), "bounds={d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d}\n", {
minval!(i8), maxval!(u8),
minval!(i16), maxval!(u16),
minval!(i32), maxval!(u32),
minval!(i64), maxval!(u64),
minval!(isize), maxval!(usize),
minval!(c_char), maxval!(c_char),
minval!(c_schar), maxval!(c_uchar),
minval!(c_short), maxval!(c_ushort),
minval!(c_int), maxval!(c_uint),
minval!(c_long), maxval!(c_ulong),
minval!(c_longlong), maxval!(c_ulonglong),
0, 0,
1, 1,
-1, 1,
}) catch |_| {
return 6
}
return 0
}
+9 -9
View File
@@ -63,18 +63,18 @@ make_box func() Box {
}
main func() i32 {
acc i32 = 0
acc i32 := 0
dog Data = Data{ dog = 9 }
bird Data = Data{ bird = 38 }
dog Data := Data{ dog = 9 }
bird Data := Data{ bird = 38 }
acc = acc + describe(dog) + describe(bird) # 10 + 40 = 50
# same-type multi-pattern capture
acc = acc + payload_of(dog) + payload_of(bird) # 9 + 38 = 47
# enum statement match, exhaustive, with a multi-pattern arm
a Animal = .bird
rank i32 = 0
a Animal := .bird
rank i32 := 0
match a {
.dog, .cat: rank = 1
.bird: rank = 3
@@ -89,7 +89,7 @@ main func() i32 {
acc = acc + legs # +2
# scalar match: a range arm, a multi-literal arm, and a mandatory else
bucket i32 = 0
bucket i32 := 0
match rank {
0..3: bucket = 1 # exclusive 0,1,2 — does not include 3
3, 4: bucket = 5 # rank is 3
@@ -98,8 +98,8 @@ main func() i32 {
acc = acc + bucket # +5
# void-payload variant: contextual construction (`.empty` coerces to Box) + no-capture arm
e Box = .empty
hit i32 = 0
e Box := .empty
hit i32 := 0
match e {
.point |pt|: hit = pt.x
.empty: hit = 7
@@ -107,7 +107,7 @@ main func() i32 {
acc = acc + hit # +7
# pointer capture mutates the subject's payload in place
b Box = Box{ point = Point{ x = 1, y = 2 } }
b Box := Box{ point = Point{ x = 1, y = 2 } }
match b {
.point |@p|: p.x = 10
.empty: hit = hit
+30
View File
@@ -0,0 +1,30 @@
mem :: import "@std/mem"
eql_test func() i32 {
if (mem.eql("bro", "bro") == false) return 21
if mem.eql("bro", "bra") return 22
if mem.eql("bro", "brolang") return 23
empty []u8 :: ""
if (mem.eql(empty, "") == false) return 24
left [3]i32 :: [1, 2, 3]
same [3]i32 :: [1, 2, 3]
different [3]i32 :: [1, 2, 4]
if (mem.eql(left[..], same[..]) == false) return 25
if mem.eql(left[..], different[..]) return 26
return 0
}
main func() i32 {
eql_result i32 :: eql_test()
if (eql_result != 0) return eql_result
typed_result i32 :: typed_allocator_test()
if (typed_result != 0) return typed_result
raw_result i32 :: raw_allocator_test()
if (raw_result != 0) return raw_result
return task_list_test()
}
@@ -0,0 +1,101 @@
mem :: import "@std/mem"
raw_allocator_test func() i32 {
resized ?*mut u8 := mem.raw_realloc(mem.c_allocator, null, 0, 4, 1)
if resized |bytes| {
bytes[0] = 10
bytes[1] = 20
bytes[2] = 30
bytes[3] = 40
} else {
return 20
}
grown ?*mut u8 := mem.raw_realloc(mem.c_allocator, resized, 4, 8, 1)
if grown |bytes| {
resized = grown
if bytes[0] != 10 or bytes[1] != 20 or bytes[2] != 30 or bytes[3] != 40 {
mem.raw_free(mem.c_allocator, grown, 8, 1)
return 21
}
} else {
mem.raw_free(mem.c_allocator, resized, 4, 1)
return 22
}
shrunk ?*mut u8 := mem.raw_realloc(mem.c_allocator, resized, 8, 2, 1)
if shrunk |bytes| {
resized = shrunk
if bytes[0] != 10 or bytes[1] != 20 {
mem.raw_free(mem.c_allocator, shrunk, 2, 1)
return 23
}
} else {
mem.raw_free(mem.c_allocator, resized, 8, 1)
return 24
}
invalid ?*mut u8 := mem.raw_realloc(mem.c_allocator, resized, 2, 4, 24)
if invalid |memory| {
mem.raw_free(mem.c_allocator, memory, 4, 24)
mem.raw_free(mem.c_allocator, resized, 2, 1)
return 25
}
if resized |bytes| {
if bytes[0] != 10 or bytes[1] != 20 {
mem.raw_free(mem.c_allocator, resized, 2, 1)
return 26
}
}
resized = mem.raw_realloc(mem.c_allocator, resized, 2, 0, 1)
if resized |memory| {
mem.raw_free(mem.c_allocator, memory, 0, 1)
return 27
}
over_aligned ?*mut u8 := mem.raw_alloc(mem.c_allocator, 4, 32)
if over_aligned |bytes| {
bytes[0] = 11
bytes[1] = 22
} else {
return 28
}
over_aligned_grown ?*mut u8 := mem.raw_realloc(mem.c_allocator, over_aligned, 4, 8, 32)
if over_aligned_grown |bytes| {
if bytes[0] != 11 or bytes[1] != 22 {
mem.raw_free(mem.c_allocator, over_aligned_grown, 8, 32)
return 29
}
mem.raw_free(mem.c_allocator, over_aligned_grown, 8, 32)
} else {
mem.raw_free(mem.c_allocator, over_aligned, 4, 32)
return 30
}
zero_alignment ?*mut u8 := mem.raw_alloc(mem.c_allocator, 8, 0)
if zero_alignment |memory| {
mem.raw_free(mem.c_allocator, memory, 8, 0)
return 1
}
bad_alignment ?*mut u8 := mem.raw_alloc(mem.c_allocator, 8, 24)
if bad_alignment |memory| {
mem.raw_free(mem.c_allocator, memory, 8, 24)
return 2
}
aligned ?*mut u8 := mem.raw_alloc(mem.c_allocator, 64, 32)
defer mem.raw_free(mem.c_allocator, aligned, 64, 32)
if aligned |bytes| {
bytes[0] = 1
bytes[63] = 2
if bytes[0] + bytes[63] != 3 {
return 4
}
} else {
return 3
}
return 0
}
@@ -0,0 +1,192 @@
mem :: import "@std/mem"
TaskList :: struct {
ids ?[]mut i32
priorities ?[]mut i32
durations ?[]mut i32
len usize
capacity usize
allocator mem.Allocator
}
task_list_init func(allocator mem.Allocator) TaskList {
return TaskList {
ids = null,
priorities = null,
durations = null,
len = 0,
capacity = 0,
allocator = allocator,
}
}
alloc_i32s func(allocator mem.Allocator, count usize) ?[]mut i32 {
fallback [1]mut i32 := undefined
failed bool := false
values []mut i32 := mem.alloc(allocator, count) catch |_| {
failed = true
yield (&fallback).ptr[..0]
}
if (failed) return null
return values
}
free_i32s func(allocator mem.Allocator, values ?[]mut i32) void {
if values |slice| {
mem.free(allocator, slice)
}
}
task_list_reserve func(list @mut TaskList, capacity usize) bool {
if capacity <= list.capacity {
return true
}
new_ids ?[]mut i32 := alloc_i32s(list.allocator, capacity)
new_priorities ?[]mut i32 := alloc_i32s(list.allocator, capacity)
new_durations ?[]mut i32 := alloc_i32s(list.allocator, capacity)
if (new_ids and new_priorities and new_durations) |ids, priorities, durations| {
if list.len > 0 {
if (list.ids and list.priorities and list.durations) |old_ids, old_priorities, old_durations| {
i usize := 0
while i < list.len : i += 1 {
ids[i] = old_ids[i]
priorities[i] = old_priorities[i]
durations[i] = old_durations[i]
}
} else {
free_i32s(list.allocator, new_ids)
free_i32s(list.allocator, new_priorities)
free_i32s(list.allocator, new_durations)
return false
}
}
free_i32s(list.allocator, list.ids)
free_i32s(list.allocator, list.priorities)
free_i32s(list.allocator, list.durations)
list.ids = new_ids
list.priorities = new_priorities
list.durations = new_durations
list.capacity = capacity
return true
}
free_i32s(list.allocator, new_ids)
free_i32s(list.allocator, new_priorities)
free_i32s(list.allocator, new_durations)
return false
}
task_list_push func(list @mut TaskList, id i32, priority i32, duration i32) bool {
if list.len == list.capacity {
new_capacity usize := 2
if list.capacity != 0 {
new_capacity = list.capacity * 2
}
if task_list_reserve(list, new_capacity) == false {
return false
}
}
if (list.ids and list.priorities and list.durations) |ids, priorities, durations| {
index usize := list.len
ids[index] = id
priorities[index] = priority
durations[index] = duration
list.len += 1
return true
}
return false
}
task_score func(priority i32, duration i32) i32 {
return priority * 10 - duration
}
task_list_best_id func(list @mut TaskList) i32 {
if list.len == 0 {
return -1
}
if (list.ids and list.priorities and list.durations) |ids, priorities, durations| {
best_index usize := 0
best_score i32 := task_score(priorities[0], durations[0])
i usize := 1
while i < list.len : i += 1 {
score i32 := task_score(priorities[i], durations[i])
if score > best_score {
best_score = score
best_index = i
}
}
return ids[best_index]
}
return -1
}
task_list_total_duration func(list @mut TaskList) i32 {
total i32 := 0
if list.durations |durations| {
i usize := 0
while i < list.len : i += 1 {
total += durations[i]
}
}
return total
}
task_list_deinit func(list @mut TaskList) void {
free_i32s(list.allocator, list.ids)
free_i32s(list.allocator, list.priorities)
free_i32s(list.allocator, list.durations)
list.ids = null
list.priorities = null
list.durations = null
list.len = 0
list.capacity = 0
}
task_list_test func() i32 {
tasks TaskList := task_list_init(mem.c_allocator)
defer task_list_deinit(&tasks)
if task_list_push(&tasks, 101, 3, 5) == false {
return 5
}
if tasks.capacity != 2 {
return 6
}
if task_list_push(&tasks, 202, 1, 4) == false {
return 7
}
if tasks.capacity != 2 {
return 8
}
if task_list_push(&tasks, 303, 4, 8) == false {
return 9
}
if tasks.capacity != 4 {
return 10
}
if tasks.len != 3 {
return 11
}
if task_list_best_id(&tasks) != 303 {
return 12
}
if task_list_total_duration(&tasks) != 17 {
return 13
}
return 0
}
@@ -0,0 +1,105 @@
mem :: import "@std/mem"
@hide probe_count func(context ?@mut anyopaque) void {
if context |raw| {
count @mut usize :: ptrcast!(usize, raw)
count^ += 1
}
}
@hide probe_alloc func(context ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
probe_count(context)
return null
}
@hide probe_realloc func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
probe_count(context)
return null
}
@hide probe_free func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
probe_count(context)
}
@hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
alloc = probe_alloc,
realloc = probe_realloc,
free = probe_free,
}
typed_allocator_test func() i32 {
if (sizeof!(mem.Allocator) != 16) return 31
first_calls [1]mut usize := [0]
second_calls [1]mut usize := [0]
first_allocator mem.Allocator :: mem.Allocator {
context = &first_calls,
vtable = &probe_vtable,
}
second_allocator mem.Allocator :: mem.Allocator {
context = &second_calls,
vtable = &probe_vtable,
}
_ = mem.raw_alloc(first_allocator, 1, 1)
_ = mem.raw_realloc(first_allocator, null, 0, 1, 1)
mem.raw_free(first_allocator, null, 0, 1)
_ = mem.raw_alloc(second_allocator, 1, 1)
if (first_calls[0] != 3 or second_calls[0] != 1) return 32
i32_fallback [1]mut i32 := undefined
empty_failed bool := false
empty []mut i32 := mem.alloc(first_allocator, 0) catch |_| {
empty_failed = true
yield (&i32_fallback).ptr[..0]
}
if (empty_failed or empty.len != 0 or first_calls[0] != 3) return 34
mem.free(first_allocator, empty)
if (first_calls[0] != 3) return 33
zero_sized_fallback [1]mut [0]u8 := undefined
zero_sized_failed bool := false
zero_sized []mut [0]u8 := mem.alloc([0]u8, first_allocator, 3) catch |_| {
zero_sized_failed = true
yield (&zero_sized_fallback).ptr[..0]
}
if (zero_sized_failed or zero_sized.len != 3 or first_calls[0] != 3) return 36
_ = zero_sized[2]
mem.free([0]u8, first_allocator, zero_sized)
if (first_calls[0] != 3) return 35
u64_fallback [1]mut u64 := undefined
overflow_fallback_failed bool := false
overflow_fallback []mut u64 := mem.alloc(first_allocator, 0) catch |_| {
overflow_fallback_failed = true
yield (&u64_fallback).ptr[..0]
}
if (overflow_fallback_failed) return 37
overflow_failed bool := false
_ = mem.alloc(u64, first_allocator, maxval!(usize)) catch |_| {
overflow_failed = true
yield overflow_fallback
}
if (overflow_failed == false or first_calls[0] != 3) return 40
typed_failed bool := false
typed []mut i32 := mem.alloc(mem.c_allocator, 4) catch |_| {
typed_failed = true
yield (&i32_fallback).ptr[..0]
}
if (typed_failed) return 38
defer mem.free(mem.c_allocator, typed)
typed[0] = 10
typed[3] = 20
if (typed[0] + typed[3] != 30) return 39
typed = mem.realloc(mem.c_allocator, typed, 8) catch |_| {
return 41
}
if (typed.len != 8 or typed[0] != 10 or typed[3] != 20) return 42
typed = mem.realloc(mem.c_allocator, typed, 2) catch |_| {
return 43
}
if (typed.len != 2 or typed[0] != 10) return 44
return 0
}
+6 -6
View File
@@ -48,26 +48,26 @@ score_for func(k Kind) i32 {
}
main func() i32 {
items [LEN]mut i32 = undefined
items [LEN]mut i32 := undefined
items[0] = 10
items[1] = 20
idx u8 = 2
idx usize := 2
items[idx] = items[0] + items[1]
if (items[2] != 30) return 1
native_i i32 = 12
native_i i32 := 12
if (take_c_int(native_i) != 12) return 2
native_u u8 = 7
native_u u8 := 7
if (take_c_uchar(native_u) != 7) return 3
native_f f32 = 3.25
native_f f32 := 3.25
cf :: take_c_float(native_f)
if (cf < 3.0 or cf > 4.0) return 4
if (cf == 0.0) return 5
native_d f64 = 5.0
native_d f64 := 5.0
cd :: take_c_double(native_d)
if (cd != 5.0) return 6
+129
View File
@@ -0,0 +1,129 @@
io :: import "@std/io"
process :: import "@std/process"
debug :: import "@std/debug"
State :: enum {
idle
running
}
Payload :: union(enum) {
count i32
empty void
}
Pair :: struct { i32, bool }
Config :: struct {
enabled bool
ratio f64
label []u8
state State
values [2]i32
pair Pair
maybe ?i32
payload Payload
}
Count :: distinct i32
Byte :: distinct u8
Ratio :: distinct f32
Inner :: distinct u32
Outer :: distinct Inner
first Config :: Config {
enabled = true,
ratio = 1.5,
label = "bro",
state = .running,
values = [20, 22],
pair = Pair {7, true},
maybe = 9,
payload = Payload {count = 4},
}
same Config :: Config {
enabled = true,
ratio = 1.5,
label = "bro",
state = .running,
values = [20, 22],
pair = Pair {7, true},
maybe = 9,
payload = Payload {count = 4},
}
different Config :: Config {
enabled = false,
ratio = 2.5,
label = "bro",
state = .idle,
values = [21, 21],
pair = Pair {7, false},
maybe = null,
payload = .empty,
}
score func($config Config) i32 {
return config.values[0]
}
Carrier func($config Config) type {
return struct { value i32 }
}
read_carrier func($config Config, carrier Carrier(config)) i32 {
return carrier.value + config.values[0]
}
main func(init process.Init) i32 {
if score(first) != 20 or score(same) != 20 or score(different) != 21 {
return 1
}
carrier Carrier(first) :: Carrier(first) {value = 22}
if read_carrier(carrier) != 42 {
return 2
}
writer io.Writer :: io.stdout(init.io)
positive f64 := 1.0
zero f64 := 0.0
infinity f64 :: positive / zero
nan f64 :: zero / zero
io.print(writer, "{} {} {} {} {s} {d} {b} {o} {x} {X} {c} {e} {{}} {d} {b} {} {} {e}\n", {
true,
-42,
1.5,
State.running,
"bro",
2.5,
10,
10,
255,
255,
u8('A'),
1.5,
minval!(i64),
u64(0),
infinity,
nan,
f32(1.5),
}) catch |_| {
return 3
}
io.print(writer, "{} {d} {b} {o} {x} {X} {c} {e} {}\n", {
Count(i32(-42)),
Count(i32(-42)),
Byte(u8(10)),
Byte(u8(10)),
Byte(u8(255)),
Byte(u8(255)),
Byte(u8('A')),
Ratio(f32(1.5)),
Outer(Inner(u32(7))),
}) catch |_| {
return 4
}
debug.print("debug={} {x}\n", {State.idle, 42})
return 0
}
+30
View File
@@ -0,0 +1,30 @@
Point :: struct {
x i32
}
counter := i32(0)
ratio := 1.0
span := 0..2
point Point := Point { x = 1 }
values [_]mut i32 := [10, 20]
bump func(value @mut i32) void {
value^ += 1
}
main func() i32 {
counter = 10
counter += 5
bump(&counter)
point.x += counter
values[1] = point.x
total i32 := counter + point.x + values[1]
for span |i| {
total += i
}
if ratio == 1.0 {
total += 1
}
return total
}
+1 -1
View File
@@ -1,5 +1,5 @@
main func() i32 {
value i32 = 1
value := i32(1)
value = value + 2
return value
}
+1 -1
View File
@@ -1,4 +1,4 @@
main func() void {
value i8 = 127
value i8 := 127
_ = value + 1
}
+1 -1
View File
@@ -11,7 +11,7 @@ sum_brolang func(a, b int) int {
}
main func() void {
y int = 4
y int := 4
a_add_b_c :: sum_c(1, 2)
a_add_b_brolang :: sum_brolang(1, 2)
_ = y
+1 -1
View File
@@ -1,5 +1,5 @@
main func() i32 {
flag bool = true
flag bool := true
value :: i32(flag)
return value
}
+2 -2
View File
@@ -16,7 +16,7 @@ Thing :: union(enum) {
}
main func() i32 {
x Data = Data{ bird = 37 }
y Thing = Thing{ a = 5 }
x Data := Data{ bird = 37 }
y Thing := Thing{ a = 5 }
return x.bird + y.a
}
+88
View File
@@ -0,0 +1,88 @@
debug :: import "@std/debug"
meta :: import "@std/meta"
Numbers :: struct { i8, i16, i32 }
Row :: struct { value i32 }
format func() []u8 {
return "tuple={d}/{s}, limits={d}/{d}"
}
sum func($T type, value T) i32 {
total i32 := 0
match typeinfo!(T) {
.record |record|: inline for record.fields |field| {
total += i32(field!(value, field.name))
}
else: compile_error!("sum requires a record")
}
return total
}
static_control func($T type, value T) i32 {
total i32 := 0
match typeinfo!(T) {
.record |record|: inline for record.fields |field| {
{
if field.index == 1 {
continue
}
total += i32(field!(value, field.name))
match typeinfo!(field.type) {
.integer: {
if field.index == 2 {
break
}
}
else: _ = 0
}
}
total += 100
}
else: compile_error!("static_control requires a record")
}
return total
}
row_value func(row Row) i32 {
return row.value
}
static_aggregates func() i32 {
total i32 := 0
inline for {Row {value = 2}, Row {value = 40}} |row| {
total += row_value(row)
}
return total
}
main func() i32 {
numbers Numbers := Numbers {1, 2, 39}
singleton :: {42,}
empty :: {}
block_value :: {
yield 42
}
_ = empty
if singleton.0 != block_value {
return 2
}
if sum(Numbers, numbers) != 42 {
return 1
}
if static_control(Numbers, numbers) != 140 {
return 3
}
if static_aggregates() != 42 {
return 4
}
field!(&numbers, "2") += 1
debug.print("hello!\n", {})
debug.print(format(), {
numbers.2,
"bro",
minval!(i64),
maxval!(u64),
})
return 0
}
+44
View File
@@ -0,0 +1,44 @@
Box func($T type) type {
return struct {
value T
}
}
Buffer func($T type, $N usize) type {
if N == 0 {
return struct {
values [0]T
}
}
return struct {
values [N]T
}
}
BoxAlias func($T type) type {
return Box(T)
}
LocalAlias func($T type) type {
chosen :: T
return chosen
}
make_box func($T type, value T) Box(T) {
return Box(T) { value = value }
}
main func() i32 {
box Box(i32) :: make_box(i32, 42)
if (box.value != 42) return 1
aliased BoxAlias(i32) :: box
if (aliased.value != 42) return 3
local_alias LocalAlias(i32) :: 42
if (local_alias != 42) return 6
pointer @Box(i32) :: &box
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 (sizeof!(Buffer(u8, 4)) != 4) return 5
return 0
}
+1 -1
View File
@@ -4,6 +4,6 @@ Val :: union {
}
main func() i32 {
x Val = Val{ n = 42 }
x Val := Val{ n = 42 }
return x.n
}
+12
View File
@@ -0,0 +1,12 @@
warn_only func(value i32, unused i32) i32 {
local i32 := 1
write_only i32 := 2
write_only = 3
consumed i32 := value
_ = consumed
return value
}
main func() i32 {
return warn_only(7, 9)
}
+10 -10
View File
@@ -1,42 +1,42 @@
# Milestone 5: boolean while loops with optional post-iteration updates.
return_before_update func() i32 {
i i32 = 0
i i32 := 0
while true : i = i + 1 {
return i
}
}
main func() i32 {
total i32 = 0
total i32 := 0
# ordinary condition and update
i u32 = 0
i u32 := 0
while i < 5 : i = i + 1 {
total = total + 2
}
# equivalent parenthesized header
j u32 = 0
j u32 := 0
while (j < 4) : (j = j + 1) {
total = total + 3
}
# nested loops and body-local storage
outer u32 = 0
outer u32 := 0
while outer < 2 : outer = outer + 1 {
inner u32 = 0
inner u32 := 0
while inner < 3 : inner = inner + 1 {
total = total + 2
}
}
# The body-local k shadows only inside the body. The update still targets
# Body-local storage stays scoped to the body. The update still targets
# the mutable k declared before the loop.
k u32 = 0
k u32 := 0
while k < 4 : k = k + 1 {
k u32 = 100
if k == 100 {
body_k u32 := 100
if body_k == 100 {
total = total + 2
}
}
+48 -27
View File
@@ -19,7 +19,7 @@ basic func() i32 {
# Typed `T =`: the yield coerces to the annotation.
typed func() i64 {
x i64 = {
x i64 := {
yield 100
}
return x
@@ -29,7 +29,7 @@ typed func() i64 {
# local, but the captured value is unchanged.
spill func() i32 {
v :: {
n i32 = 5
n i32 := 5
defer n = 999
yield n
}
@@ -38,7 +38,7 @@ spill func() i32 {
# Reassignment into an existing mutable local.
reassign func() i32 {
r i32 = 0
r i32 := 0
r = {
yield 7
}
@@ -61,13 +61,13 @@ vif_untyped func(sel i32) i32 {
# Typed `T =`: every branch coerces to the annotation.
vif_typed func(sel i32) i32 {
r i32 = if (sel == 0) { yield 100 } else { yield 200 }
r i32 := if (sel == 0) { yield 100 } else { yield 200 }
return r
}
# Assigned into an existing local.
vif_reassign func(sel i32) i32 {
r i32 = 0
r i32 := 0
r = if (sel == 0) { yield 7 } else { yield 9 }
return r
}
@@ -76,7 +76,7 @@ vif_reassign func(sel i32) i32 {
# the yield.
vif_defer func() i32 {
r :: if (true) {
n i32 = 5
n i32 := 5
defer n = 999
yield n
} else {
@@ -85,16 +85,21 @@ vif_defer func() i32 {
return r # 5
}
vif_expression func(old_entries []u8) usize {
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
return new_size
}
# --- value loops (milestone 20.5) --------------------------------------------
# Labeled `for` used as a value: `yield :blk i` exits early with a value, the
# trailing `yield none` supplies the value when the loop completes. The `{i,
# none}` yields resolve the result to an optional.
# trailing `yield null` supplies the value when the loop completes. The `{i,
# null}` yields resolve the result to an optional.
loop_search func() i32 {
# first i in 0..10 whose square exceeds 40 (6*6=36 no, 7*7=49 yes -> 7).
idx :: for 0..10 |i| blk: {
if (i * i > 40) yield :blk i
yield none
yield null
}
if idx |found| {
if (found == 7) return 0
@@ -103,11 +108,11 @@ loop_search func() i32 {
return 2
}
# Same loop, but nothing matches -> the fall-through `yield none` is the result.
# Same loop, but nothing matches -> the fall-through `yield null` is the result.
loop_none func() i32 {
idx :: for 0..10 |i| blk: {
if (i > 100) yield :blk i
yield none
yield null
}
if idx |found| {
_ = found
@@ -118,10 +123,10 @@ loop_none func() i32 {
# Labeled `while` value loop (label follows the `: update` clause).
loop_while func() i32 {
n i32 = 0
n i32 := 0
found :: while n < 100 : n += 1 blk: {
if (n == 8) yield :blk n
yield none
yield null
}
if found |v| {
if (v == 8) return 0
@@ -159,13 +164,13 @@ orelse_value func(opt ?i32) i32 {
return r
}
# Untyped value loop where `none` is yielded (in a labeled yield) before any
# Untyped value loop where `null` is yielded (in a labeled yield) before any
# concrete value: the element type still resolves to ?<i> from `yield :blk i`.
loop_none_first func() i32 {
r :: for 0..10 |i| blk: {
if (i > 100) yield :blk none
if (i > 100) yield :blk null
if (i * i > 40) yield :blk i # first concrete yield: i == 7
yield none
yield null
}
if r |found| {
if (found == 7) return 0
@@ -194,7 +199,7 @@ lblock func(sel i32) i32 {
# the block's defer runs.
lblock_defer func() i32 {
r :: blk: {
n i32 = 5
n i32 := 5
defer n = 999
if (true) yield :blk n
yield :blk 0
@@ -202,10 +207,10 @@ lblock_defer func() i32 {
return r # 5, not 999
}
# A labeled block whose `{T, none}` yields resolve the result to an optional.
# A labeled block whose `{T, null}` yields resolve the result to an optional.
lblock_optional func(present i32) i32 {
r :: blk: {
if (present == 0) yield :blk none
if (present == 0) yield :blk null
yield :blk 8
}
if r |v| {
@@ -220,7 +225,7 @@ yield_outer func(target i32) i32 {
for 0..3 |col| {
if (row * 3 + col == target) yield :outer (row * 10 + col)
}
yield none
yield null
}
if found |v| {
return v
@@ -230,7 +235,7 @@ yield_outer func(target i32) i32 {
# Plain `break :outer` exits an outer loop from an inner loop.
break_outer func() i32 {
count i32 = 0
count i32 := 0
for 0..3 |a| outer: {
for 0..3 |b| {
count += 1
@@ -242,7 +247,7 @@ break_outer func() i32 {
# A labeled block *statement* (not a value source): `break :blk` exits it early.
stmt_block func(early i32) i32 {
x i32 = 0
x i32 := 0
blk: {
x = 1
if (early == 1) break :blk
@@ -254,7 +259,7 @@ stmt_block func(early i32) i32 {
# `break :search` escapes a nested loop and the block in one jump; the block's
# defer still runs on the way out.
stmt_block_escape func() i32 {
hits i32 = 0
hits i32 := 0
search: {
defer hits += 1000
for 0..10 |i| {
@@ -266,11 +271,24 @@ stmt_block_escape func() i32 {
return hits # 4 + 1000 (defer) = 1004
}
# Item B: a `none` yielded before a concrete `yield :blk` that references a block local.
# 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 `null` yielded before a concrete `yield :blk` that references a block local.
lblock_local func() i32 {
r :: blk: {
val :: 9
if (false) yield :blk none
if (false) yield :blk null
yield :blk val
}
if r |v| {
@@ -293,6 +311,8 @@ main func() i32 {
if (vif_reassign(0) != 7) return 110
if (vif_reassign(9) != 9) return 111
if (vif_defer() != 5) return 112
if (vif_expression("") != 8) return 136
if (vif_expression("abc") != 6) return 137
if (loop_search() != 0) return 113
if (loop_none() != 0) return 114
@@ -301,9 +321,9 @@ main func() i32 {
if (vif_return(0) != 11) return 116
if (vif_return(1) != 55) return 117
if (vif_unwrap(21) != 42) return 118
if (vif_unwrap(none) != 99) return 119
if (vif_unwrap(null) != 99) return 119
if (orelse_value(5) != 5) return 120
if (orelse_value(none) != 7) return 121
if (orelse_value(null) != 7) return 121
if (loop_none_first() != 0) return 122
if (lblock(0) != 10) return 123
@@ -319,6 +339,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
}
+120
View File
@@ -0,0 +1,120 @@
# generated by brolang translate-c from errno.h
errno_t :: alias c_int
EPERM c_int :: 1
ENOENT c_int :: 2
ESRCH c_int :: 3
EINTR c_int :: 4
EIO c_int :: 5
ENXIO c_int :: 6
E2BIG c_int :: 7
ENOEXEC c_int :: 8
EBADF c_int :: 9
ECHILD c_int :: 10
EDEADLK c_int :: 11
ENOMEM c_int :: 12
EACCES c_int :: 13
EFAULT c_int :: 14
ENOTBLK c_int :: 15
EBUSY c_int :: 16
EEXIST c_int :: 17
EXDEV c_int :: 18
ENODEV c_int :: 19
ENOTDIR c_int :: 20
EISDIR c_int :: 21
EINVAL c_int :: 22
ENFILE c_int :: 23
EMFILE c_int :: 24
ENOTTY c_int :: 25
ETXTBSY c_int :: 26
EFBIG c_int :: 27
ENOSPC c_int :: 28
ESPIPE c_int :: 29
EROFS c_int :: 30
EMLINK c_int :: 31
EPIPE c_int :: 32
EDOM c_int :: 33
ERANGE c_int :: 34
EAGAIN c_int :: 35
EINPROGRESS c_int :: 36
EALREADY c_int :: 37
ENOTSOCK c_int :: 38
EDESTADDRREQ c_int :: 39
EMSGSIZE c_int :: 40
EPROTOTYPE c_int :: 41
ENOPROTOOPT c_int :: 42
EPROTONOSUPPORT c_int :: 43
ESOCKTNOSUPPORT c_int :: 44
ENOTSUP c_int :: 45
EPFNOSUPPORT c_int :: 46
EAFNOSUPPORT c_int :: 47
EADDRINUSE c_int :: 48
EADDRNOTAVAIL c_int :: 49
ENETDOWN c_int :: 50
ENETUNREACH c_int :: 51
ENETRESET c_int :: 52
ECONNABORTED c_int :: 53
ECONNRESET c_int :: 54
ENOBUFS c_int :: 55
EISCONN c_int :: 56
ENOTCONN c_int :: 57
ESHUTDOWN c_int :: 58
ETOOMANYREFS c_int :: 59
ETIMEDOUT c_int :: 60
ECONNREFUSED c_int :: 61
ELOOP c_int :: 62
ENAMETOOLONG c_int :: 63
EHOSTDOWN c_int :: 64
EHOSTUNREACH c_int :: 65
ENOTEMPTY c_int :: 66
EPROCLIM c_int :: 67
EUSERS c_int :: 68
EDQUOT c_int :: 69
ESTALE c_int :: 70
EREMOTE c_int :: 71
EBADRPC c_int :: 72
ERPCMISMATCH c_int :: 73
EPROGUNAVAIL c_int :: 74
EPROGMISMATCH c_int :: 75
EPROCUNAVAIL c_int :: 76
ENOLCK c_int :: 77
ENOSYS c_int :: 78
EFTYPE c_int :: 79
EAUTH c_int :: 80
ENEEDAUTH c_int :: 81
EPWROFF c_int :: 82
EDEVERR c_int :: 83
EOVERFLOW c_int :: 84
EBADEXEC c_int :: 85
EBADARCH c_int :: 86
ESHLIBVERS c_int :: 87
EBADMACHO c_int :: 88
ECANCELED c_int :: 89
EIDRM c_int :: 90
ENOMSG c_int :: 91
EILSEQ c_int :: 92
ENOATTR c_int :: 93
EBADMSG c_int :: 94
EMULTIHOP c_int :: 95
ENODATA c_int :: 96
ENOLINK c_int :: 97
ENOSR c_int :: 98
ENOSTR c_int :: 99
EPROTO c_int :: 100
ETIME c_int :: 101
EOPNOTSUPP c_int :: 102
ENOPOLICY c_int :: 103
ENOTRECOVERABLE c_int :: 104
EOWNERDEAD c_int :: 105
EQFULL c_int :: 106
ENOTCAPABLE c_int :: 107
ELAST c_int :: 107
__error c_func() ?*mut c_int
# unsupported in bindings: _SYS_ERRNO_H_ — C macro has no replacement value
# unsupported in bindings: _CDEFS_H_ — C macro has no replacement value
# unsupported in bindings: _ERRNO_T — C macro has no replacement value
# unsupported in bindings: errno — C macro is not a supported constant
# unsupported in bindings: EWOULDBLOCK — C macro is not a supported constant
+393
View File
@@ -0,0 +1,393 @@
# generated by brolang translate-c from fcntl.h
# unsupported in bindings: C union '__mbstate_t' has no native spelling
__c_fcntl_bro_flock_record :: c_struct {
l_start c_longlong
l_len c_longlong
l_pid c_int
l_type c_short
l_whence c_short
}
flocktimeout :: c_struct {
fl __c_fcntl_bro_flock_record
timeout timespec
}
radvisory :: c_struct {
ra_offset c_longlong
ra_count c_int
}
fsignatures :: c_struct {
fs_file_start c_longlong
fs_blob_start ?*mut anyopaque
fs_blob_size c_ulong
fs_fsignatures_size c_ulong
fs_cdhash [20]c_char
fs_hash_type c_int
}
fsupplement :: c_struct {
fs_file_start c_longlong
fs_blob_start c_longlong
fs_blob_size c_ulong
fs_orig_fd c_int
}
fchecklv :: c_struct {
lv_file_start c_longlong
lv_error_message_size c_ulong
lv_error_message ?*mut anyopaque
}
fgetsigsinfo :: c_struct {
fg_file_start c_longlong
fg_info_request c_int
fg_sig_is_platform c_int
}
fstore :: c_struct {
fst_flags c_uint
fst_posmode c_int
fst_offset c_longlong
fst_length c_longlong
fst_bytesalloc c_longlong
}
fpunchhole :: c_struct {
fp_flags c_uint
reserved c_uint
fp_offset c_longlong
fp_length c_longlong
}
ftrimactivefile :: c_struct {
fta_offset c_longlong
fta_length c_longlong
}
fspecread :: c_struct {
fsr_flags c_uint
reserved c_uint
fsr_offset c_longlong
fsr_length c_longlong
}
fattributiontag :: c_struct {
ft_flags c_uint
ft_hash c_ulonglong
ft_attribution_name [255]c_char
}
log2phys :: c_struct {
l2p_flags c_uint
l2p_contigbytes c_longlong
l2p_devoffset c_longlong
}
_filesec :: opaque
# unsupported in bindings: typedef '__mbstate_t' — underlying type has no native spelling
# unsupported in bindings: typedef '__darwin_mbstate_t' — underlying type has no native spelling
fsignatures_t :: alias fsignatures
fsupplement_t :: alias fsupplement
fchecklv_t :: alias fchecklv
fgetsigsinfo_t :: alias fgetsigsinfo
fstore_t :: alias fstore
fpunchhole_t :: alias fpunchhole
ftrimactivefile_t :: alias ftrimactivefile
fspecread_t :: alias fspecread
fattributiontag_t :: alias fattributiontag
# unsupported in bindings: typedef '_filesec' — underlying type has no native spelling
filesec_t :: alias ?*mut _filesec
filesec_property_t :: alias c_uint
O_RDONLY c_int :: 0
O_WRONLY c_int :: 1
O_RDWR c_int :: 2
O_ACCMODE c_int :: 3
FREAD c_int :: 1
FWRITE c_int :: 2
O_NONBLOCK c_int :: 4
O_APPEND c_int :: 8
O_SYNC c_int :: 128
O_SHLOCK c_int :: 16
O_EXLOCK c_int :: 32
O_ASYNC c_int :: 64
O_NOFOLLOW c_int :: 256
O_CREAT c_int :: 512
O_TRUNC c_int :: 1024
O_EXCL c_int :: 2048
O_RESOLVE_BENEATH c_int :: 4096
O_UNIQUE c_int :: 8192
O_EVTONLY c_int :: 32768
O_NOCTTY c_int :: 131072
O_DIRECTORY c_int :: 1048576
O_SYMLINK c_int :: 2097152
O_DSYNC c_int :: 4194304
O_CLOEXEC c_int :: 16777216
O_NOFOLLOW_ANY c_int :: 536870912
O_EXEC c_int :: 1073741824
AT_FDCWD c_int :: -2
AT_EACCESS c_int :: 16
AT_SYMLINK_NOFOLLOW c_int :: 32
AT_SYMLINK_FOLLOW c_int :: 64
AT_REMOVEDIR c_int :: 128
AT_REALDEV c_int :: 512
AT_FDONLY c_int :: 1024
AT_SYMLINK_NOFOLLOW_ANY c_int :: 2048
AT_RESOLVE_BENEATH c_int :: 8192
AT_NODELETEBUSY c_int :: 16384
AT_UNIQUE c_int :: 32768
O_DP_GETRAWENCRYPTED c_int :: 1
O_DP_GETRAWUNENCRYPTED c_int :: 2
O_DP_AUTHENTICATE c_int :: 4
AUTH_OPEN_NOAUTHFD c_int :: -1
CPF_OVERWRITE c_int :: 1
CPF_IGNORE_MODE c_int :: 2
F_DUPFD c_int :: 0
F_GETFD c_int :: 1
F_SETFD c_int :: 2
F_GETFL c_int :: 3
F_SETFL c_int :: 4
F_GETOWN c_int :: 5
F_SETOWN c_int :: 6
F_GETLK c_int :: 7
F_SETLK c_int :: 8
F_SETLKW c_int :: 9
F_SETLKWTIMEOUT c_int :: 10
F_FLUSH_DATA c_int :: 40
F_CHKCLEAN c_int :: 41
F_PREALLOCATE c_int :: 42
F_SETSIZE c_int :: 43
F_RDADVISE c_int :: 44
F_RDAHEAD c_int :: 45
F_NOCACHE c_int :: 48
F_LOG2PHYS c_int :: 49
F_GETPATH c_int :: 50
F_FULLFSYNC c_int :: 51
F_PATHPKG_CHECK c_int :: 52
F_FREEZE_FS c_int :: 53
F_THAW_FS c_int :: 54
F_GLOBAL_NOCACHE c_int :: 55
F_ADDSIGS c_int :: 59
F_ADDFILESIGS c_int :: 61
F_NODIRECT c_int :: 62
F_GETPROTECTIONCLASS c_int :: 63
F_SETPROTECTIONCLASS c_int :: 64
F_LOG2PHYS_EXT c_int :: 65
F_GETLKPID c_int :: 66
F_SETBACKINGSTORE c_int :: 70
F_GETPATH_MTMINFO c_int :: 71
F_GETCODEDIR c_int :: 72
F_SETNOSIGPIPE c_int :: 73
F_GETNOSIGPIPE c_int :: 74
F_TRANSCODEKEY c_int :: 75
F_SINGLE_WRITER c_int :: 76
F_GETPROTECTIONLEVEL c_int :: 77
F_FINDSIGS c_int :: 78
F_ADDFILESIGS_FOR_DYLD_SIM c_int :: 83
F_BARRIERFSYNC c_int :: 85
F_OFD_SETLK c_int :: 90
F_OFD_SETLKW c_int :: 91
F_OFD_GETLK c_int :: 92
F_OFD_SETLKWTIMEOUT c_int :: 93
F_ADDFILESIGS_RETURN c_int :: 97
F_CHECK_LV c_int :: 98
F_PUNCHHOLE c_int :: 99
F_TRIM_ACTIVE_FILE c_int :: 100
F_SPECULATIVE_READ c_int :: 101
F_GETPATH_NOFIRMLINK c_int :: 102
F_ADDFILESIGS_INFO c_int :: 103
F_ADDFILESUPPL c_int :: 104
F_GETSIGSINFO c_int :: 105
F_SETLEASE c_int :: 106
F_GETLEASE c_int :: 107
F_TRANSFEREXTENTS c_int :: 110
F_ATTRIBUTION_TAG c_int :: 111
F_NOCACHE_EXT c_int :: 112
F_ADDSIGS_MAIN_BINARY c_int :: 113
FCNTL_FS_SPECIFIC_BASE c_int :: 65536
F_DUPFD_CLOEXEC c_int :: 67
FD_CLOEXEC c_int :: 1
F_RDLCK c_int :: 1
F_UNLCK c_int :: 2
F_WRLCK c_int :: 3
S_IFMT c_int :: 61440
S_IFIFO c_int :: 4096
S_IFCHR c_int :: 8192
S_IFDIR c_int :: 16384
S_IFBLK c_int :: 24576
S_IFREG c_int :: 32768
S_IFLNK c_int :: 40960
S_IFSOCK c_int :: 49152
S_IFWHT c_int :: 57344
S_IRWXU c_int :: 448
S_IRUSR c_int :: 256
S_IWUSR c_int :: 128
S_IXUSR c_int :: 64
S_IRWXG c_int :: 56
S_IRGRP c_int :: 32
S_IWGRP c_int :: 16
S_IXGRP c_int :: 8
S_IRWXO c_int :: 7
S_IROTH c_int :: 4
S_IWOTH c_int :: 2
S_IXOTH c_int :: 1
S_ISUID c_int :: 2048
S_ISGID c_int :: 1024
S_ISVTX c_int :: 512
F_ALLOCATECONTIG c_int :: 2
F_ALLOCATEALL c_int :: 4
F_ALLOCATEPERSIST c_int :: 8
F_PEOFPOSMODE c_int :: 3
F_VOLPOSMODE c_int :: 4
USER_FSIGNATURES_CDHASH_LEN c_int :: 20
GETSIGSINFO_PLATFORM_BINARY c_int :: 1
LOCK_SH c_int :: 1
LOCK_EX c_int :: 2
LOCK_NB c_int :: 4
LOCK_UN c_int :: 8
ATTRIBUTION_NAME_MAX c_int :: 255
F_CREATE_TAG c_int :: 1
F_DELETE_TAG c_int :: 2
F_QUERY_TAG c_int :: 4
O_POPUP c_uint :: 2147483648
O_ALERT c_int :: 536870912
FILESEC_OWNER c_uint :: 1
FILESEC_GROUP c_uint :: 2
FILESEC_UUID c_uint :: 3
FILESEC_MODE c_uint :: 4
FILESEC_ACL c_uint :: 5
FILESEC_GRPUUID c_uint :: 6
FILESEC_ACL_RAW c_uint :: 100
FILESEC_ACL_ALLOCSIZE c_uint :: 101
open c_func(_ ?*c_char, _ c_int, ...) c_int
openat c_func(_ c_int, _ ?*c_char, _ c_int, ...) c_int
creat c_func(_ ?*c_char, _ c_ushort) c_int
fcntl c_func(_ c_int, _ c_int, ...) c_int
openx_np c_func(_ ?*c_char, _ c_int, _ ?*mut _filesec) c_int
open_dprotected_np c_func(_ ?*c_char, _ c_int, _ c_int, _ c_int, ...) c_int
openat_dprotected_np c_func(_ c_int, _ ?*c_char, _ c_int, _ c_int, _ c_int, ...) c_int
openat_authenticated_np c_func(_ c_int, _ ?*c_char, _ c_int, _ c_int) c_int
flock c_func(_ c_int, _ c_int) c_int
filesec_init c_func() ?*mut _filesec
filesec_dup c_func(_ ?*mut _filesec) ?*mut _filesec
filesec_free c_func(_ ?*mut _filesec) void
filesec_get_property c_func(_ ?*mut _filesec, _ c_uint, _ ?*mut anyopaque) c_int
filesec_query_property c_func(_ ?*mut _filesec, _ c_uint, _ ?*mut c_int) c_int
filesec_set_property c_func(_ ?*mut _filesec, _ c_uint, _ ?*anyopaque) c_int
filesec_unset_property c_func(_ ?*mut _filesec, _ c_uint) c_int
# unsupported in bindings: _SYS_FCNTL_H_ — C macro has no replacement value
# unsupported in bindings: _SYS__TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _CDEFS_H_ — C macro has no replacement value
# unsupported in bindings: _BSD_MACHINE__TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _BSD_ARM__TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _SYS__PTHREAD_TYPES_H_ — C macro has no replacement value
# unsupported in bindings: MAC_OS_X_VERSION_10_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_8 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_9 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_10 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_10_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_10_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_15 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_15_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_15_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_16 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_16_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_4 — C macro is not a supported constant
# unsupported in bindings: _SIZE_T — C macro has no replacement value
# unsupported in bindings: _MODE_T — C macro has no replacement value
# unsupported in bindings: _OFF_T — C macro has no replacement value
# unsupported in bindings: _PID_T — C macro has no replacement value
# unsupported in bindings: O_FSYNC — C macro is not a supported constant
# unsupported in bindings: O_SEARCH — C macro is not a supported constant
# unsupported in bindings: FAPPEND — C macro is not a supported constant
# unsupported in bindings: FASYNC — C macro is not a supported constant
# unsupported in bindings: FFSYNC — C macro is not a supported constant
# unsupported in bindings: FFDSYNC — C macro is not a supported constant
# unsupported in bindings: FNONBLOCK — C macro is not a supported constant
# unsupported in bindings: FNDELAY — C macro is not a supported constant
# unsupported in bindings: O_NDELAY — C macro is not a supported constant
# unsupported in bindings: CPF_MASK — C macro is not a supported constant
# unsupported in bindings: F_SETLEASE_ARG — C function-like macros are not supported
# unsupported in bindings: _SEEK_SET_H_ — C macro has no replacement value
# unsupported in bindings: S_ISTXT — C macro is not a supported constant
# unsupported in bindings: S_IREAD — C macro is not a supported constant
# unsupported in bindings: S_IWRITE — C macro is not a supported constant
# unsupported in bindings: S_IEXEC — C macro is not a supported constant
# unsupported in bindings: _STRUCT_TIMESPEC — C macro is not a supported constant
# unsupported in bindings: _BSD_MACHINE_TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _ARM_MACHTYPES_H_ — C macro has no replacement value
# unsupported in bindings: _MACHTYPES_H_ — C macro has no replacement value
# unsupported in bindings: _INT8_T — C macro has no replacement value
# unsupported in bindings: _INT16_T — C macro has no replacement value
# unsupported in bindings: _INT32_T — C macro has no replacement value
# unsupported in bindings: _INT64_T — C macro has no replacement value
# unsupported in bindings: _U_INT8_T — C macro has no replacement value
# unsupported in bindings: _U_INT16_T — C macro has no replacement value
# unsupported in bindings: _U_INT32_T — C macro has no replacement value
# unsupported in bindings: _U_INT64_T — C macro has no replacement value
# unsupported in bindings: _INTPTR_T — C macro has no replacement value
# unsupported in bindings: _UINTPTR_T — C macro has no replacement value
# unsupported in bindings: USER_ADDR_NULL — C macro is not a supported constant
# unsupported in bindings: CAST_USER_ADDR_T — C function-like macros are not supported
# unsupported in bindings: _FILESEC_T — C macro has no replacement value
# unsupported in bindings: FILESEC_GUID — C macro is not a supported constant
# unsupported in bindings: _FILESEC_UNSET_PROPERTY — C macro is not a supported constant
# unsupported in bindings: _FILESEC_REMOVE_ACL — C macro is not a supported constant
+16 -13
View File
@@ -1,9 +1,10 @@
# generated by brolang translate-c from stdio.h
# unsupported in bindings: C union '__mbstate_t' has no native spelling
__mbstate_t :: opaque
__darwin_pthread_handler_rec :: c_struct {
__routine ?*c_func(_ ?*mut void) void
__arg ?*mut void
__routine ?*c_func(_ ?*mut anyopaque) void
__arg ?*mut anyopaque
__next ?*mut __darwin_pthread_handler_rec
}
_opaque_pthread_attr_t :: c_struct {
@@ -47,7 +48,7 @@ __sbuf :: c_struct {
_base ?*mut c_uchar
_size c_int
}
__sFILEX :: c_struct
__sFILEX :: opaque
__sFILE :: c_struct {
_p ?*mut c_uchar
_r c_int
@@ -56,11 +57,11 @@ __sFILE :: c_struct {
_file c_short
_bf __sbuf
_lbfsize c_int
_cookie ?*mut void
_close ?*c_func(_ ?*mut void) c_int
_read ?*c_func(_ ?*mut void, _ ?*mut c_char, _ c_int) c_int
_seek ?*c_func(_ ?*mut void, _ c_longlong, _ c_int) c_longlong
_write ?*c_func(_ ?*mut void, _ ?*c_char, _ c_int) c_int
_cookie ?*mut anyopaque
_close ?*c_func(_ ?*mut anyopaque) c_int
_read ?*c_func(_ ?*mut anyopaque, _ ?*mut c_char, _ c_int) c_int
_seek ?*c_func(_ ?*mut anyopaque, _ c_longlong, _ c_int) c_longlong
_write ?*c_func(_ ?*mut anyopaque, _ ?*c_char, _ c_int) c_int
_ub __sbuf
_extra ?*mut __sFILEX
_ur c_int
@@ -82,7 +83,8 @@ __uint64_t :: alias c_ulonglong
__darwin_intptr_t :: alias c_long
__darwin_natural_t :: alias c_uint
__darwin_ct_rune_t :: alias c_int
__darwin_mbstate_t :: alias __mbstate_t
# unsupported in bindings: typedef '__mbstate_t' — underlying type has no native spelling
# unsupported in bindings: typedef '__darwin_mbstate_t' — underlying type has no native spelling
__darwin_ptrdiff_t :: alias c_long
__darwin_size_t :: alias c_ulong
__darwin_va_list :: alias ?*mut c_char
@@ -148,6 +150,7 @@ syscall_arg_t :: alias c_ulonglong
va_list :: alias ?*mut c_char
size_t :: alias c_ulong
fpos_t :: alias c_longlong
# unsupported in bindings: typedef '__sFILEX' — underlying type has no native spelling
FILE :: alias __sFILE
off_t :: alias c_longlong
ssize_t :: alias c_long
@@ -196,13 +199,13 @@ fopen c_func(__filename ?*c_char, __mode ?*c_char) ?*mut __sFILE
fprintf c_func(_ ?*mut __sFILE, _ ?*c_char, ...) c_int
fputc c_func(_ c_int, _ ?*mut __sFILE) c_int
fputs c_func(_ ?*c_char, _ ?*mut __sFILE) c_int
fread c_func(__ptr ?*mut void, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong
fread c_func(__ptr ?*mut anyopaque, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong
freopen c_func(_ ?*c_char, _ ?*c_char, _ ?*mut __sFILE) ?*mut __sFILE
fscanf c_func(_ ?*mut __sFILE, _ ?*c_char, ...) c_int
fseek c_func(_ ?*mut __sFILE, _ c_long, _ c_int) c_int
fsetpos c_func(_ ?*mut __sFILE, _ ?*c_longlong) c_int
ftell c_func(_ ?*mut __sFILE) c_long
fwrite c_func(__ptr ?*void, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong
fwrite c_func(__ptr ?*anyopaque, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong
getc c_func(_ ?*mut __sFILE) c_int
getchar c_func() c_int
gets c_func(_ ?*mut c_char) ?*mut c_char
@@ -254,7 +257,7 @@ dprintf c_func(_ c_int, _ ?*c_char, ...) c_int
vdprintf c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) c_int
getdelim c_func(__linep ?*mut ?*mut c_char, __linecapp ?*mut c_ulong, __delimiter c_int, __stream ?*mut __sFILE) c_long
getline c_func(__linep ?*mut ?*mut c_char, __linecapp ?*mut c_ulong, __stream ?*mut __sFILE) c_long
fmemopen c_func(__buf ?*mut void, __size c_ulong, __mode ?*c_char) ?*mut __sFILE
fmemopen c_func(__buf ?*mut anyopaque, __size c_ulong, __mode ?*c_char) ?*mut __sFILE
open_memstream c_func(__bufp ?*mut ?*mut c_char, __sizep ?*mut c_ulong) ?*mut __sFILE
asprintf c_func(_ ?*mut ?*mut c_char, _ ?*c_char, ...) c_int
ctermid_r c_func(_ ?*mut c_char) ?*mut c_char
@@ -264,7 +267,7 @@ fpurge c_func(_ ?*mut __sFILE) c_int
setbuffer c_func(_ ?*mut __sFILE, _ ?*mut c_char, __size c_int) void
setlinebuf c_func(_ ?*mut __sFILE) c_int
vasprintf c_func(_ ?*mut ?*mut c_char, _ ?*c_char, _ ?*mut c_char) c_int
funopen c_func(_ ?*void, _ ?*c_func(_ ?*mut void, _ ?*mut c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut void, _ ?*c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut void, _ c_longlong, _ c_int) c_longlong, _ ?*c_func(_ ?*mut void) c_int) ?*mut __sFILE
funopen c_func(_ ?*anyopaque, _ ?*c_func(_ ?*mut anyopaque, _ ?*mut c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut anyopaque, _ ?*c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut anyopaque, _ c_longlong, _ c_int) c_longlong, _ ?*c_func(_ ?*mut anyopaque) c_int) ?*mut __sFILE
__snprintf_chk c_func(_ ?*mut c_char, __maxlen c_ulong, _ c_int, _ c_ulong, _ ?*c_char, ...) c_int
__vsnprintf_chk c_func(_ ?*mut c_char, __maxlen c_ulong, _ c_int, _ c_ulong, _ ?*c_char, _ ?*mut c_char) c_int
__sprintf_chk c_func(_ ?*mut c_char, _ c_int, _ c_ulong, _ ?*c_char, ...) c_int
+1094
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More