Compare commits
45 Commits
10abba54a5
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 378eb637c6 | |||
| c2343b54bb | |||
| fa53ca2219 | |||
| ba6052eef3 | |||
| 5244bfbf1b | |||
| 8785912b73 | |||
| a7a32894f3 | |||
| 572ffe7d07 | |||
| 6688822de2 | |||
| 729488e702 | |||
| e0f1d2d7cb | |||
| 081a3fd5df | |||
| 88b94197c9 | |||
| aadce94b42 | |||
| 84b88f6127 | |||
| 9e79d6692b | |||
| c92723bc14 | |||
| f25f76adff | |||
| b9526b5f06 | |||
| 91aa601464 | |||
| f267e8c3cb | |||
| 304880a9aa | |||
| a8593c00f9 | |||
| 96275121be | |||
| 8b50eb7606 | |||
| 21ff291788 | |||
| ec5880e757 | |||
| 07e89e23e1 | |||
| 0d04925b3a | |||
| 63f000dd42 | |||
| c389c19a81 | |||
| 8e153fa84e | |||
| 2c2a310e6d | |||
| 09571ffeb9 | |||
| 9c6215776e | |||
| 5f343ad2d3 | |||
| 17508ff751 | |||
| 402871ef7a | |||
| 1619ea98a3 | |||
| dd00af7731 | |||
| 297f2e3078 | |||
| ad4802a270 | |||
| 05aa7d084a | |||
| ec36b6b861 | |||
| deab47e75e |
@@ -1,3 +1,2 @@
|
||||
/build/
|
||||
/grammars/
|
||||
.DS_Store
|
||||
|
||||
+73
-28
@@ -8,15 +8,17 @@ roadmap and milestone history.
|
||||
### source, declarations, and packages
|
||||
|
||||
- newline-terminated statements and `#` comments
|
||||
- immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
|
||||
- immutable package globals, mutable runtime 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
|
||||
- `hide` makes any named top-level declaration file-local; declarations are public by default,
|
||||
leading underscores are ordinary identifier characters, and imports are always file-local
|
||||
- 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
|
||||
@@ -25,24 +27,48 @@ roadmap and milestone history.
|
||||
|
||||
### scalar, aggregate, and pointer types
|
||||
|
||||
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, and `anyopaque`; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
|
||||
- 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/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 keyword casts such as `i32(x)` / `c_float(x)`
|
||||
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
|
||||
- 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
|
||||
- `ptrcast!(T, ptr)` as a first-pass pointer-child retype that preserves optionality, pointer kind, mutability, and sentinel shape
|
||||
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
|
||||
- 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 and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
|
||||
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
||||
- 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, optionally grouped as `(A | B)`, using program-global `u16` variant ids
|
||||
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
||||
- 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
|
||||
|
||||
@@ -92,16 +118,17 @@ fields. `_` is not a keyword member name.
|
||||
- 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, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
|
||||
- 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 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; `expand for` specializes a comptime aggregate into one checked body per element
|
||||
- `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
|
||||
- a final `expand |value|:` enum arm or `expand |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`, 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
|
||||
@@ -128,9 +155,9 @@ and
|
||||
or
|
||||
```
|
||||
|
||||
Each level is left-associative. Because `|` also delimits `if` and `for` captures, a bitwise-OR
|
||||
header expression must be parenthesized before a capture list, for example
|
||||
`if (flags | mask) |value| { ... }`.
|
||||
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
|
||||
|
||||
@@ -180,6 +207,25 @@ 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
|
||||
@@ -188,14 +234,15 @@ Only these six division bang calls select integer-division behavior. Bare calls
|
||||
- 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 `none` means a required field, and `some!(none)` installs an optional `none` default
|
||||
- `some!(value)` explicitly constructs the present branch of an expected optional, including nested optionals where `some!(none)` differs from outer `none`
|
||||
- `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 `expand 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 expand-loop `break` / `continue` must be selected entirely at comptime
|
||||
- `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
|
||||
@@ -217,9 +264,9 @@ Only these six division bang calls select integer-division behavior. Bare calls
|
||||
- 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 `none` means no field default
|
||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
||||
- `std/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
|
||||
@@ -238,7 +285,6 @@ Only these six division bang calls select integer-division behavior. Bare calls
|
||||
|
||||
## PLANNED / DEFERRED
|
||||
|
||||
- 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
|
||||
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
|
||||
@@ -247,5 +293,4 @@ Only these six division bang calls select integer-division behavior. Bare calls
|
||||
- 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
|
||||
|
||||
@@ -48,9 +48,10 @@ 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 `root/build.bro` and compiles the program package it names. Without
|
||||
`root`, it searches the current directory and parents for the nearest
|
||||
`build.bro`. Build outputs are written to `root/build/<name>`.
|
||||
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"
|
||||
@@ -67,15 +68,16 @@ config :: b.BuildConfig{
|
||||
```
|
||||
|
||||
`name` is a plain executable name, and `source` is the program package relative
|
||||
to `build.bro`. The list fields map to the matching C options (`libraries` →
|
||||
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.bro`, writes `build/<name>-test`, and reuses
|
||||
its C link inputs, libraries, include paths, and defines.
|
||||
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"
|
||||
@@ -206,20 +208,25 @@ mem :: import "@std/mem"
|
||||
value :: math.sum(other_math.value, 1)
|
||||
```
|
||||
|
||||
Top-level declarations are public by default. Prefix a declaration with `hide`
|
||||
to keep it local to its source file; leading underscores have no visibility
|
||||
meaning. Imports are always file-local and cannot be hidden or re-exported:
|
||||
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 helper func() i32 { return 42 }
|
||||
hide State :: struct { value i32 }
|
||||
@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, typed mutable `=` locals/globals, 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`, 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
|
||||
@@ -227,6 +234,7 @@ Current prototype features:
|
||||
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
|
||||
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
|
||||
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
|
||||
- Scalar-backed nominal `distinct` types with same-identity runtime/comptime operators, explicit backing extraction casts, integer bounds, 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
|
||||
@@ -244,6 +252,7 @@ Current prototype features:
|
||||
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||
- Checked signed addition and unary negation
|
||||
- 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`
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- unsigned integers, floats, and target-dependent c scalar types
|
||||
- atomic `c_*` primitive types remain distinct until target-aware lowering
|
||||
- `c_func`, complete `c_struct`, and pointer-only `opaque`; `c` remains an ordinary identifier
|
||||
- keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`)
|
||||
- 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`
|
||||
@@ -93,11 +93,11 @@
|
||||
- if statements (implemented). example: `if condition { ... } else if { ... } else { ... }`
|
||||
- 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
|
||||
@@ -139,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`
|
||||
@@ -177,11 +180,11 @@
|
||||
- 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 {
|
||||
@@ -189,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;
|
||||
@@ -320,7 +323,7 @@
|
||||
- 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
|
||||
@@ -351,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
|
||||
@@ -370,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)
|
||||
@@ -383,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
|
||||
@@ -406,10 +409,10 @@
|
||||
`.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
|
||||
- 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)
|
||||
@@ -692,10 +695,11 @@
|
||||
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 source type syntax and an initializer; constraints (`int`/`float`/`range`)
|
||||
and inferred array counts may resolve through the existing inference fixpoint, but
|
||||
the final type must be concrete runtime storage
|
||||
- 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
|
||||
@@ -770,7 +774,7 @@
|
||||
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)
|
||||
values ArrayList(i32) := arraylist.init(mem.c_allocator)
|
||||
defer arraylist.deinit(&values)
|
||||
try arraylist.append(&values, 42)
|
||||
```
|
||||
@@ -828,9 +832,6 @@
|
||||
conflicting targets
|
||||
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
||||
|
||||
35. syntax highlighting (tree-sitter) updates (implemented)
|
||||
- pointer sigils are highlighted as operators
|
||||
- type-factory calls in type positions and struct literals are highlighted as functions
|
||||
|
||||
36. transitive package namespaces (spike completed; no language change)
|
||||
- imports remain file-local implementation details, including explicitly named imports such as
|
||||
@@ -841,13 +842,13 @@
|
||||
- this keeps package lookup shallow and deterministic and avoids overloading import aliases with
|
||||
declaration visibility
|
||||
|
||||
36.5. explicit `hide` file-local declarations (implemented)
|
||||
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 the existing file-local semantics
|
||||
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 native and C declarations with the same name may coexist in separate files, while public
|
||||
collisions are still diagnosed
|
||||
- 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
|
||||
|
||||
@@ -855,8 +856,8 @@
|
||||
- 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
|
||||
`expand for` use checker-owned persistent compile-time values for aggregate-first reflection and
|
||||
heterogeneous static expansion; expand-loop control is recursively resolved at comptime
|
||||
`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,
|
||||
@@ -889,10 +890,9 @@
|
||||
- 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 `expand` (implemented)
|
||||
- expansion-oriented `inline for` is strictly renamed to `expand for`; `inline` remains available
|
||||
for future function-inlining syntax
|
||||
- final expanded enum and tagged-union match arms generate checker-local specialized arms only
|
||||
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
|
||||
@@ -934,6 +934,37 @@
|
||||
- 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:
|
||||
@@ -942,7 +973,8 @@ For casts that bypass safety checks, Honey provides builtin functions:
|
||||
| -- | -- | -- |
|
||||
| `truncate(x, T)` | Keep low bits, discard rest | Never |
|
||||
| `bitcast(x, T)` | Reinterpret bits, no cast | Sizes don't match (compile error) |
|
||||
| `ptrcast!(p, T)` | Change pointer type | Gaining mutability (compile error) |
|
||||
| `ptrcast!(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
|
||||
@@ -955,23 +987,21 @@ m := bitcast(n, u32) # m == 0xFFFFFFFF (same bits)
|
||||
f: f32 = 3.14
|
||||
bits := bitcast(f, u32) # IEEE 754 representation
|
||||
|
||||
# pointer casts (element type, many ↔ single, pointer ↔ usize)
|
||||
buf: *u8 = get_buffer()
|
||||
ints := ptrcast!(buf, *u32) # element type change
|
||||
single := ptrcast!(buf, @u8) # many → single (restricting)
|
||||
addr := ptrcast!(buf, usize) # pointer to integer
|
||||
ptr := ptrcast!(addr, @u8) # integer to pointer
|
||||
# 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)
|
||||
}
|
||||
```
|
||||
@@ -1000,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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1031,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
|
||||
|
||||
```
|
||||
@@ -1177,7 +1221,7 @@ data :: {
|
||||
}
|
||||
|
||||
# match arms
|
||||
label []u8 = match p {
|
||||
label []u8 := match p {
|
||||
.high: "HIGH", # single expression: implicit yield
|
||||
.low: {
|
||||
log("low priority")
|
||||
@@ -1186,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
|
||||
}
|
||||
@@ -1207,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
|
||||
@@ -1229,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).
|
||||
@@ -1238,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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1437,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 ...
|
||||
@@ -1452,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
|
||||
@@ -1602,7 +1646,7 @@ Brolang provides a libc-backed allocator value:
|
||||
mem :: import "@std/mem"
|
||||
|
||||
process func(input []u8) u64 {
|
||||
temp ?*mut u8 = mem.alloc(mem.c_allocator, input.len * 2, 1)
|
||||
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 ...
|
||||
@@ -1632,7 +1676,7 @@ mem :: import "@std/mem"
|
||||
|
||||
# Allocation escapes via return value — requires allocator
|
||||
duplicate func(input []u8, allocator mem.Allocator) ?*mut u8 {
|
||||
result ?*mut u8 = mem.alloc(allocator, input.len, 1)
|
||||
result ?*mut u8 := mem.alloc(allocator, input.len, 1)
|
||||
mem.copy(result, input)
|
||||
return result # caller manages this memory
|
||||
}
|
||||
@@ -1645,7 +1689,7 @@ init func(obj @mut MyStruct, allocator mem.Allocator) void {
|
||||
|
||||
# No allocation escapes — no allocator needed
|
||||
process func(input []u8) u64 {
|
||||
temp ?*mut u8 = mem.alloc(mem.c_allocator, input.len, 1)
|
||||
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)
|
||||
|
||||
+13
-12
@@ -78,7 +78,8 @@ Expr_Kind :: enum u8 {
|
||||
String,
|
||||
Bool,
|
||||
Array,
|
||||
None,
|
||||
Null,
|
||||
Unreachable,
|
||||
Undefined,
|
||||
Inference_Hole,
|
||||
Type,
|
||||
@@ -201,13 +202,12 @@ Stmt :: struct {
|
||||
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.
|
||||
@@ -220,7 +220,7 @@ Stmt :: struct {
|
||||
// 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. An expanded arm has `expand` set, no patterns, and one or two
|
||||
// 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.
|
||||
@@ -242,12 +242,13 @@ Function :: struct {
|
||||
generated: bool,
|
||||
analysis_root: bool,
|
||||
test: bool,
|
||||
file_hidden: 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,
|
||||
@@ -264,7 +265,7 @@ Global :: struct {
|
||||
immutable: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
file_hidden: bool,
|
||||
visibility: types.Visibility,
|
||||
expr: Expr_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
@@ -312,7 +313,7 @@ Declaration_Alias :: struct {
|
||||
target_pkg: Package_Id,
|
||||
target: u32,
|
||||
kind: Declaration_Alias_Kind,
|
||||
file_hidden: bool,
|
||||
visibility: types.Visibility,
|
||||
valid: bool,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
|
||||
+18
-16
@@ -66,10 +66,10 @@ load_build_config :: proc(project_root: string) -> (BuildConfig, bool) {
|
||||
return {}, false
|
||||
}
|
||||
// check needs no `main`: it synthesizes a trap main and emits one benign
|
||||
// "missing main" diagnostic, which is expected for build.bro. Suppress that
|
||||
// one but surface any real errors in build.bro (and fail on them).
|
||||
// "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_bro_has_errors(&diagnostics) {
|
||||
if build_config_has_errors(&diagnostics) {
|
||||
source.print_all(&diagnostics)
|
||||
return {}, false
|
||||
}
|
||||
@@ -82,14 +82,14 @@ project_root_for_command :: proc(root, command: string) -> (string, bool, bool)
|
||||
}
|
||||
project_root, found := find_build_root()
|
||||
if !found {
|
||||
fmt.eprintfln("brolang %s: could not find build.bro in the current directory or any parent", command)
|
||||
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 build.bro and compiles
|
||||
// the configured program package.
|
||||
// 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 {
|
||||
@@ -161,7 +161,7 @@ valid_output_name :: proc(name: string) -> bool {
|
||||
|
||||
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
|
||||
if !valid_output_name(name) {
|
||||
fmt.eprintfln("build.bro: config 'name' must be a plain executable name, got '%s'", 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)
|
||||
@@ -202,7 +202,8 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
|
||||
current = strings.clone(start, allocator)
|
||||
}
|
||||
for {
|
||||
build_path, build_error := filepath.join({current, "build.bro"}, allocator)
|
||||
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
|
||||
@@ -212,6 +213,7 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
|
||||
if found {
|
||||
return current, true
|
||||
}
|
||||
}
|
||||
if current == "/" {
|
||||
delete(current, allocator)
|
||||
return "", false
|
||||
@@ -227,10 +229,10 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
|
||||
}
|
||||
}
|
||||
|
||||
// build_bro_has_errors reports whether checking build.bro produced any diagnostic
|
||||
// other than the benign "missing or unusable main function" (build.bro has no
|
||||
// main by design; that one is emitted with an empty span).
|
||||
build_bro_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
|
||||
// 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
|
||||
@@ -255,12 +257,12 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.eprintln("build.bro: missing top-level 'config' constant")
|
||||
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.bro: 'config' must be a BuildConfig{...} literal")
|
||||
fmt.eprintln("build config: 'config' must be a BuildConfig{...} literal")
|
||||
return {}, false
|
||||
}
|
||||
args := m.exprs[root].args
|
||||
@@ -322,7 +324,7 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
|
||||
cfg.c_options.defines = defines[:]
|
||||
|
||||
if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 {
|
||||
fmt.eprintln("build.bro: config requires non-empty 'name' and 'source'")
|
||||
fmt.eprintln("build config: config requires non-empty 'name' and 'source'")
|
||||
destroy_build_config(&cfg)
|
||||
return {}, false
|
||||
}
|
||||
@@ -336,7 +338,7 @@ unwrap_coercions :: proc(m: ^hir.Module, id: hir.Expr_Id) -> hir.Expr_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, .Optional_Some, .C_Coerce, .Scalar_Cast:
|
||||
.Widen, .Sum_Widen, .Sum_Project, .Optional_Some, .C_Coerce, .Scalar_Cast:
|
||||
cur = m.exprs[cur].left
|
||||
case:
|
||||
return cur
|
||||
|
||||
+1942
-578
File diff suppressed because it is too large
Load Diff
+1737
-212
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,15 @@ 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,
|
||||
@@ -142,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)
|
||||
@@ -149,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 {
|
||||
|
||||
+14
-7
@@ -79,9 +79,11 @@ Expr_Kind :: enum u8 {
|
||||
Float,
|
||||
String,
|
||||
Bool,
|
||||
Undefined,
|
||||
Array,
|
||||
Struct,
|
||||
None,
|
||||
Null,
|
||||
Unreachable,
|
||||
Optional_Some,
|
||||
Local,
|
||||
Global,
|
||||
@@ -100,11 +102,13 @@ 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,
|
||||
@@ -137,6 +141,8 @@ Expr_Kind :: enum u8 {
|
||||
And,
|
||||
Or,
|
||||
Range,
|
||||
Mem_Copy,
|
||||
Mem_Set,
|
||||
Call,
|
||||
}
|
||||
|
||||
@@ -223,12 +229,12 @@ Stmt :: struct {
|
||||
// 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.
|
||||
@@ -265,6 +271,7 @@ Global :: struct {
|
||||
expr: Expr_Id,
|
||||
static_value: i64,
|
||||
is_static: bool,
|
||||
eager: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
dependencies: [dynamic]Global_Id,
|
||||
|
||||
+7
-1
@@ -68,9 +68,10 @@ Linkage :: enum u8 {
|
||||
Opcode :: enum u8 {
|
||||
Param,
|
||||
Const,
|
||||
Poison,
|
||||
String,
|
||||
Aggregate,
|
||||
None,
|
||||
Null,
|
||||
Optional_Some,
|
||||
Load_Global,
|
||||
Function_Address,
|
||||
@@ -96,11 +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,
|
||||
@@ -125,6 +128,8 @@ Opcode :: enum u8 {
|
||||
Shift_Right,
|
||||
Shift_Left_Saturating,
|
||||
Compare,
|
||||
Mem_Copy,
|
||||
Mem_Set,
|
||||
Label,
|
||||
Br,
|
||||
Cond_Br,
|
||||
@@ -174,6 +179,7 @@ Global :: struct {
|
||||
link_name: string,
|
||||
type: types.Type,
|
||||
is_static: bool,
|
||||
eager: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
static_value: i64,
|
||||
|
||||
@@ -8,6 +8,10 @@ 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'
|
||||
}
|
||||
@@ -16,6 +20,7 @@ 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
|
||||
@@ -25,12 +30,11 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
||||
case "distinct": return .Keyword_Distinct
|
||||
case "alias": return .Keyword_Alias
|
||||
case "import": return .Keyword_Import
|
||||
case "hide": return .Keyword_Hide
|
||||
case "return": return .Keyword_Return
|
||||
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
|
||||
@@ -39,7 +43,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
||||
case "if": return .Keyword_If
|
||||
case "while": return .Keyword_While
|
||||
case "for": return .Keyword_For
|
||||
case "expand": return .Keyword_Expand
|
||||
case "inline": return .Keyword_Inline
|
||||
case "break": return .Keyword_Break
|
||||
case "continue": return .Keyword_Continue
|
||||
case "defer": return .Keyword_Defer
|
||||
@@ -50,7 +54,9 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
||||
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
|
||||
@@ -130,9 +136,16 @@ lex :: proc(
|
||||
case ':':
|
||||
start := cursor
|
||||
cursor += 1
|
||||
if cursor < len(bytes) && bytes[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)
|
||||
}
|
||||
@@ -310,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
|
||||
}
|
||||
|
||||
+300
-52
@@ -142,6 +142,11 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
|
||||
if types.is_void(resolved) {
|
||||
return "void"
|
||||
}
|
||||
// `noreturn` has no value representation. Keep malformed storage recoverable with
|
||||
// a byte carrier; function and call result positions map it to LLVM `void` below.
|
||||
if types.is_noreturn(resolved) {
|
||||
return "i8"
|
||||
}
|
||||
if types.is_bool(resolved) {
|
||||
return "i1"
|
||||
}
|
||||
@@ -183,6 +188,9 @@ function_result_type :: proc(function: ir.Function, store: ^types.Store) -> stri
|
||||
if function.is_main {
|
||||
return "i32"
|
||||
}
|
||||
if types.is_noreturn(function.result) {
|
||||
return "void"
|
||||
}
|
||||
if function.calling_convention == .C {
|
||||
return c_abi_result_type(function.result, store)
|
||||
}
|
||||
@@ -240,6 +248,29 @@ valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Ins
|
||||
return instruction_id != ir.INVALID_INSTRUCTION && int(instruction_id) < len(instructions)
|
||||
}
|
||||
|
||||
memory_region :: proc(value: types.Type, store: ^types.Store) -> (
|
||||
child, array_type: types.Type,
|
||||
count: u64,
|
||||
mutable, is_slice, ok: bool,
|
||||
) {
|
||||
resolved := types.resolve_alias(value, store)
|
||||
item, item_ok := types.node(store, resolved)
|
||||
if !item_ok {
|
||||
return types.INVALID, types.INVALID, 0, false, false, false
|
||||
}
|
||||
if item.kind == .Slice {
|
||||
return item.child, types.INVALID, 0, item.mutable, true, true
|
||||
}
|
||||
if item.kind == .Pointer && !item.many {
|
||||
array_type = types.resolve_alias(item.child, store)
|
||||
array, array_ok := types.node(store, array_type)
|
||||
if array_ok && array.kind == .Array {
|
||||
return array.child, array_type, array.count, item.mutable && array.mutable, false, true
|
||||
}
|
||||
}
|
||||
return types.INVALID, types.INVALID, 0, false, false, false
|
||||
}
|
||||
|
||||
valid_value :: proc(
|
||||
instructions: []ir.Instruction,
|
||||
value_id: ir.Instruction_Id,
|
||||
@@ -252,11 +283,11 @@ valid_value :: proc(
|
||||
return false
|
||||
}
|
||||
switch instructions[value_id].op {
|
||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||
case .Param, .Const, .Poison, .String, .Aggregate, .Null, .Optional_Some,
|
||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
||||
.Fallible_Error, .Extract, .Select, .Unwrap,
|
||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||
.Widen, .Sum_Widen, .Sum_Project, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Const_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked,
|
||||
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
|
||||
.Rem_Checked, .Mod_Checked,
|
||||
@@ -264,7 +295,7 @@ valid_value :: proc(
|
||||
.Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Compare, .Call:
|
||||
return true
|
||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||
.Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||
.Store, .Fill, .Mem_Copy, .Mem_Set, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
@@ -333,6 +364,8 @@ write_operand :: proc(
|
||||
value := instructions[value_id]
|
||||
if value.op == .Const {
|
||||
write_constant(builder, value.integer, expected, store)
|
||||
} else if value.op == .Poison {
|
||||
strings.write_string(builder, "poison")
|
||||
} else {
|
||||
fmt.sbprintf(builder, "%%v%d", value_id)
|
||||
}
|
||||
@@ -384,8 +417,9 @@ emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction:
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
|
||||
emit_trap_call(emitter, message)
|
||||
if types.is_runtime_value(instruction.type, &emitter.module.types) {
|
||||
if !types.is_float(instruction.type, emitter.module.target) {
|
||||
if !types.is_concrete_scalar(instruction.type) {
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !types.is_float(representation, emitter.module.target) {
|
||||
if !types.is_concrete_scalar(representation) {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = freeze %s zeroinitializer\n",
|
||||
@@ -550,8 +584,9 @@ emit_checked_arithmetic :: proc(
|
||||
float_op: string,
|
||||
overflow_message: string,
|
||||
) {
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
if types.is_float(instruction.type, emitter.module.target) {
|
||||
if types.is_float(representation, emitter.module.target) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, float_op, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
@@ -559,7 +594,7 @@ emit_checked_arithmetic :: proc(
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
return
|
||||
}
|
||||
prefix := "u" if types.is_unsigned(instruction.type, emitter.module.target) else "s"
|
||||
prefix := "u" if types.is_unsigned(representation, emitter.module.target) else "s"
|
||||
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s%s.with.overflow.%s(%s ", type_name, prefix, mnemonic, type_name, type_name)
|
||||
@@ -592,8 +627,9 @@ emit_division_zero_guard :: proc(
|
||||
instruction_index: int,
|
||||
instruction: ir.Instruction,
|
||||
) {
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
if types.is_float(instruction.type, emitter.module.target) {
|
||||
if types.is_float(representation, emitter.module.target) {
|
||||
fmt.sbprintf(&emitter.builder, " %%divzero%d = fcmp oeq %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", 0.000000e+00\n")
|
||||
@@ -622,7 +658,8 @@ emit_division_overflow_guard :: proc(
|
||||
instruction: ir.Instruction,
|
||||
) {
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
min_value := -(i128(1) << u32(types.bits(representation, emitter.module.target)-1))
|
||||
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %d\n %%divminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
|
||||
@@ -650,7 +687,8 @@ emit_float_division_builtin :: proc(
|
||||
instruction: ir.Instruction,
|
||||
) {
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
suffix := "f32" if types.bits(instruction.type, emitter.module.target) == 32 else "f64"
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
suffix := "f32" if types.bits(representation, emitter.module.target) == 32 else "f64"
|
||||
|
||||
if instruction.op == .Rem_Checked || instruction.op == .Mod_Checked {
|
||||
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked else fmt.tprintf("%%rawrem%d", instruction_index)
|
||||
@@ -707,7 +745,8 @@ emit_integer_remainder_builtin :: proc(
|
||||
instruction: ir.Instruction,
|
||||
) {
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
signed := types.is_signed(instruction.type, emitter.module.target)
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
signed := types.is_signed(representation, emitter.module.target)
|
||||
raw_name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked || !signed else fmt.tprintf("%%rawrem%d", instruction_index)
|
||||
if !signed {
|
||||
fmt.sbprintf(&emitter.builder, " %s = urem %s ", raw_name, type_name)
|
||||
@@ -716,7 +755,7 @@ emit_integer_remainder_builtin :: proc(
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
} else {
|
||||
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
|
||||
min_value := -(i128(1) << u32(types.bits(representation, emitter.module.target)-1))
|
||||
fmt.sbprintf(&emitter.builder, " %%remminlo%d = icmp eq %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %d\n %%remminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
|
||||
@@ -749,7 +788,8 @@ emit_integer_quotient_builtin :: proc(
|
||||
instruction: ir.Instruction,
|
||||
) {
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
signed := types.is_signed(instruction.type, emitter.module.target)
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
signed := types.is_signed(representation, emitter.module.target)
|
||||
operation := "sdiv" if signed else "udiv"
|
||||
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Div_Trunc_Checked else fmt.tprintf("%%divq%d", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %s = %s %s ", name, operation, type_name)
|
||||
@@ -799,13 +839,14 @@ emit_division_builtin :: proc(
|
||||
instruction: ir.Instruction,
|
||||
) {
|
||||
emit_division_zero_guard(emitter, instructions, instruction_index, instruction)
|
||||
if types.is_float(instruction.type, emitter.module.target) {
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if types.is_float(representation, emitter.module.target) {
|
||||
emit_float_division_builtin(emitter, instructions, instruction_index, instruction)
|
||||
return
|
||||
}
|
||||
quotient := instruction.op == .Div_Trunc_Checked || instruction.op == .Div_Floor_Checked ||
|
||||
instruction.op == .Div_Exact_Checked || instruction.op == .Div_Ceil_Checked
|
||||
if quotient && types.is_signed(instruction.type, emitter.module.target) {
|
||||
if quotient && types.is_signed(representation, emitter.module.target) {
|
||||
emit_division_overflow_guard(emitter, instructions, instruction_index, instruction)
|
||||
}
|
||||
if quotient {
|
||||
@@ -825,7 +866,8 @@ emit_shift :: proc(
|
||||
if valid_instruction(instructions, instruction.b) {
|
||||
count_type = instructions[instruction.b].type
|
||||
}
|
||||
if !types.is_concrete_integer(instruction.type) ||
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !types.is_concrete_integer(representation) ||
|
||||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!types.is_concrete_integer(count_type) || !types.is_unsigned(count_type, emitter.module.target) ||
|
||||
!valid_value(instructions, instruction.b, count_type, &emitter.module.types) {
|
||||
@@ -833,7 +875,7 @@ emit_shift :: proc(
|
||||
return
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
bits := types.bits(instruction.type, emitter.module.target)
|
||||
bits := types.bits(representation, emitter.module.target)
|
||||
count_bits := types.bits(count_type, emitter.module.target)
|
||||
if count_bits < 64 {
|
||||
fmt.sbprintf(&emitter.builder, " %%shift_count64_%d = zext %s ", instruction_index, llvm_type(count_type, &emitter.module.types))
|
||||
@@ -860,7 +902,7 @@ emit_shift :: proc(
|
||||
}
|
||||
operation := "shl"
|
||||
if instruction.op == .Shift_Right {
|
||||
operation = "ashr" if types.is_signed(instruction.type, emitter.module.target) else "lshr"
|
||||
operation = "ashr" if types.is_signed(representation, emitter.module.target) else "lshr"
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
@@ -878,7 +920,7 @@ emit_shift :: proc(
|
||||
if bits < 64 {
|
||||
fmt.sbprintf(&emitter.builder, " %%shift_safe%d = trunc i64 %%shift_safe64_%d to %s\n", instruction_index, instruction_index, type_name)
|
||||
}
|
||||
signed := types.is_signed(instruction.type, emitter.module.target)
|
||||
signed := types.is_signed(representation, emitter.module.target)
|
||||
intrinsic := "sshl" if signed else "ushl"
|
||||
fmt.sbprintf(&emitter.builder, " %%shift_saturated%d = call %s @llvm.%s.sat.%s(%s ", instruction_index, type_name, intrinsic, type_name, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
@@ -925,7 +967,7 @@ emit_instruction_stream :: proc(
|
||||
after_terminator = false
|
||||
}
|
||||
switch instruction.op {
|
||||
case .Param, .Const:
|
||||
case .Param, .Const, .Poison:
|
||||
case .String:
|
||||
string_id := int(instruction.integer)
|
||||
_, array, pointer_ok := types.array_pointer(instruction.type, &emitter.module.types)
|
||||
@@ -1008,6 +1050,16 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset)
|
||||
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%fallible_payload%d, ptr %%fallible_error_payload%d, i64 %d, i1 false)\n", instruction_index, instruction_index, error_payload_size)
|
||||
}
|
||||
} else if types.is_struct(error_type, &emitter.module.types) {
|
||||
if !valid_value(instructions, instruction.args[0], error_type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid fallible struct error operand")
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " store i16 1, ptr %%fallible_slot%d\n", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset)
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(error_type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.args[0], error_type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %%fallible_payload%d\n", instruction_index)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_slot%d\n", instruction_index, type_name, instruction_index)
|
||||
continue
|
||||
@@ -1065,6 +1117,15 @@ emit_instruction_stream :: proc(
|
||||
} else if item.kind == .Range && arg_index == 2 {
|
||||
element_type = types.BOOL
|
||||
}
|
||||
aggregate_index := arg_index
|
||||
if item.kind == .Struct {
|
||||
aggregate_index = types.physical_field_index(
|
||||
&emitter.module.types,
|
||||
instruction.type,
|
||||
arg_index,
|
||||
emitter.module.target,
|
||||
)
|
||||
}
|
||||
final := arg_index == total-1
|
||||
if final {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name)
|
||||
@@ -1082,12 +1143,12 @@ emit_instruction_stream :: proc(
|
||||
} else {
|
||||
write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, ", %d\n", arg_index)
|
||||
fmt.sbprintf(&emitter.builder, ", %d\n", aggregate_index)
|
||||
}
|
||||
case .None:
|
||||
case .Null:
|
||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||
if !ok || item.kind != .Optional {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional none")
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid optional null")
|
||||
continue
|
||||
}
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
@@ -1282,13 +1343,19 @@ emit_instruction_stream :: proc(
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference")
|
||||
continue
|
||||
}
|
||||
physical_index := types.physical_field_index(
|
||||
&emitter.module.types,
|
||||
base_type,
|
||||
field_index,
|
||||
emitter.module.target,
|
||||
)
|
||||
if types.is_union(base_type, &emitter.module.types) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, types.union_payload_offset(base_type, &emitter.module.types, emitter.module.target))
|
||||
} else {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n",
|
||||
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index,
|
||||
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, physical_index,
|
||||
)
|
||||
}
|
||||
case .Load:
|
||||
@@ -1347,6 +1414,12 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_error_slot%d\n", instruction_index, type_name, instruction_index)
|
||||
continue
|
||||
}
|
||||
if types.is_struct(error_type, &emitter.module.types) {
|
||||
source_offset := types.fallible_payload_offset(channel_type, &emitter.module.types, emitter.module.target)
|
||||
fmt.sbprintf(&emitter.builder, " %%fallible_error_source%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, source_offset)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_error_source%d\n", instruction_index, llvm_type(error_type, &emitter.module.types), instruction_index)
|
||||
continue
|
||||
}
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "unsupported fallible error type")
|
||||
case .Store:
|
||||
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
@@ -1368,6 +1441,109 @@ emit_instruction_stream :: proc(
|
||||
instruction.a,
|
||||
types.size(instruction.type, &emitter.module.types, emitter.module.target),
|
||||
)
|
||||
case .Mem_Copy:
|
||||
if !valid_instruction(instructions, instruction.a) || !valid_instruction(instructions, instruction.b) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid memcopy operands")
|
||||
continue
|
||||
}
|
||||
destination_type := instructions[instruction.a].type
|
||||
source_type := instructions[instruction.b].type
|
||||
destination_child, destination_array, destination_count, destination_mutable, destination_is_slice, destination_ok := memory_region(destination_type, &emitter.module.types)
|
||||
source_child, source_array, source_count, _, source_is_slice, source_ok := memory_region(source_type, &emitter.module.types)
|
||||
if !destination_ok || !destination_mutable || !source_ok ||
|
||||
!types.equal(types.resolve_alias(destination_child, &emitter.module.types), types.resolve_alias(source_child, &emitter.module.types)) ||
|
||||
!types.equal(types.resolve_alias(destination_child, &emitter.module.types), types.resolve_alias(instruction.type, &emitter.module.types)) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid memcopy region types")
|
||||
continue
|
||||
}
|
||||
if destination_is_slice {
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_dst%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_dst_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_dst%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(destination_array, &emitter.module.types), instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_dst_len%d = add i64 0, %d\n", instruction_index, destination_count)
|
||||
}
|
||||
if source_is_slice {
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_src%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(source_type, &emitter.module.types), instruction.b)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_src_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(source_type, &emitter.module.types), instruction.b)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_src%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(source_array, &emitter.module.types), instruction.b)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_src_len%d = add i64 0, %d\n", instruction_index, source_count)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_len_ok%d = icmp eq i64 %%memcopy_dst_len%d, %%memcopy_src_len%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%memcopy_len_ok%d, label %%memcopy_size_check%d, label %%memcopy_len_trap%d\nmemcopy_len_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memcopy! source and destination lengths differ")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nmemcopy_size_check%d:\n", instruction_index)
|
||||
element_size := types.size(instruction.type, &emitter.module.types, emitter.module.target)
|
||||
if element_size == 0 {
|
||||
continue
|
||||
}
|
||||
max_count := u64(0xffff_ffff_ffff_ffff)/element_size
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_size_ok%d = icmp ule i64 %%memcopy_dst_len%d, %d\n", instruction_index, instruction_index, max_count)
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%memcopy_size_ok%d, label %%memcopy_overlap_check%d, label %%memcopy_size_trap%d\nmemcopy_size_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message = diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memory operation size overflow")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nmemcopy_overlap_check%d:\n", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_bytes%d = mul i64 %%memcopy_dst_len%d, %d\n", instruction_index, instruction_index, element_size)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_dst_end%d = getelementptr i8, ptr %%memcopy_dst%d, i64 %%memcopy_bytes%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_src_end%d = getelementptr i8, ptr %%memcopy_src%d, i64 %%memcopy_bytes%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_before%d = icmp ule ptr %%memcopy_dst_end%d, %%memcopy_src%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_after%d = icmp ule ptr %%memcopy_src_end%d, %%memcopy_dst%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_disjoint%d = or i1 %%memcopy_before%d, %%memcopy_after%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_empty%d = icmp eq i64 %%memcopy_bytes%d, 0\n", instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memcopy_ok%d = or i1 %%memcopy_empty%d, %%memcopy_disjoint%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%memcopy_ok%d, label %%memcopy_continue%d, label %%memcopy_overlap_trap%d\nmemcopy_overlap_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message = diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memcopy! source and destination overlap")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nmemcopy_continue%d:\n", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%memcopy_dst%d, ptr %%memcopy_src%d, i64 %%memcopy_bytes%d, i1 false)\n", instruction_index, instruction_index, instruction_index)
|
||||
case .Mem_Set:
|
||||
if !valid_instruction(instructions, instruction.a) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid memset operands")
|
||||
continue
|
||||
}
|
||||
destination_type := instructions[instruction.a].type
|
||||
destination_child, destination_array, destination_count, destination_mutable, destination_is_slice, destination_ok := memory_region(destination_type, &emitter.module.types)
|
||||
if !destination_ok || !destination_mutable ||
|
||||
!types.equal(types.resolve_alias(destination_child, &emitter.module.types), types.resolve_alias(instruction.type, &emitter.module.types)) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid memset destination")
|
||||
continue
|
||||
}
|
||||
if destination_is_slice {
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_dst%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_dst%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(destination_array, &emitter.module.types), instruction.a)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_len%d = add i64 0, %d\n", instruction_index, destination_count)
|
||||
}
|
||||
element_size := types.size(instruction.type, &emitter.module.types, emitter.module.target)
|
||||
if element_size == 0 {
|
||||
continue
|
||||
}
|
||||
max_count := u64(0xffff_ffff_ffff_ffff)/element_size
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_size_ok%d = icmp ule i64 %%memset_len%d, %d\n", instruction_index, instruction_index, max_count)
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%memset_size_ok%d, label %%memset_start%d, label %%memset_size_trap%d\nmemset_size_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memory operation size overflow")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\nmemset_start%d:\n", instruction_index)
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if types.is_concrete_integer(representation) && types.bits(representation, emitter.module.target) == 8 {
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_bytes%d = mul i64 %%memset_len%d, %d\n", instruction_index, instruction_index, element_size)
|
||||
fmt.sbprintf(&emitter.builder, " call void @llvm.memset.p0.i64(ptr %%memset_dst%d, i8 ", instruction_index)
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", i64 %%memset_bytes%d, i1 false)\n", instruction_index)
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_index_slot%d = alloca i64\n store i64 0, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_loop%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_index%d = load i64, ptr %%memset_index_slot%d\n", instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_more%d = icmp ult i64 %%memset_index%d, %%memset_len%d\n", instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%memset_more%d, label %%memset_body%d, label %%memset_done%d\nmemset_body%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_element%d = getelementptr %s, ptr %%memset_dst%d, i64 %%memset_index%d\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index, instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", ptr %%memset_element%d\n", instruction_index)
|
||||
fmt.sbprintf(&emitter.builder, " %%memset_next%d = add i64 %%memset_index%d, 1\n store i64 %%memset_next%d, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_done%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
case .Slice:
|
||||
if !valid_instruction(instructions, instruction.a) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")
|
||||
@@ -1528,7 +1704,7 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%optional_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a)
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " br i1 %%optional_ok%d, label %%optional_continue%d, label %%optional_trap%d\noptional_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "attempted to unwrap none")
|
||||
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "attempted to unwrap null")
|
||||
emit_trap_call(emitter, message)
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\noptional_continue%d:\n", instruction_index)
|
||||
if types.is_pointer(item.child, &emitter.module.types) {
|
||||
@@ -1612,10 +1788,14 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
||||
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
||||
case .Sum_Widen:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.can_sum_widen(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid sum widening operand")
|
||||
case .Sum_Widen, .Sum_Project:
|
||||
project := instruction.op == .Sum_Project
|
||||
valid_conversion := valid_instruction(instructions, instruction.a) &&
|
||||
(types.can_sum_project(instructions[instruction.a].type, instruction.type, &emitter.module.types) if project else
|
||||
types.can_sum_widen(instructions[instruction.a].type, instruction.type, &emitter.module.types))
|
||||
if !valid_conversion {
|
||||
emit_recovery_value(emitter, instruction_index, instruction,
|
||||
"invalid sum projection operand" if project else "invalid sum widening operand")
|
||||
continue
|
||||
}
|
||||
from_type := instructions[instruction.a].type
|
||||
@@ -1663,6 +1843,9 @@ emit_instruction_stream :: proc(
|
||||
from_payload_offset := types.union_payload_offset(from_type, &emitter.module.types, emitter.module.target)
|
||||
to_payload_offset := types.union_payload_offset(instruction.type, &emitter.module.types, emitter.module.target)
|
||||
payload_size := types.sum_payload_size(from_type, &emitter.module.types, emitter.module.target)
|
||||
if project {
|
||||
payload_size = min(payload_size, types.sum_payload_size(instruction.type, &emitter.module.types, emitter.module.target))
|
||||
}
|
||||
if payload_size > 0 {
|
||||
fmt.sbprintf(&emitter.builder, " %%sum_from_payload%d = getelementptr i8, ptr %%sum_from_slot%d, i64 %d\n", instruction_index, instruction_index, from_payload_offset)
|
||||
fmt.sbprintf(&emitter.builder, " %%sum_to_payload%d = getelementptr i8, ptr %%sum_to_slot%d, i64 %d\n", instruction_index, instruction_index, to_payload_offset)
|
||||
@@ -1671,7 +1854,8 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%sum_to_slot%d\n", instruction_index, to_name, instruction_index)
|
||||
continue
|
||||
}
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "unsupported sum widening operand")
|
||||
emit_recovery_value(emitter, instruction_index, instruction,
|
||||
"unsupported sum projection operand" if project else "unsupported sum widening operand")
|
||||
case .C_Coerce:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!(types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type, emitter.module.target) ||
|
||||
@@ -1724,8 +1908,8 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
||||
case .Retype:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.can_construct_distinct(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid distinct type construction")
|
||||
!types.can_retype_distinct(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid distinct retype")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
@@ -1747,7 +1931,8 @@ emit_instruction_stream :: proc(
|
||||
from_repr := types.runtime_representation(from_type, &emitter.module.types)
|
||||
from_item, from_item_ok := types.node(&emitter.module.types, from_type)
|
||||
explicit_enum := from_item_ok && from_item.kind == .Enum && from_item.explicit_backing
|
||||
valid_from := (types.is_concrete_scalar(from_type) || explicit_enum) &&
|
||||
_, distinct_scalar := types.distinct_scalar_backing(from_type, &emitter.module.types)
|
||||
valid_from := (types.is_concrete_scalar(from_type) || explicit_enum || distinct_scalar) &&
|
||||
types.is_concrete_scalar(from_repr) && !types.is_bool(from_repr)
|
||||
if !valid_from || !types.is_concrete_scalar(instruction.type) || types.is_bool(instruction.type) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid scalar cast operand")
|
||||
@@ -1794,6 +1979,16 @@ emit_instruction_stream :: proc(
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
|
||||
case .Const_Cast:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.same_constcast_shape(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid const cast operand")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types)
|
||||
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
|
||||
case .Weaken_Slice:
|
||||
if !valid_instruction(instructions, instruction.a) ||
|
||||
!types.can_weaken_slice(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||
@@ -1819,12 +2014,15 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%decay_slice%d, i64 %d, 1\n", instruction_index, type_name, instruction_index, array.count)
|
||||
}
|
||||
case .Neg_Checked:
|
||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
(!types.is_signed(representation, emitter.module.target) &&
|
||||
!types.is_float(representation, emitter.module.target)) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||
if types.is_float(instruction.type, emitter.module.target) {
|
||||
if types.is_float(representation, emitter.module.target) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = fneg %s ", instruction_index, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
@@ -1874,19 +2072,21 @@ emit_instruction_stream :: proc(
|
||||
}
|
||||
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow")
|
||||
case .Div_Checked:
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
|
||||
!types.is_float(instruction.type, emitter.module.target) {
|
||||
!types.is_float(representation, emitter.module.target) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand")
|
||||
continue
|
||||
}
|
||||
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "div", "fdiv", "")
|
||||
case .Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
|
||||
.Rem_Checked, .Mod_Checked:
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
|
||||
(!types.is_concrete_integer(instruction.type) &&
|
||||
!types.is_float(instruction.type, emitter.module.target)) {
|
||||
(!types.is_concrete_integer(representation) &&
|
||||
!types.is_float(representation, emitter.module.target)) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid division builtin operands")
|
||||
continue
|
||||
}
|
||||
@@ -1908,7 +2108,8 @@ emit_instruction_stream :: proc(
|
||||
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, "\n")
|
||||
case .Bit_Not:
|
||||
if !types.is_concrete_integer(instruction.type) ||
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !types.is_concrete_integer(representation) ||
|
||||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid bitwise complement operand")
|
||||
continue
|
||||
@@ -1918,7 +2119,8 @@ emit_instruction_stream :: proc(
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", -1\n")
|
||||
case .Bit_And, .Bit_Or, .Bit_Xor:
|
||||
if !types.is_concrete_integer(instruction.type) ||
|
||||
representation := types.runtime_representation(instruction.type, &emitter.module.types)
|
||||
if !types.is_concrete_integer(representation) ||
|
||||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
|
||||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid bitwise operands")
|
||||
@@ -1993,7 +2195,7 @@ emit_instruction_stream :: proc(
|
||||
strings.write_string(&emitter.builder, " ")
|
||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
|
||||
} else if !types.is_void(instruction.type) {
|
||||
} else if !types.is_void(instruction.type) && !types.is_noreturn(instruction.type) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, " ")
|
||||
@@ -2009,7 +2211,10 @@ emit_instruction_stream :: proc(
|
||||
}
|
||||
strings.write_string(&emitter.builder, c_abi_result_type(function_item.child, &emitter.module.types))
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, llvm_type(function_item.child, &emitter.module.types))
|
||||
strings.write_string(
|
||||
&emitter.builder,
|
||||
"void" if types.is_noreturn(function_item.child) else llvm_type(function_item.child, &emitter.module.types),
|
||||
)
|
||||
}
|
||||
if function_item.variadic {
|
||||
strings.write_string(&emitter.builder, " (")
|
||||
@@ -2059,6 +2264,10 @@ emit_instruction_stream :: proc(
|
||||
wrote_arg = true
|
||||
}
|
||||
strings.write_string(&emitter.builder, ")\n")
|
||||
if types.is_noreturn(instruction.type) {
|
||||
strings.write_string(&emitter.builder, " unreachable\n")
|
||||
after_terminator = true
|
||||
}
|
||||
if result_abi.kind == .Indirect {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), instruction_index)
|
||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||
@@ -2120,7 +2329,7 @@ emit_instruction_stream :: proc(
|
||||
strings.write_string(&emitter.builder, " ")
|
||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
|
||||
} else if !types.is_void(instruction.type) {
|
||||
} else if !types.is_void(instruction.type) && !types.is_noreturn(instruction.type) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, " ")
|
||||
@@ -2176,6 +2385,10 @@ emit_instruction_stream :: proc(
|
||||
wrote_arg = true
|
||||
}
|
||||
strings.write_string(&emitter.builder, ")\n")
|
||||
if types.is_noreturn(instruction.type) {
|
||||
strings.write_string(&emitter.builder, " unreachable\n")
|
||||
after_terminator = true
|
||||
}
|
||||
if result_abi.kind == .Indirect {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(target.result, &emitter.module.types), instruction_index)
|
||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||
@@ -2202,10 +2415,11 @@ emit_instruction_stream :: proc(
|
||||
}
|
||||
predicate := ir.Compare_Predicate(instruction.integer)
|
||||
type_name := llvm_type(operand_type, &emitter.module.types)
|
||||
if types.is_float(operand_type, emitter.module.target) {
|
||||
representation := types.runtime_representation(operand_type, &emitter.module.types)
|
||||
if types.is_float(representation, emitter.module.target) {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = fcmp %s %s ", instruction_index, float_predicate(predicate), type_name)
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = icmp %s %s ", instruction_index, integer_predicate(predicate, types.is_signed(operand_type, emitter.module.target)), type_name)
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = icmp %s %s ", instruction_index, integer_predicate(predicate, types.is_signed(representation, emitter.module.target)), type_name)
|
||||
}
|
||||
write_operand(&emitter.builder, instructions, instruction.a, operand_type, &emitter.module.types)
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
@@ -2227,8 +2441,11 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, ", label %%bro_block_%d, label %%bro_block_%d\n", instruction.integer, u32(instruction.target))
|
||||
after_terminator = true
|
||||
case .Trap:
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||
fallback := "reached unreachable code" if instruction.integer != 0 else "invalid recovered source"
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
|
||||
emit_trap_call(emitter, message)
|
||||
strings.write_string(&emitter.builder, " unreachable\n")
|
||||
after_terminator = true
|
||||
case .Return:
|
||||
if global_initializer {
|
||||
return_value = instruction.a
|
||||
@@ -2391,12 +2608,30 @@ emit_types :: proc(emitter: ^Emitter) {
|
||||
strings.write_string(&emitter.builder, " }\n")
|
||||
continue
|
||||
}
|
||||
fields := types.fields_for(&emitter.module.types, id)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
for field, field_index in types.fields_for(&emitter.module.types, id) {
|
||||
if field_index > 0 {
|
||||
previous_index := -1
|
||||
for physical_index in 0..<len(fields) {
|
||||
logical_index := -1
|
||||
for _, candidate_index in fields {
|
||||
if previous_index >= 0 &&
|
||||
!types.field_layout_precedes(
|
||||
&emitter.module.types, id, previous_index, candidate_index, emitter.module.target,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
if logical_index < 0 ||
|
||||
types.field_layout_precedes(
|
||||
&emitter.module.types, id, candidate_index, logical_index, emitter.module.target,
|
||||
) {
|
||||
logical_index = candidate_index
|
||||
}
|
||||
}
|
||||
if physical_index > 0 {
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
}
|
||||
strings.write_string(&emitter.builder, llvm_type(field.type, &emitter.module.types))
|
||||
strings.write_string(&emitter.builder, llvm_type(fields[logical_index].type, &emitter.module.types))
|
||||
previous_index = logical_index
|
||||
}
|
||||
strings.write_string(&emitter.builder, " }\n")
|
||||
}
|
||||
@@ -2474,7 +2709,12 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
||||
)
|
||||
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
||||
for global, global_id in emitter.module.globals {
|
||||
if !global.is_static && !global.external && !global.problematic {
|
||||
if global.eager && !global.is_static && !global.external && !global.problematic {
|
||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||
}
|
||||
}
|
||||
for global, global_id in emitter.module.globals {
|
||||
if !global.eager && !global.is_static && !global.external && !global.problematic {
|
||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||
}
|
||||
}
|
||||
@@ -2553,10 +2793,18 @@ emit_functions :: proc(emitter: ^Emitter) {
|
||||
strings.write_string(&emitter.builder, "...")
|
||||
}
|
||||
if function.implementation == .Declaration {
|
||||
strings.write_string(&emitter.builder, ")\n\n")
|
||||
strings.write_string(&emitter.builder, ")")
|
||||
if types.is_noreturn(function.result) {
|
||||
strings.write_string(&emitter.builder, " noreturn")
|
||||
}
|
||||
strings.write_string(&emitter.builder, "\n\n")
|
||||
continue
|
||||
}
|
||||
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
||||
strings.write_string(&emitter.builder, ")")
|
||||
if types.is_noreturn(function.result) {
|
||||
strings.write_string(&emitter.builder, " noreturn")
|
||||
}
|
||||
strings.write_string(&emitter.builder, " {\nentry:\n")
|
||||
emit_entry_allocas(emitter, function.instructions)
|
||||
if function.calling_convention == .C {
|
||||
for param_type, index in function.param_types {
|
||||
|
||||
+37
-24
@@ -437,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
|
||||
@@ -680,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
|
||||
@@ -1249,14 +1249,29 @@ load_package :: proc(
|
||||
return pkg_id
|
||||
}
|
||||
|
||||
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 && (!function.file_hidden || function.file == file) {
|
||||
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 && (!global.file_hidden || global.file == file) {
|
||||
if global.pkg == pkg && global.name == name &&
|
||||
declaration_visible_in_file(global.visibility, global.file, file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1328,7 +1343,8 @@ find_visible_enum_global :: proc(
|
||||
) -> ast.Global_Id {
|
||||
for global, index in module.globals {
|
||||
if global.pkg == pkg && global.name == name &&
|
||||
(!global.file_hidden || !public_only && global.file == file) {
|
||||
((public_only && global.visibility == .Public) ||
|
||||
(!public_only && declaration_visible_in_file(global.visibility, global.file, file))) {
|
||||
return ast.global_id(index)
|
||||
}
|
||||
}
|
||||
@@ -1449,26 +1465,23 @@ resolve_enum_values :: proc(state: ^State) {
|
||||
}
|
||||
}
|
||||
|
||||
alias_declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool {
|
||||
return left_file == right_file if left_hidden && right_hidden else 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 &&
|
||||
alias_declarations_conflict(alias.file, alias.file_hidden, function.file, function.file_hidden) {
|
||||
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 &&
|
||||
alias_declarations_conflict(alias.file, alias.file_hidden, global.file, global.file_hidden) {
|
||||
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) &&
|
||||
alias_declarations_conflict(alias.file, alias.file_hidden, ast.File_Id(item.file), item.file_hidden) {
|
||||
declaration_scopes_overlap(item.visibility, ast.File_Id(item.file), alias.visibility, alias.file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1480,7 +1493,7 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb
|
||||
target: u32
|
||||
kinds := 0
|
||||
for function, index in module.functions {
|
||||
if function.pkg == pkg && function.name == name && !function.generated && !function.file_hidden {
|
||||
if function.pkg == pkg && function.name == name && !function.generated && function.visibility == .Public {
|
||||
kind = .Function
|
||||
target = u32(ast.function_id(index))
|
||||
kinds += 1
|
||||
@@ -1488,7 +1501,7 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb
|
||||
}
|
||||
}
|
||||
for global, index in module.globals {
|
||||
if global.pkg == pkg && global.name == name && !global.file_hidden {
|
||||
if global.pkg == pkg && global.name == name && global.visibility == .Public {
|
||||
kind = .Global
|
||||
target = u32(ast.global_id(index))
|
||||
kinds += 1
|
||||
@@ -1505,24 +1518,24 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb
|
||||
return kind, target, kinds
|
||||
}
|
||||
|
||||
hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
||||
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.file_hidden {
|
||||
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.file_hidden {
|
||||
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.file_hidden {
|
||||
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.file_hidden {
|
||||
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility != .Public {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1531,7 +1544,7 @@ hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, nam
|
||||
|
||||
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.file_hidden {
|
||||
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility == .Public {
|
||||
return index
|
||||
}
|
||||
}
|
||||
@@ -1588,11 +1601,11 @@ resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bo
|
||||
return false
|
||||
}
|
||||
|
||||
if hidden_alias_target_exists(state.module, alias.target_pkg, alias.member) {
|
||||
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 file-hidden",
|
||||
"package member '%s.%s' is not public",
|
||||
symbol.resolve(state.symbols, alias.qualifier),
|
||||
symbol.resolve(state.symbols, alias.member),
|
||||
)
|
||||
@@ -1619,7 +1632,7 @@ validate_declaration_aliases :: proc(state: ^State) {
|
||||
}
|
||||
for previous in state.module.aliases[:index] {
|
||||
if previous.pkg == alias.pkg && previous.name == alias.name &&
|
||||
alias_declarations_conflict(alias.file, alias.file_hidden, previous.file, previous.file_hidden) {
|
||||
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
|
||||
@@ -1669,9 +1682,9 @@ validate_declaration_aliases :: proc(state: ^State) {
|
||||
u32(alias.pkg),
|
||||
u32(alias.name),
|
||||
file=u32(alias.file),
|
||||
file_hidden=alias.file_hidden,
|
||||
visibility=alias.visibility,
|
||||
)
|
||||
if !types.define_alias(&state.module.type_store, id, types.Type(alias.target)) {
|
||||
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
|
||||
}
|
||||
|
||||
+300
-121
@@ -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,
|
||||
})
|
||||
@@ -468,7 +468,6 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
if expr.integer != hir.CATCH_EXPRESSION {
|
||||
capture := hir.as_local(expr.target)
|
||||
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
|
||||
error_type := state.func_locals[capture].type
|
||||
@@ -490,6 +489,7 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
||||
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{
|
||||
@@ -497,6 +497,15 @@ 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,
|
||||
})
|
||||
} 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 {
|
||||
@@ -561,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
|
||||
@@ -628,6 +656,24 @@ 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
|
||||
@@ -672,7 +718,21 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
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)
|
||||
@@ -717,7 +777,7 @@ 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, .Pointer_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, .Bit_Not:
|
||||
@@ -725,7 +785,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
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:
|
||||
.Shift_Right, .Shift_Left_Saturating, .Mem_Copy, .Mem_Set:
|
||||
stack[frame_index].stage = 2
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
case .Call:
|
||||
@@ -778,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
|
||||
@@ -786,6 +847,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
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{
|
||||
@@ -804,6 +866,7 @@ 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
|
||||
@@ -821,10 +884,16 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
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)
|
||||
@@ -855,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 {
|
||||
@@ -986,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,
|
||||
@@ -1064,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{
|
||||
@@ -1210,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,
|
||||
})
|
||||
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,
|
||||
@@ -1629,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,
|
||||
@@ -1669,13 +1761,25 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al
|
||||
|
||||
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,
|
||||
@@ -1685,11 +1789,6 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
main_function := &hir_module.functions[main_index]
|
||||
param_index, param_ok := hir.index(main_function.params[0], hir.INVALID_LOCAL, len(main_function.locals))
|
||||
if !param_ok {
|
||||
return
|
||||
}
|
||||
init_args := make([]ir.Instruction_Id, 1, allocator)
|
||||
init_args[0] = provider_call
|
||||
init_value := ir.instruction_id(len(instructions))
|
||||
@@ -1702,8 +1801,9 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
args := make([]ir.Instruction_Id, 1, allocator)
|
||||
args = make([]ir.Instruction_Id, 1, allocator)
|
||||
args[0] = init_value
|
||||
}
|
||||
main_call := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Call,
|
||||
@@ -1714,36 +1814,114 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if types.is_void(hir_module.functions[main_index].result) {
|
||||
|
||||
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=.Return_Void,
|
||||
type=types.VOID,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_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=.Return,
|
||||
type=hir_module.functions[main_index].result,
|
||||
target=ir.INVALID_REF,
|
||||
a=main_call,
|
||||
b=ir.INVALID_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=hir_module.functions[main_index].result,
|
||||
result=types.I32,
|
||||
instructions=instructions[:],
|
||||
problematic=hir_module.functions[main_index].problematic ||
|
||||
hir_module.functions[provider_index].problematic,
|
||||
problematic=problematic,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1761,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,
|
||||
|
||||
+297
-118
@@ -29,7 +29,14 @@ Parser :: struct {
|
||||
// At the top level of if/for headers, `|` begins captures. Bitwise OR in
|
||||
// those headers remains available inside parentheses.
|
||||
capture_pipe: bool,
|
||||
hidden_names: [dynamic]symbol.Id,
|
||||
// A parenthesized `if` condition ends before a leading-dot brace-less body.
|
||||
if_condition: bool,
|
||||
local_names: [dynamic]Local_Name,
|
||||
}
|
||||
|
||||
Local_Name :: struct {
|
||||
name: symbol.Id,
|
||||
visibility: types.Visibility,
|
||||
}
|
||||
|
||||
MAX_EXPRESSION_NESTING :: 256
|
||||
@@ -41,16 +48,16 @@ token_text :: proc(parser: ^Parser, tok: token.Token) -> string {
|
||||
return parser.source_file.text[int(tok.span.start):int(tok.span.end)]
|
||||
}
|
||||
|
||||
file_hidden_name :: proc(parser: ^Parser, name: symbol.Id) -> bool {
|
||||
for hidden in parser.hidden_names {
|
||||
if hidden == name {
|
||||
return true
|
||||
declaration_visibility :: proc(parser: ^Parser, name: symbol.Id) -> types.Visibility {
|
||||
for local in parser.local_names {
|
||||
if local.name == name {
|
||||
return local.visibility
|
||||
}
|
||||
}
|
||||
return false
|
||||
return .Public
|
||||
}
|
||||
|
||||
collect_hidden_names :: proc(parser: ^Parser) {
|
||||
collect_local_names :: proc(parser: ^Parser) {
|
||||
depth := 0
|
||||
for item, index in parser.tokens.items {
|
||||
#partial switch item.kind {
|
||||
@@ -58,10 +65,38 @@ collect_hidden_names :: proc(parser: ^Parser) {
|
||||
depth += 1
|
||||
case .Right_Brace:
|
||||
depth = max(depth-1, 0)
|
||||
case .Keyword_Hide:
|
||||
if depth == 0 && index+1 < len(parser.tokens.items) &&
|
||||
parser.tokens.items[index+1].kind == .Identifier {
|
||||
append(&parser.hidden_names, parser.tokens.items[index+1].symbol)
|
||||
case .At:
|
||||
if depth != 0 || index+1 >= len(parser.tokens.items) ||
|
||||
parser.tokens.items[index+1].kind != .Identifier ||
|
||||
token_text(parser, parser.tokens.items[index+1]) != "hide" {
|
||||
continue
|
||||
}
|
||||
visibility := types.Visibility.Package
|
||||
name_index := index+2
|
||||
if name_index < len(parser.tokens.items) &&
|
||||
parser.tokens.items[name_index].kind == .Colon {
|
||||
if index+3 >= len(parser.tokens.items) ||
|
||||
parser.tokens.items[index+3].kind != .Identifier {
|
||||
continue
|
||||
}
|
||||
scope := token_text(parser, parser.tokens.items[index+3])
|
||||
if scope == "file" {
|
||||
visibility = .File
|
||||
} else if scope != "package" {
|
||||
continue
|
||||
}
|
||||
name_index = index+4
|
||||
}
|
||||
for name_index < len(parser.tokens.items) &&
|
||||
parser.tokens.items[name_index].kind == .Newline {
|
||||
name_index += 1
|
||||
}
|
||||
if name_index < len(parser.tokens.items) &&
|
||||
parser.tokens.items[name_index].kind == .Identifier {
|
||||
append(&parser.local_names, Local_Name{
|
||||
name=parser.tokens.items[name_index].symbol,
|
||||
visibility=visibility,
|
||||
})
|
||||
}
|
||||
case:
|
||||
}
|
||||
@@ -156,7 +191,7 @@ is_type_token :: proc(kind: token.Kind) -> bool {
|
||||
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
|
||||
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
||||
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
|
||||
.Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func,
|
||||
.Keyword_Void, .Keyword_Noreturn, .Keyword_Anyopaque, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func,
|
||||
.Identifier, .Question, .At, .Star, .Left_Bracket:
|
||||
return true
|
||||
}
|
||||
@@ -184,6 +219,16 @@ decode_character :: proc(parser: ^Parser, tok: token.Token) -> (u64, bool) {
|
||||
return u64(value), width == len(contents)
|
||||
}
|
||||
|
||||
decode_hex_digit :: proc(value: byte) -> byte {
|
||||
if value >= '0' && value <= '9' {
|
||||
return value - '0'
|
||||
}
|
||||
if value >= 'a' && value <= 'f' {
|
||||
return value - 'a' + 10
|
||||
}
|
||||
return value - 'A' + 10
|
||||
}
|
||||
|
||||
parse_type_constant :: proc(parser: ^Parser) -> (u64, bool) {
|
||||
negative := false
|
||||
if _, ok := allow(parser, .Minus); ok {
|
||||
@@ -373,6 +418,9 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
case .Keyword_Void:
|
||||
advance(parser)
|
||||
return types.VOID
|
||||
case .Keyword_Noreturn:
|
||||
advance(parser)
|
||||
return types.NORETURN
|
||||
case .Keyword_Anyopaque:
|
||||
advance(parser)
|
||||
return types.ANYOPAQUE
|
||||
@@ -398,8 +446,8 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters")
|
||||
}
|
||||
result := parse_type(parser)
|
||||
if _, ok := allow(parser, .Bang); ok {
|
||||
error_type := parse_error_type(parser)
|
||||
error_type, _ := parse_function_error(parser, false)
|
||||
if types.is_valid(error_type) {
|
||||
if c_abi {
|
||||
source.add(parser.diagnostics, current(parser).span, "c_func pointer types cannot be fallible")
|
||||
} else {
|
||||
@@ -432,9 +480,10 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
u32(name.symbol),
|
||||
u32(qualifier),
|
||||
u32(parser.file),
|
||||
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol),
|
||||
types.Visibility.Public if symbol.is_valid(qualifier) else declaration_visibility(parser, name.symbol),
|
||||
)
|
||||
if current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
|
||||
if token_text(parser, name) == "struct_type" &&
|
||||
current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
|
||||
advance(parser)
|
||||
if symbol.is_valid(qualifier) {
|
||||
source.add(parser.diagnostics, first.span, "intrinsic calls must be unqualified")
|
||||
@@ -524,6 +573,22 @@ parse_error_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
return parse_type_pipe_tail(parser, left)
|
||||
}
|
||||
|
||||
parse_function_error :: proc(parser: ^Parser, allow_inferred: bool) -> (ast.Type_Syntax, bool) {
|
||||
bang, present := allow(parser, .Bang)
|
||||
if !present {
|
||||
return types.INVALID, false
|
||||
}
|
||||
kind := current(parser).kind
|
||||
inferred := kind == .Left_Brace || kind == .Newline || kind == .Eof
|
||||
if !inferred {
|
||||
return parse_error_type(parser), false
|
||||
}
|
||||
if !allow_inferred {
|
||||
source.add(parser.diagnostics, bang.span, "inferred error channels require a function body")
|
||||
}
|
||||
return types.INVALID, true
|
||||
}
|
||||
|
||||
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
|
||||
start := current(parser)
|
||||
depth := 0
|
||||
@@ -851,7 +916,7 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
||||
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
tok := current(parser)
|
||||
#partial switch tok.kind {
|
||||
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool:
|
||||
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Noreturn, .Keyword_Anyopaque, .Keyword_Bool:
|
||||
start := tok
|
||||
target := parse_type_atom(parser)
|
||||
return add_expr(parser, ast.Expr{
|
||||
@@ -966,10 +1031,19 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Keyword_None:
|
||||
case .Keyword_Null:
|
||||
advance(parser)
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.None,
|
||||
kind=.Null,
|
||||
span=tok.span,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Keyword_Unreachable:
|
||||
advance(parser)
|
||||
return add_expr(parser, ast.Expr{
|
||||
kind=.Unreachable,
|
||||
span=tok.span,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
@@ -1022,7 +1096,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
}
|
||||
payload := ast.INVALID_EXPR
|
||||
end := member.span
|
||||
if left_brace, ok := allow(parser, .Left_Brace); ok {
|
||||
if !(parser.no_struct_literal && parser.delimiter_depth == 0) && current(parser).kind == .Left_Brace {
|
||||
left_brace := advance(parser)
|
||||
parser.delimiter_depth += 1
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind == .Identifier && peek(parser).kind == .Equal {
|
||||
@@ -1239,7 +1314,7 @@ is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
|
||||
return true
|
||||
}
|
||||
#partial switch expr.kind {
|
||||
case .Integer, .Float, .String, .Bool, .Name:
|
||||
case .Integer, .Float, .String, .Bool, .Name, .Call, .Cast:
|
||||
return true
|
||||
case:
|
||||
return false
|
||||
@@ -1317,6 +1392,10 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
})
|
||||
continue
|
||||
}
|
||||
if parser.if_condition && parser.delimiter_depth == 0 &&
|
||||
parser.module.exprs[left].parenthesized && current(parser).kind == .Dot {
|
||||
break
|
||||
}
|
||||
if current(parser).kind == .Dot {
|
||||
advance(parser)
|
||||
if current(parser).kind == .Integer {
|
||||
@@ -1458,8 +1537,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
skip_newlines(parser)
|
||||
if operator.kind == .Keyword_Catch {
|
||||
left_expr := parser.module.exprs[left]
|
||||
capture := token.Token{}
|
||||
if _, pipe_ok := allow(parser, .Pipe); pipe_ok {
|
||||
capture := current(parser)
|
||||
capture = current(parser)
|
||||
if capture.kind != .Identifier && capture.kind != .Underscore {
|
||||
source.add(parser.diagnostics, capture.span, "expected a catch capture name")
|
||||
} else {
|
||||
@@ -1468,6 +1548,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' after catch capture")
|
||||
}
|
||||
skip_newlines(parser)
|
||||
}
|
||||
if current(parser).kind == .Left_Brace {
|
||||
body := parse_block(parser)
|
||||
end := previous(parser)
|
||||
left = add_expr(parser, ast.Expr{
|
||||
@@ -1481,11 +1564,27 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
||||
})
|
||||
continue
|
||||
}
|
||||
if cf, is_cf := parse_value_control_flow(parser); is_cf {
|
||||
body := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
||||
body[0] = cf
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Catch,
|
||||
span=span_from(left_expr.span, parser.module.statements[cf].span),
|
||||
name=capture.symbol,
|
||||
left=left,
|
||||
right=ast.INVALID_EXPR,
|
||||
body=body,
|
||||
integer=1,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
continue
|
||||
}
|
||||
right := parse_expression_bp(parser, right_power, nesting+1)
|
||||
right_expr := parser.module.exprs[right]
|
||||
left = add_expr(parser, ast.Expr{
|
||||
kind=.Catch,
|
||||
span=span_from(left_expr.span, right_expr.span),
|
||||
name=capture.symbol,
|
||||
left=left,
|
||||
right=right,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
@@ -1773,16 +1872,7 @@ parse_value_control_flow :: proc(parser: ^Parser) -> (ast.Stmt_Id, bool) {
|
||||
}
|
||||
|
||||
parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if current(parser).kind == .Identifier && token_text(parser, current(parser)) == "inline" &&
|
||||
peek(parser).kind == .Keyword_For {
|
||||
start := advance(parser)
|
||||
id := parse_for(parser)
|
||||
parser.module.statements[id].expand = true
|
||||
parser.module.statements[id].span = span_from(start.span, parser.module.statements[id].span)
|
||||
source.add(parser.diagnostics, start.span, "'inline for' was renamed to 'expand for'")
|
||||
return id
|
||||
}
|
||||
if current(parser).kind == .Keyword_Expand && peek(parser).kind == .Keyword_For {
|
||||
if current(parser).kind == .Keyword_Inline && peek(parser).kind == .Keyword_For {
|
||||
start := advance(parser)
|
||||
id := parse_for(parser)
|
||||
parser.module.statements[id].expand = true
|
||||
@@ -1837,15 +1927,35 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
had_type = true
|
||||
}
|
||||
operator := current(parser)
|
||||
if operator.kind == .Colon_Colon || operator.kind == .Equal {
|
||||
if had_type && operator.kind == .Equal {
|
||||
diagnostic := source.add(
|
||||
parser.diagnostics,
|
||||
operator.span,
|
||||
"mutable declarations use ':=' instead of '='",
|
||||
)
|
||||
for current(parser).kind != .Newline &&
|
||||
current(parser).kind != .Right_Brace &&
|
||||
current(parser).kind != .Eof {
|
||||
advance(parser)
|
||||
}
|
||||
id := ast.stmt_id(len(parser.module.statements))
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Invalid,
|
||||
span=span_from(name.span, operator.span),
|
||||
expr=ast.INVALID_EXPR,
|
||||
diagnostic=diagnostic,
|
||||
})
|
||||
return id
|
||||
}
|
||||
is_sink := name.kind == .Underscore && operator.kind == .Equal
|
||||
if operator.kind == .Colon_Colon || operator.kind == .Colon_Equal || is_sink {
|
||||
advance(parser)
|
||||
skip_newlines(parser)
|
||||
kind := ast.Stmt_Kind.Assignment
|
||||
immutable := false
|
||||
if operator.kind == .Colon_Colon || had_type {
|
||||
kind = .Declaration
|
||||
immutable = operator.kind == .Colon_Colon
|
||||
kind := ast.Stmt_Kind.Declaration
|
||||
if is_sink {
|
||||
kind = .Assignment
|
||||
}
|
||||
immutable := operator.kind == .Colon_Colon
|
||||
// A labeled value block (`x :: blk: { … yield :blk v }`): the label lets a
|
||||
// `yield :blk` exit the block past a nested `if`. Block-init body + label.
|
||||
if current(parser).kind == .Identifier && peek(parser).kind == .Colon {
|
||||
@@ -1923,6 +2033,11 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
}
|
||||
|
||||
expr := parse_expression(parser)
|
||||
assignment_name := symbol.INVALID
|
||||
target_expr := parser.module.exprs[expr]
|
||||
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
||||
assignment_name = target_expr.name
|
||||
}
|
||||
if _, ok := allow(parser, .Equal); ok {
|
||||
skip_newlines(parser)
|
||||
// A labeled value block assigned to a complex target (`a[i] = blk: { … }`).
|
||||
@@ -1933,6 +2048,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Assignment,
|
||||
span=span_from(parser.module.exprs[expr].span, previous(parser).span),
|
||||
name=assignment_name,
|
||||
target=expr,
|
||||
label=label,
|
||||
expr=ast.INVALID_EXPR,
|
||||
@@ -1949,6 +2065,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Assignment,
|
||||
span=span_from(parser.module.exprs[expr].span, brace.span),
|
||||
name=assignment_name,
|
||||
target=expr,
|
||||
expr=ast.INVALID_EXPR,
|
||||
body=body,
|
||||
@@ -1964,6 +2081,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Assignment,
|
||||
span=span_from(parser.module.exprs[expr].span, previous(parser).span),
|
||||
name=assignment_name,
|
||||
target=expr,
|
||||
value_control_flow=true,
|
||||
expr=ast.INVALID_EXPR,
|
||||
@@ -1977,6 +2095,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
kind=.Assignment,
|
||||
span=span_from(parser.module.exprs[expr].span, parser.module.exprs[value].span),
|
||||
name=assignment_name,
|
||||
target=expr,
|
||||
expr=value,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
@@ -2120,16 +2239,7 @@ parse_control_body :: proc(parser: ^Parser, header: ast.Expr_Id, diagnostic: str
|
||||
return single
|
||||
}
|
||||
|
||||
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := advance(parser) // consume 'if'
|
||||
skip_newlines(parser)
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
parser.capture_pipe = true
|
||||
condition := parse_expression(parser)
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
parser.no_struct_literal = saved
|
||||
parse_conditional_captures :: proc(parser: ^Parser) -> ([]symbol.Id, ast.Expr_Id) {
|
||||
captures: [dynamic]symbol.Id
|
||||
captures.allocator = parser.module.allocator
|
||||
guard := ast.INVALID_EXPR
|
||||
@@ -2155,9 +2265,9 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if current(parser).kind == .Pipe {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'")
|
||||
} else {
|
||||
saved = parser.no_struct_literal
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe = parser.capture_pipe
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
parser.capture_pipe = true
|
||||
guard = parse_expression(parser)
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
@@ -2168,6 +2278,23 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' to close unwrap captures")
|
||||
}
|
||||
}
|
||||
return captures[:], guard
|
||||
}
|
||||
|
||||
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := advance(parser) // consume 'if'
|
||||
skip_newlines(parser)
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
saved_if_condition := parser.if_condition
|
||||
parser.capture_pipe = true
|
||||
parser.if_condition = true
|
||||
condition := parse_expression(parser)
|
||||
parser.if_condition = saved_if_condition
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
parser.no_struct_literal = saved
|
||||
captures, guard := parse_conditional_captures(parser)
|
||||
then_body := parse_control_body(
|
||||
parser,
|
||||
condition,
|
||||
@@ -2176,7 +2303,11 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
else_body: []ast.Stmt_Id = nil
|
||||
saved_cursor := parser.cursor
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind == .Keyword_Else {
|
||||
// `else:` (and the reserved `else |...|:` shape) starts the next match arm;
|
||||
// it is not the else-branch of a brace-less if used as the previous arm body.
|
||||
match_arm_else := current(parser).kind == .Keyword_Else &&
|
||||
(peek(parser).kind == .Colon || peek(parser).kind == .Pipe)
|
||||
if current(parser).kind == .Keyword_Else && !match_arm_else {
|
||||
advance(parser)
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind == .Keyword_If {
|
||||
@@ -2219,7 +2350,7 @@ parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
||||
}
|
||||
|
||||
// parse_match_arm parses one `<pattern,...> [|[@]capture|]: <body>`, `else: <body>`,
|
||||
// or `expand |[@]value[, tag]|: <body>` arm.
|
||||
// or `inline |[@]value[, tag]|: <body>` arm.
|
||||
parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
start := current(parser).span
|
||||
patterns: [dynamic]ast.Expr_Id
|
||||
@@ -2228,10 +2359,10 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
captures.allocator = parser.module.allocator
|
||||
pointer_capture := false
|
||||
expand := false
|
||||
if _, is_expand := allow(parser, .Keyword_Expand); is_expand {
|
||||
if _, is_expand := allow(parser, .Keyword_Inline); is_expand {
|
||||
expand = true
|
||||
if _, ok := allow(parser, .Pipe); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' before expand captures")
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' before inline captures")
|
||||
} else {
|
||||
if _, at_ok := allow(parser, .At); at_ok {
|
||||
pointer_capture = true
|
||||
@@ -2241,7 +2372,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
advance(parser)
|
||||
append(&captures, name_tok.symbol)
|
||||
} else {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected an expand value capture")
|
||||
source.add(parser.diagnostics, current(parser).span, "expected an inline value capture")
|
||||
}
|
||||
if _, comma_ok := allow(parser, .Comma); comma_ok {
|
||||
tag_tok := current(parser)
|
||||
@@ -2249,10 +2380,10 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
advance(parser)
|
||||
append(&captures, tag_tok.symbol)
|
||||
} else {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected an expand tag capture")
|
||||
source.add(parser.diagnostics, current(parser).span, "expected an inline tag capture")
|
||||
}
|
||||
if _, extra := allow(parser, .Comma); extra {
|
||||
source.add(parser.diagnostics, current(parser).span, "'expand' accepts at most two captures")
|
||||
source.add(parser.diagnostics, current(parser).span, "'inline' accepts at most two captures")
|
||||
for current(parser).kind != .Pipe && current(parser).kind != .Colon &&
|
||||
current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||
advance(parser)
|
||||
@@ -2260,7 +2391,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
}
|
||||
}
|
||||
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' to close expand captures")
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '|' to close inline captures")
|
||||
}
|
||||
}
|
||||
} else if _, is_else := allow(parser, .Keyword_Else); !is_else {
|
||||
@@ -2443,15 +2574,17 @@ parse_for :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if _, ok := allow(parser, .At); ok {
|
||||
pointer_capture = true
|
||||
}
|
||||
item, item_ok := allow(parser, .Identifier)
|
||||
if item_ok {
|
||||
item := current(parser)
|
||||
if item.kind == .Identifier || item.kind == .Underscore {
|
||||
advance(parser)
|
||||
item_name = item.symbol
|
||||
} else {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected a for-loop item capture")
|
||||
}
|
||||
if _, ok := allow(parser, .Comma); ok {
|
||||
index, index_ok := allow(parser, .Identifier)
|
||||
if index_ok {
|
||||
index := current(parser)
|
||||
if index.kind == .Identifier || index.kind == .Underscore {
|
||||
advance(parser)
|
||||
index_name = index.symbol
|
||||
} else {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected an index capture after ','")
|
||||
@@ -2490,8 +2623,12 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
skip_newlines(parser)
|
||||
saved := parser.no_struct_literal
|
||||
parser.no_struct_literal = true
|
||||
saved_capture_pipe := parser.capture_pipe
|
||||
parser.capture_pipe = true
|
||||
condition := parse_expression(parser)
|
||||
parser.capture_pipe = saved_capture_pipe
|
||||
parser.no_struct_literal = saved
|
||||
captures, guard := parse_conditional_captures(parser)
|
||||
skip_newlines(parser)
|
||||
|
||||
update := ast.INVALID_STMT
|
||||
@@ -2501,7 +2638,11 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
label := parse_optional_loop_label(parser)
|
||||
body := parse_block(parser)
|
||||
body := parse_control_body(
|
||||
parser,
|
||||
condition,
|
||||
"a brace-less 'while' body requires the condition to be parenthesized unless it is a function call",
|
||||
)
|
||||
|
||||
id := ast.stmt_id(len(parser.module.statements))
|
||||
append(&parser.module.statements, ast.Stmt{
|
||||
@@ -2509,6 +2650,8 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
span=span_from(start.span, previous(parser).span),
|
||||
expr=condition,
|
||||
body=body,
|
||||
captures=captures,
|
||||
guard=guard,
|
||||
label=label,
|
||||
update=update,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
@@ -2516,7 +2659,7 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
return id
|
||||
}
|
||||
|
||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: bool) {
|
||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool, visibility: types.Visibility) {
|
||||
advance(parser)
|
||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
||||
@@ -2527,10 +2670,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: b
|
||||
}
|
||||
skip_newlines(parser)
|
||||
result := parse_type(parser)
|
||||
error_type := types.INVALID
|
||||
if _, ok := allow(parser, .Bang); ok {
|
||||
error_type = parse_error_type(parser)
|
||||
}
|
||||
error_type, infer_error := parse_function_error(parser, true)
|
||||
end := previous(parser)
|
||||
ended_by_newline := current(parser).kind == .Newline
|
||||
if current(parser).kind == .Newline {
|
||||
@@ -2547,12 +2687,13 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: b
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=c_abi,
|
||||
file_hidden=file_hidden,
|
||||
visibility=visibility,
|
||||
has_body=false,
|
||||
variadic=variadic,
|
||||
params=params,
|
||||
result=result,
|
||||
error=error_type,
|
||||
infer_error=infer_error,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return
|
||||
@@ -2566,12 +2707,13 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: b
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=c_abi,
|
||||
file_hidden=file_hidden,
|
||||
visibility=visibility,
|
||||
has_body=true,
|
||||
variadic=variadic,
|
||||
params=params,
|
||||
result=result,
|
||||
error=error_type,
|
||||
infer_error=infer_error,
|
||||
body=body,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -2588,10 +2730,7 @@ parse_function_literal :: proc(parser: ^Parser) -> ast.Expr_Id {
|
||||
}
|
||||
skip_newlines(parser)
|
||||
result := parse_type(parser)
|
||||
error_type := types.INVALID
|
||||
if _, ok := allow(parser, .Bang); ok {
|
||||
error_type = parse_error_type(parser)
|
||||
}
|
||||
error_type, infer_error := parse_function_error(parser, true)
|
||||
if current(parser).kind == .Newline {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
@@ -2614,6 +2753,7 @@ parse_function_literal :: proc(parser: ^Parser) -> ast.Expr_Id {
|
||||
params=params,
|
||||
result=result,
|
||||
error=error_type,
|
||||
infer_error=infer_error,
|
||||
body=body,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -2752,9 +2892,9 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
|
||||
return types.union_anonymous(&parser.module.type_store, fields[:], tag)
|
||||
}
|
||||
|
||||
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden: bool, is_union := false) {
|
||||
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, visibility: types.Visibility, is_union := false) {
|
||||
start := advance(parser)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), visibility=visibility)
|
||||
// A tagged union spells its discriminant in parens: `union(Enum)` reuses an existing
|
||||
// enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
|
||||
tag := types.INVALID
|
||||
@@ -2788,7 +2928,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
|
||||
"native union declarations require a body" if is_union else "native struct declarations require a body",
|
||||
)
|
||||
}
|
||||
if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union, tag=tag, declared_tag=declared_tag) {
|
||||
if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union, tag=tag, declared_tag=declared_tag, visibility=visibility) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if !ended_by_newline {
|
||||
@@ -2825,7 +2965,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
|
||||
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
|
||||
tag = synthesize_union_tag(parser, fields[:])
|
||||
}
|
||||
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple) {
|
||||
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple, visibility=visibility) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
} else if !c_layout && !is_union && !tuple {
|
||||
for value, index in defaults {
|
||||
@@ -2844,10 +2984,10 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
parse_opaque :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
parse_opaque :: proc(parser: ^Parser, name: token.Token, visibility: types.Visibility) {
|
||||
start := advance(parser)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||
if !types.define_record(&parser.module.type_store, id, nil, false, true, false) {
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), visibility=visibility)
|
||||
if !types.define_record(&parser.module.type_store, id, nil, false, true, false, visibility=visibility) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if current(parser).kind == .Left_Brace {
|
||||
@@ -2872,11 +3012,11 @@ synthesize_union_tag :: proc(parser: ^Parser, fields: []types.Field) -> types.Ty
|
||||
return types.enum_anonymous(&parser.module.type_store, members, types.U16)
|
||||
}
|
||||
|
||||
parse_distinct :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
parse_distinct :: proc(parser: ^Parser, name: token.Token, visibility: types.Visibility) {
|
||||
start := advance(parser)
|
||||
child := parse_type(parser)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||
if !types.define_distinct(&parser.module.type_store, id, child) {
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), visibility=visibility)
|
||||
if !types.define_distinct(&parser.module.type_store, id, child, visibility) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if !types.is_valid(child) {
|
||||
@@ -2885,7 +3025,7 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
parse_alias :: proc(parser: ^Parser, name: token.Token, visibility: types.Visibility) {
|
||||
start := advance(parser)
|
||||
saved := parser.cursor
|
||||
if current(parser).kind == .Identifier && peek(parser).kind == .Dot {
|
||||
@@ -2902,7 +3042,7 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
target_pkg=ast.INVALID_PACKAGE,
|
||||
file_hidden=file_hidden,
|
||||
visibility=visibility,
|
||||
valid=true,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -2913,8 +3053,8 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
}
|
||||
parser.cursor = saved
|
||||
child := parse_type(parser)
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||
if !types.define_alias(&parser.module.type_store, id, child) {
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), visibility=visibility)
|
||||
if !types.define_alias(&parser.module.type_store, id, child, visibility) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if !types.is_valid(child) {
|
||||
@@ -3057,7 +3197,7 @@ parse_inline_enum_type :: proc(parser: ^Parser) -> types.Type {
|
||||
return types.enum_anonymous(&parser.module.type_store, members[:], backing)
|
||||
}
|
||||
|
||||
parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
parse_enum :: proc(parser: ^Parser, name: token.Token, visibility: types.Visibility) {
|
||||
start := advance(parser)
|
||||
explicit_backing := false
|
||||
backing := types.INVALID
|
||||
@@ -3079,8 +3219,8 @@ parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) {
|
||||
_ = finish_statement(parser)
|
||||
return
|
||||
}
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden)
|
||||
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), visibility=visibility)
|
||||
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing, visibility) {
|
||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||
}
|
||||
if explicit_backing && deferred {
|
||||
@@ -3113,6 +3253,9 @@ decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
|
||||
case 'r': value = '\r'
|
||||
case 't': value = '\t'
|
||||
case '0': value = 0
|
||||
case 'x':
|
||||
value = decode_hex_digit(text[index+1])*16 + decode_hex_digit(text[index+2])
|
||||
index += 2
|
||||
case:
|
||||
}
|
||||
}
|
||||
@@ -3209,10 +3352,38 @@ parse_test :: proc(parser: ^Parser, name: token.Token) {
|
||||
}
|
||||
|
||||
parse_top_level :: proc(parser: ^Parser) {
|
||||
hide_token, file_hidden := allow(parser, .Keyword_Hide)
|
||||
modifier := current(parser)
|
||||
hide_name := peek(parser)
|
||||
visibility := types.Visibility.Public
|
||||
has_modifier := modifier.kind == .At && hide_name.kind == .Identifier &&
|
||||
token_text(parser, hide_name) == "hide"
|
||||
if has_modifier {
|
||||
visibility = .Package
|
||||
advance(parser)
|
||||
advance(parser)
|
||||
if _, scoped := allow(parser, .Colon); scoped {
|
||||
if current(parser).kind == .Identifier {
|
||||
scope := advance(parser)
|
||||
scope_text := token_text(parser, scope)
|
||||
if scope_text == "file" {
|
||||
visibility = .File
|
||||
} else if scope_text != "package" {
|
||||
source.addf(
|
||||
parser.diagnostics,
|
||||
scope.span,
|
||||
"unknown hide scope '%s'; expected 'package' or 'file'",
|
||||
scope_text,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected 'package' or 'file' after '@hide:'")
|
||||
}
|
||||
}
|
||||
skip_newlines(parser)
|
||||
}
|
||||
if current(parser).kind == .Keyword_Test && peek(parser).kind == .Keyword_Import {
|
||||
if file_hidden {
|
||||
source.add(parser.diagnostics, hide_token.span, "test imports cannot use 'hide'")
|
||||
if has_modifier {
|
||||
source.add(parser.diagnostics, modifier.span, "test imports cannot use a visibility modifier")
|
||||
}
|
||||
start := advance(parser)
|
||||
advance(parser) // consume 'import'
|
||||
@@ -3220,15 +3391,15 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
return
|
||||
}
|
||||
if current(parser).kind == .Keyword_Import {
|
||||
if file_hidden {
|
||||
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'")
|
||||
if has_modifier {
|
||||
source.add(parser.diagnostics, modifier.span, "imports are already file-local and cannot use a visibility modifier")
|
||||
}
|
||||
start := advance(parser)
|
||||
parse_import(parser, token.Token{}, start)
|
||||
return
|
||||
}
|
||||
if current(parser).kind != .Identifier {
|
||||
message := "expected a declaration name after 'hide'" if file_hidden else "expected a top-level declaration"
|
||||
message := "expected a declaration name after visibility modifier" if has_modifier else "expected a top-level declaration"
|
||||
source.add(parser.diagnostics, current(parser).span, message)
|
||||
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||
advance(parser)
|
||||
@@ -3238,8 +3409,8 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
}
|
||||
name := advance(parser)
|
||||
if current(parser).kind == .Keyword_Test {
|
||||
if file_hidden {
|
||||
source.add(parser.diagnostics, hide_token.span, "test declarations cannot use 'hide'")
|
||||
if has_modifier {
|
||||
source.add(parser.diagnostics, modifier.span, "test declarations cannot use a visibility modifier")
|
||||
}
|
||||
parse_test(parser, name)
|
||||
return
|
||||
@@ -3249,8 +3420,8 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
advance(parser)
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind == .Keyword_Import {
|
||||
if file_hidden {
|
||||
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'")
|
||||
if has_modifier {
|
||||
source.add(parser.diagnostics, modifier.span, "imports are already file-local and cannot use a visibility modifier")
|
||||
}
|
||||
start := advance(parser)
|
||||
parse_import(parser, name, start)
|
||||
@@ -3260,7 +3431,7 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
}
|
||||
if current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func {
|
||||
c_abi := current(parser).kind == .Keyword_C_Func
|
||||
parse_function(parser, name, c_abi, file_hidden)
|
||||
parse_function(parser, name, c_abi, visibility)
|
||||
return
|
||||
}
|
||||
type_syntax := types.INVALID
|
||||
@@ -3268,8 +3439,16 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
type_syntax = parse_type(parser)
|
||||
}
|
||||
operator := current(parser)
|
||||
if operator.kind != .Colon_Colon && operator.kind != .Equal {
|
||||
source.add(parser.diagnostics, operator.span, "expected '::' or '=' after top-level name")
|
||||
if operator.kind == .Equal {
|
||||
source.add(parser.diagnostics, operator.span, "mutable declarations use ':=' instead of '='")
|
||||
for current(parser).kind != .Newline && current(parser).kind != .Eof {
|
||||
advance(parser)
|
||||
}
|
||||
_ = finish_statement(parser)
|
||||
return
|
||||
}
|
||||
if operator.kind != .Colon_Colon && operator.kind != .Colon_Equal {
|
||||
source.add(parser.diagnostics, operator.span, "expected '::' or ':=' after top-level name")
|
||||
_ = finish_statement(parser)
|
||||
return
|
||||
}
|
||||
@@ -3281,32 +3460,32 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
source.add(parser.diagnostics, span_from(name.span, current(parser).span),
|
||||
"function declarations do not use '::'; write 'name func(...)' or 'name c_func(...)'")
|
||||
c_abi := current(parser).kind == .Keyword_C_Func
|
||||
parse_function(parser, name, c_abi, file_hidden)
|
||||
parse_function(parser, name, c_abi, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon &&
|
||||
(current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) {
|
||||
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct, file_hidden)
|
||||
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque {
|
||||
parse_opaque(parser, name, file_hidden)
|
||||
parse_opaque(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union {
|
||||
parse_struct(parser, name, false, file_hidden, is_union=true)
|
||||
parse_struct(parser, name, false, visibility, is_union=true)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
|
||||
parse_enum(parser, name, file_hidden)
|
||||
parse_enum(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
|
||||
parse_distinct(parser, name, file_hidden)
|
||||
parse_distinct(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias {
|
||||
parse_alias(parser, name, file_hidden)
|
||||
parse_alias(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3317,7 +3496,7 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
name=name.symbol,
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
file_hidden=file_hidden,
|
||||
visibility=visibility,
|
||||
type=type_syntax,
|
||||
immutable=operator.kind == .Colon_Colon,
|
||||
expr=expr,
|
||||
@@ -3338,9 +3517,9 @@ parse :: proc(
|
||||
diagnostics=diagnostics,
|
||||
module=ast.init_module(allocator),
|
||||
}
|
||||
parser.hidden_names.allocator = allocator
|
||||
defer delete(parser.hidden_names)
|
||||
collect_hidden_names(&parser)
|
||||
parser.local_names.allocator = allocator
|
||||
defer delete(parser.local_names)
|
||||
collect_local_names(&parser)
|
||||
skip_newlines(&parser)
|
||||
for current(&parser).kind != .Eof {
|
||||
parse_top_level(&parser)
|
||||
@@ -3365,9 +3544,9 @@ parse_into :: proc(
|
||||
pkg=pkg,
|
||||
file=file,
|
||||
}
|
||||
parser.hidden_names.allocator = module.allocator
|
||||
defer delete(parser.hidden_names)
|
||||
collect_hidden_names(&parser)
|
||||
parser.local_names.allocator = module.allocator
|
||||
defer delete(parser.local_names)
|
||||
collect_local_names(&parser)
|
||||
skip_newlines(&parser)
|
||||
for current(&parser).kind != .Eof {
|
||||
parse_top_level(&parser)
|
||||
|
||||
@@ -143,6 +143,7 @@ inject_assertion_locations :: proc(
|
||||
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
|
||||
@@ -174,12 +175,12 @@ append_runner :: proc(
|
||||
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)
|
||||
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")
|
||||
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]
|
||||
|
||||
@@ -16,6 +16,7 @@ Kind :: enum u8 {
|
||||
Underscore,
|
||||
Colon,
|
||||
Colon_Colon,
|
||||
Colon_Equal,
|
||||
Equal,
|
||||
Equal_Equal,
|
||||
Bang,
|
||||
@@ -71,12 +72,11 @@ Kind :: enum u8 {
|
||||
Keyword_Distinct,
|
||||
Keyword_Alias,
|
||||
Keyword_Import,
|
||||
Keyword_Hide,
|
||||
Keyword_Return,
|
||||
Keyword_Try,
|
||||
Keyword_Catch,
|
||||
Keyword_Mut,
|
||||
Keyword_None,
|
||||
Keyword_Null,
|
||||
Keyword_Undefined,
|
||||
Keyword_Orelse,
|
||||
Keyword_And,
|
||||
@@ -85,7 +85,7 @@ Kind :: enum u8 {
|
||||
Keyword_If,
|
||||
Keyword_While,
|
||||
Keyword_For,
|
||||
Keyword_Expand,
|
||||
Keyword_Inline,
|
||||
Keyword_Break,
|
||||
Keyword_Continue,
|
||||
Keyword_Defer,
|
||||
@@ -96,7 +96,9 @@ Kind :: enum u8 {
|
||||
Keyword_True,
|
||||
Keyword_False,
|
||||
Keyword_Void,
|
||||
Keyword_Noreturn,
|
||||
Keyword_Anyopaque,
|
||||
Keyword_Unreachable,
|
||||
Keyword_Bool,
|
||||
Keyword_Int,
|
||||
Keyword_Uint,
|
||||
|
||||
+249
-27
@@ -45,6 +45,7 @@ FLOAT :: Type(30)
|
||||
RANGE :: Type(31)
|
||||
ANYOPAQUE :: Type(32)
|
||||
UINT :: Type(33)
|
||||
NORETURN :: Type(34)
|
||||
|
||||
DYNAMIC_START :: Type(64)
|
||||
|
||||
@@ -55,9 +56,16 @@ Numeric_Category :: enum u8 {
|
||||
Float,
|
||||
}
|
||||
|
||||
Visibility :: enum u8 {
|
||||
Public,
|
||||
Package,
|
||||
File,
|
||||
}
|
||||
|
||||
Kind :: enum u8 {
|
||||
Invalid,
|
||||
Void,
|
||||
Noreturn,
|
||||
Anyopaque,
|
||||
Int_Constraint,
|
||||
Uint_Constraint,
|
||||
@@ -107,7 +115,7 @@ Node :: struct {
|
||||
tuple: bool,
|
||||
opaque: bool,
|
||||
declared: bool,
|
||||
file_hidden: bool,
|
||||
visibility: Visibility,
|
||||
explicit_backing: bool,
|
||||
}
|
||||
|
||||
@@ -191,43 +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, file_hidden := false) -> Type {
|
||||
normalized_file := file if qualifier != 0 || file_hidden 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 {
|
||||
visibility_matches := existing.file_hidden == file_hidden &&
|
||||
(!file_hidden || existing.file == normalized_file)
|
||||
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 &&
|
||||
(visibility_matches || qualifier == 0 && existing.file_hidden != file_hidden) {
|
||||
((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, file_hidden=file_hidden})
|
||||
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, 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 {
|
||||
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 {
|
||||
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_hidden && existing.file == file {
|
||||
fallback = DYNAMIC_START+Type(index)
|
||||
}
|
||||
case .File:
|
||||
if existing.file == file {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
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_hidden {
|
||||
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
|
||||
@@ -235,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
|
||||
@@ -247,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
|
||||
@@ -262,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
|
||||
@@ -278,6 +311,7 @@ define_record :: proc(
|
||||
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) ||
|
||||
@@ -289,6 +323,7 @@ define_record :: proc(
|
||||
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
|
||||
@@ -550,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)
|
||||
}
|
||||
@@ -567,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 {
|
||||
@@ -599,6 +738,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
||||
return .Invalid
|
||||
case VOID:
|
||||
return .Void
|
||||
case NORETURN:
|
||||
return .Noreturn
|
||||
case ANYOPAQUE:
|
||||
return .Anyopaque
|
||||
case INT:
|
||||
@@ -643,6 +784,10 @@ 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
|
||||
}
|
||||
@@ -1031,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 {
|
||||
@@ -1332,6 +1501,45 @@ replace_pointer_child :: proc(store: ^Store, value, child: Type) -> (Type, bool)
|
||||
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)
|
||||
@@ -1539,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:
|
||||
@@ -1571,12 +1779,19 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||
}
|
||||
offset: u64
|
||||
max_align: u64 = 1
|
||||
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:
|
||||
item, _ := node(store, value)
|
||||
@@ -1717,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
|
||||
}
|
||||
@@ -1759,6 +1980,7 @@ 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"
|
||||
|
||||
+3084
-450
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@ Ball :: struct {
|
||||
}
|
||||
|
||||
# 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 none).
|
||||
# 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
|
||||
@@ -79,8 +79,8 @@ 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
|
||||
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
|
||||
@@ -130,14 +130,14 @@ step func(b @mut Ball) void {
|
||||
|
||||
draw_ball func(b @Ball, highlight bool) void {
|
||||
col :: color_for(b.kind)
|
||||
center rl.Vector2 = rl.Vector2{ x = b.x, y = b.y }
|
||||
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 }
|
||||
ring rl.Color := rl.Color{ r = 250, g = 245, b = 200, a = 255 }
|
||||
rl.DrawPoly(center, 24, b.radius + RING_PAD, 0.0, ring)
|
||||
}
|
||||
}
|
||||
@@ -152,11 +152,11 @@ main func() i32 {
|
||||
`[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
|
||||
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 }
|
||||
@@ -211,9 +211,9 @@ main func() i32 {
|
||||
# --- 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 }
|
||||
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 none
|
||||
yield null
|
||||
}
|
||||
|
||||
# --- draw -----------------------------------------------------------
|
||||
@@ -222,7 +222,7 @@ main func() i32 {
|
||||
|
||||
for (&balls) |@b, i| {
|
||||
if (i >= count) break
|
||||
hot bool = false
|
||||
hot bool := false
|
||||
if sel |s| {
|
||||
if (s == i) hot = true # true only for the hovered ball
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
hide sibling func() i32 {
|
||||
@hide sibling func() i32 {
|
||||
return 1
|
||||
}
|
||||
|
||||
hide Sibling :: struct {
|
||||
@hide Sibling :: struct {
|
||||
value i32
|
||||
}
|
||||
|
||||
hide sibling_value :: 1
|
||||
@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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "../dep"
|
||||
|
||||
hide collision func() i32 {
|
||||
@hide collision func() i32 {
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ read_sibling_value func() i32 {
|
||||
return sibling_value
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
return sibling() + dep.secret() + read_sibling(Sibling { value = 1 }) + read_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 })
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
hide secret c_func() i32 {
|
||||
@hide:package secret c_func() i32 {
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
hide helper func() i32 {
|
||||
thing Thing = Thing { value = value }
|
||||
@hide:package
|
||||
helper func() i32 {
|
||||
thing Thing := Thing { value = value }
|
||||
return thing.value
|
||||
}
|
||||
|
||||
hide Thing :: struct {
|
||||
@hide
|
||||
Thing :: struct {
|
||||
value i32
|
||||
}
|
||||
|
||||
hide Local_Union :: union {
|
||||
@hide Local_Union :: union {
|
||||
value i32
|
||||
}
|
||||
|
||||
hide Local_Enum :: enum {
|
||||
@hide Local_Enum :: enum {
|
||||
value
|
||||
}
|
||||
|
||||
hide Local_Opaque :: opaque
|
||||
hide Local_Distinct :: distinct i32
|
||||
hide Local_Alias :: alias i32
|
||||
@hide Local_Opaque :: opaque
|
||||
@hide Local_Distinct :: distinct i32
|
||||
@hide Local_Alias :: alias i32
|
||||
|
||||
hide value :: 1
|
||||
hide mutable_value i32 = 1
|
||||
@hide value :: 1
|
||||
@hide mutable_value i32 := 1
|
||||
|
||||
_foreign c_func() i32 {
|
||||
return 1
|
||||
@@ -30,15 +32,27 @@ _C_Record :: c_struct {
|
||||
value c_int
|
||||
}
|
||||
|
||||
hide local_foreign c_func() i32 {
|
||||
@hide local_foreign c_func() i32 {
|
||||
return 1
|
||||
}
|
||||
|
||||
hide Local_C_Record :: c_struct {
|
||||
@hide Local_C_Record :: c_struct {
|
||||
value c_int
|
||||
}
|
||||
|
||||
from_a func() i32 {
|
||||
record Local_C_Record = Local_C_Record { value = 0 }
|
||||
return helper() + local_foreign() + i32(record.value)
|
||||
@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)
|
||||
}
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
import "../dep"
|
||||
|
||||
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 :: 2
|
||||
hide mutable_value i32 = 2
|
||||
|
||||
hide helper func() i32 {
|
||||
thing Thing = Thing { value = value }
|
||||
return thing.value
|
||||
}
|
||||
|
||||
hide local_foreign c_func() i32 {
|
||||
return 1
|
||||
}
|
||||
|
||||
hide Local_C_Record :: c_struct {
|
||||
value c_int
|
||||
}
|
||||
|
||||
Box :: struct {
|
||||
_value i32
|
||||
}
|
||||
|
||||
from_b func(_input i32) i32 {
|
||||
_local Box = Box { _value = _input }
|
||||
record _C_Record = _C_Record { value = 0 }
|
||||
return helper() + _local._value + _foreign() + i32(record.value) + dep._visible()
|
||||
@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
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
bad = 1
|
||||
bad := undefined
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -2,33 +2,53 @@ arraylist :: import "@std/arraylist"
|
||||
mem :: import "@std/mem"
|
||||
std :: import "@std"
|
||||
|
||||
hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||
return none
|
||||
Token :: struct { value i32 }
|
||||
ScanDiagnostic :: struct { value i32 }
|
||||
|
||||
State :: struct {
|
||||
tokens std.ArrayList(Token)
|
||||
diagnostics std.ArrayList(ScanDiagnostic)
|
||||
}
|
||||
|
||||
hide fail_realloc func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||
return none
|
||||
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_free func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
|
||||
@hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||
return null
|
||||
}
|
||||
|
||||
hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
@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 = none,
|
||||
@hide fail_allocator mem.Allocator :: mem.Allocator {
|
||||
context = null,
|
||||
vtable = &fail_vtable,
|
||||
}
|
||||
|
||||
run func() i32 ! mem.AllocError {
|
||||
values std.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||
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
|
||||
i usize := 0
|
||||
while i < 20 : i += 1 {
|
||||
arraylist.append(&values, i32(i)) catch |_| {
|
||||
return .out_of_memory
|
||||
@@ -51,7 +71,7 @@ run func() i32 ! mem.AllocError {
|
||||
}
|
||||
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)
|
||||
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 |_| {
|
||||
@@ -59,8 +79,8 @@ run func() i32 ! mem.AllocError {
|
||||
}
|
||||
if (empty_values.items.len != 1) return 8
|
||||
|
||||
failed arraylist.ArrayList(i32) = arraylist.init(i32, fail_allocator)
|
||||
failed_as_expected bool = false
|
||||
failed arraylist.ArrayList(i32) := arraylist.init(i32, fail_allocator)
|
||||
failed_as_expected bool := false
|
||||
arraylist.append(&failed, 1) catch |_| {
|
||||
failed_as_expected = true
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ c_count_shift func(value u8, count c_uint) u8 {
|
||||
c_count_fold u8 :: c_count_shift(3, 2)
|
||||
|
||||
main func() i32 {
|
||||
buffer Buffer = undefined
|
||||
buffer Buffer := undefined
|
||||
if (folded != 0) return 1
|
||||
if (contextual != 255) return 28
|
||||
if (contextual_shift != 128) return 29
|
||||
@@ -50,7 +50,7 @@ main func() i32 {
|
||||
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 u8 := 3
|
||||
value <<= 2
|
||||
value |= 1
|
||||
value xor= 5
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
# 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
|
||||
@@ -13,7 +13,7 @@ check_float func() i32 {
|
||||
}
|
||||
|
||||
check_unsigned func() i32 {
|
||||
n u32 = 100
|
||||
n u32 := 100
|
||||
n = divtrunc!(n, 7) # 14
|
||||
n -= 4 # 10
|
||||
if n == 10 {
|
||||
@@ -23,7 +23,7 @@ check_unsigned func() i32 {
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
total i32 = 0
|
||||
total i32 := 0
|
||||
total += 10 # 10
|
||||
total -= 3 # 7
|
||||
total *= 4 # 28
|
||||
@@ -33,7 +33,7 @@ main func() i32 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ nested func(value int) int {
|
||||
}
|
||||
|
||||
make_array func($N usize) [N]u8 {
|
||||
data [N]u8 = undefined
|
||||
data [N]u8 := undefined
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ id func($T type, value T) T {
|
||||
}
|
||||
|
||||
buffer func($T type, $N usize, value T) [N]T {
|
||||
data [N]T = undefined
|
||||
data [N]T := undefined
|
||||
_ = value
|
||||
return data
|
||||
}
|
||||
|
||||
zero func($T type) T {
|
||||
value T = undefined
|
||||
value T := undefined
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -86,12 +86,12 @@ main func() i32 {
|
||||
if fixed_len(&fixed) != 3 {
|
||||
return 7
|
||||
}
|
||||
assigned i32 = 1
|
||||
assigned i32 := 1
|
||||
assigned = zero()
|
||||
_ = assigned
|
||||
_ = take_i32(zero())
|
||||
_ = return_zero()
|
||||
optional ?u32 = none
|
||||
optional ?u32 := null
|
||||
if !same_type(?u32, optional) {
|
||||
return 8
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ make_point func() Point {
|
||||
}
|
||||
|
||||
sum_loop func(limit i32) i32 {
|
||||
total i32 = 0
|
||||
i i32 = 0
|
||||
total i32 := 0
|
||||
i i32 := 0
|
||||
while i < limit {
|
||||
i += 1
|
||||
if i == 2 {
|
||||
@@ -45,8 +45,8 @@ sum_loop func(limit i32) i32 {
|
||||
}
|
||||
|
||||
sum_for func() i32 {
|
||||
total i32 = 0
|
||||
values [_]i32 = [1, 2, 3]
|
||||
total i32 := 0
|
||||
values [_]i32 := [1, 2, 3]
|
||||
for values |value, index| {
|
||||
total += value + index
|
||||
}
|
||||
@@ -54,7 +54,7 @@ sum_for func() i32 {
|
||||
}
|
||||
|
||||
defer_value func() i32 {
|
||||
value i32 = 1
|
||||
value i32 := 1
|
||||
{
|
||||
defer value += 10
|
||||
value += 1
|
||||
@@ -73,7 +73,7 @@ maybe func(flag bool) ?i32 {
|
||||
if flag {
|
||||
return 9
|
||||
}
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
may_fail func(flag bool) i32 ! Error {
|
||||
@@ -140,7 +140,7 @@ use_try func() i32 ! Error {
|
||||
}
|
||||
|
||||
ct_errdefer func(fail bool) i32 ! Error {
|
||||
trace i32 = 0
|
||||
trace i32 := 0
|
||||
defer trace = trace * 10 + 1
|
||||
errdefer |err| {
|
||||
if (err == .bad) trace = trace * 10 + 2
|
||||
@@ -151,7 +151,7 @@ ct_errdefer func(fail bool) i32 ! Error {
|
||||
}
|
||||
|
||||
ct_try_errdefer func() i32 ! Error {
|
||||
trace i32 = 0
|
||||
trace i32 := 0
|
||||
defer trace = trace * 10 + 4
|
||||
errdefer |err| {
|
||||
if (err == .bad) trace = trace * 10 + 5
|
||||
@@ -170,10 +170,8 @@ ct_errdefer_check func() i32 {
|
||||
}
|
||||
|
||||
recover func() i32 {
|
||||
return may_fail(true) catch |e| {
|
||||
match e {
|
||||
.bad: yield 5
|
||||
}
|
||||
return may_fail(true) catch |e| match e {
|
||||
.bad: 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,20 +185,20 @@ alias_add func(left @mut i32, right @mut i32) void {
|
||||
}
|
||||
|
||||
storage_mutation func() i32 {
|
||||
values [3]mut i32 = [1, 2, 3]
|
||||
values [3]mut i32 := [1, 2, 3]
|
||||
values[0] += 1
|
||||
bump_ptr(&values[1])
|
||||
|
||||
view []mut i32 = values[..]
|
||||
view []mut i32 := values[..]
|
||||
for view |@item| {
|
||||
item^ += 1
|
||||
}
|
||||
|
||||
pointer *mut i32 = view.ptr
|
||||
pointer *mut i32 := view.ptr
|
||||
pointer[2] += 1
|
||||
alias_add(&values[0], &view[0])
|
||||
|
||||
box Box = Box { point = Point { x = 2, y = 3 } }
|
||||
box Box := Box { point = Point { x = 2, y = 3 } }
|
||||
match box {
|
||||
.point |@p|: p.x += values[1]
|
||||
.empty: values[0] = values[0]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
make_array func($N usize) [N]u8 {
|
||||
data [N]u8 = undefined
|
||||
data [N]u8 := undefined
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
# 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -45,9 +45,9 @@ main func() i32 {
|
||||
}
|
||||
|
||||
# block scoping: inner bindings do not escape the block
|
||||
x i32 = 1
|
||||
x i32 := 1
|
||||
if x == 1 {
|
||||
inner_x i32 = 100
|
||||
inner_x i32 := 100
|
||||
if inner_x == 100 {
|
||||
total = total + 5 # 40
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -89,7 +89,7 @@ main func() i32 {
|
||||
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
|
||||
@@ -99,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
|
||||
@@ -119,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
|
||||
@@ -127,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
|
||||
@@ -139,7 +139,7 @@ main func() i32 {
|
||||
if (enclosing_defer_check() != 2) return 107
|
||||
|
||||
# 8. errdefer is skipped on success; ordinary defers stay interleaved.
|
||||
trace i32 = 0
|
||||
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.
|
||||
|
||||
@@ -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 LocalID := take(id)
|
||||
maybe ?LocalID := copy
|
||||
pointer @LocalID := ©
|
||||
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
|
||||
|
||||
@@ -17,7 +17,7 @@ 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
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
@@ -18,7 +18,7 @@ Value :: union(enum) {
|
||||
enum_score func(kind Kind) i32 {
|
||||
result :: match kind {
|
||||
.first: 1
|
||||
expand |value|: {
|
||||
inline |value|: {
|
||||
yield match value {
|
||||
.second: 2
|
||||
.third: 3
|
||||
@@ -31,7 +31,7 @@ enum_score func(kind Kind) i32 {
|
||||
equal_value func(a, b Value) bool {
|
||||
if (tag!(a) != tag!(b)) return false
|
||||
result :: match a {
|
||||
expand |value, tag|: {
|
||||
inline |value, tag|: {
|
||||
yield match tag {
|
||||
.number: value == field!(b, tagname!(tag))
|
||||
.pair: {
|
||||
@@ -49,7 +49,7 @@ equal_value func(a, b Value) bool {
|
||||
|
||||
increment func(value @mut Value) void {
|
||||
match value^ {
|
||||
expand |@payload, tag|: match tag {
|
||||
inline |@payload, tag|: match tag {
|
||||
.number: payload^ += 1
|
||||
.pair: payload.left += 1
|
||||
.empty: _ = payload
|
||||
@@ -68,8 +68,8 @@ main func() i32 {
|
||||
return 2
|
||||
}
|
||||
|
||||
a Value = .number{41}
|
||||
b Value = .number{41}
|
||||
a Value := .number{41}
|
||||
b Value := .number{41}
|
||||
if !equal_value(a, b) or equal_value(a, .pair{left = 41, right = 0}) {
|
||||
return 3
|
||||
}
|
||||
@@ -78,8 +78,8 @@ main func() i32 {
|
||||
return 4
|
||||
}
|
||||
|
||||
pair_a Value = .pair{left = 2, right = 3}
|
||||
pair_b Value = .pair{left = 2, right = 3}
|
||||
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
|
||||
}
|
||||
@@ -88,7 +88,7 @@ main func() i32 {
|
||||
return 6
|
||||
}
|
||||
|
||||
empty Value = .empty
|
||||
empty Value := .empty
|
||||
increment(&empty)
|
||||
return 0
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
bad int = 4
|
||||
bad int := 4
|
||||
|
||||
read_bad func() int {
|
||||
return bad
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
io :: import "@std/io"
|
||||
process :: import "@std/process"
|
||||
|
||||
hide read_ok func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
|
||||
@hide read_ok func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
|
||||
if buffer.len == 0 {
|
||||
return 0
|
||||
}
|
||||
@@ -13,32 +13,32 @@ hide read_ok func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.Re
|
||||
return 2
|
||||
}
|
||||
|
||||
hide read_too_much func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.ReadError {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 = none,
|
||||
context = null,
|
||||
handle = io.Handle {file_desc = 0},
|
||||
read = read_ok,
|
||||
}
|
||||
@@ -46,7 +46,7 @@ ok_reader func() io.Reader {
|
||||
|
||||
bad_reader func() io.Reader {
|
||||
return io.Reader {
|
||||
context = none,
|
||||
context = null,
|
||||
handle = io.Handle {file_desc = 0},
|
||||
read = read_too_much,
|
||||
}
|
||||
@@ -54,7 +54,7 @@ bad_reader func() io.Reader {
|
||||
|
||||
eof_reader func() io.Reader {
|
||||
return io.Reader {
|
||||
context = none,
|
||||
context = null,
|
||||
handle = io.Handle {file_desc = 0},
|
||||
read = read_eof,
|
||||
}
|
||||
@@ -62,7 +62,7 @@ eof_reader func() io.Reader {
|
||||
|
||||
short_writer func() io.Writer {
|
||||
return io.Writer {
|
||||
context = none,
|
||||
context = null,
|
||||
handle = io.Handle {file_desc = 0},
|
||||
write = write_short,
|
||||
}
|
||||
@@ -70,7 +70,7 @@ short_writer func() io.Writer {
|
||||
|
||||
none_writer func() io.Writer {
|
||||
return io.Writer {
|
||||
context = none,
|
||||
context = null,
|
||||
handle = io.Handle {file_desc = 0},
|
||||
write = write_none,
|
||||
}
|
||||
@@ -78,14 +78,14 @@ none_writer func() io.Writer {
|
||||
|
||||
bad_writer func() io.Writer {
|
||||
return io.Writer {
|
||||
context = none,
|
||||
context = null,
|
||||
handle = io.Handle {file_desc = 0},
|
||||
write = write_too_much,
|
||||
}
|
||||
}
|
||||
|
||||
rejects_bad_read func() bool {
|
||||
buffer [1]mut u8 = [0]
|
||||
buffer [1]mut u8 := [0]
|
||||
_ = io.read(bad_reader(), buffer[..]) catch |err| {
|
||||
return err == .read_failed
|
||||
}
|
||||
@@ -108,7 +108,7 @@ rejects_bad_write func() bool {
|
||||
|
||||
main func(init process.Init) i32 {
|
||||
system io.Io :: init.io
|
||||
buffer [2]mut u8 = [0, 0]
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mem :: import "@std/mem"
|
||||
|
||||
raw_allocator_test func() i32 {
|
||||
resized ?*mut u8 = mem.raw_realloc(mem.c_allocator, none, 0, 4, 1)
|
||||
resized ?*mut u8 := mem.raw_realloc(mem.c_allocator, null, 0, 4, 1)
|
||||
if resized |bytes| {
|
||||
bytes[0] = 10
|
||||
bytes[1] = 20
|
||||
@@ -11,7 +11,7 @@ raw_allocator_test func() i32 {
|
||||
return 20
|
||||
}
|
||||
|
||||
grown ?*mut u8 = mem.raw_realloc(mem.c_allocator, resized, 4, 8, 1)
|
||||
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 {
|
||||
@@ -23,7 +23,7 @@ raw_allocator_test func() i32 {
|
||||
return 22
|
||||
}
|
||||
|
||||
shrunk ?*mut u8 = mem.raw_realloc(mem.c_allocator, resized, 8, 2, 1)
|
||||
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 {
|
||||
@@ -35,7 +35,7 @@ raw_allocator_test func() i32 {
|
||||
return 24
|
||||
}
|
||||
|
||||
invalid ?*mut u8 = mem.raw_realloc(mem.c_allocator, resized, 2, 4, 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)
|
||||
@@ -54,14 +54,14 @@ raw_allocator_test func() i32 {
|
||||
return 27
|
||||
}
|
||||
|
||||
over_aligned ?*mut u8 = mem.raw_alloc(mem.c_allocator, 4, 32)
|
||||
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)
|
||||
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)
|
||||
@@ -73,19 +73,19 @@ raw_allocator_test func() i32 {
|
||||
return 30
|
||||
}
|
||||
|
||||
zero_alignment ?*mut u8 = mem.raw_alloc(mem.c_allocator, 8, 0)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -11,9 +11,9 @@ TaskList :: struct {
|
||||
|
||||
task_list_init func(allocator mem.Allocator) TaskList {
|
||||
return TaskList {
|
||||
ids = none,
|
||||
priorities = none,
|
||||
durations = none,
|
||||
ids = null,
|
||||
priorities = null,
|
||||
durations = null,
|
||||
len = 0,
|
||||
capacity = 0,
|
||||
allocator = allocator,
|
||||
@@ -21,13 +21,13 @@ task_list_init func(allocator mem.Allocator) TaskList {
|
||||
}
|
||||
|
||||
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 |_| {
|
||||
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 none
|
||||
if (failed) return null
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -42,14 +42,14 @@ task_list_reserve func(list @mut TaskList, capacity usize) bool {
|
||||
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)
|
||||
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
|
||||
i usize := 0
|
||||
while i < list.len : i += 1 {
|
||||
ids[i] = old_ids[i]
|
||||
priorities[i] = old_priorities[i]
|
||||
@@ -82,7 +82,7 @@ task_list_reserve func(list @mut TaskList, capacity usize) bool {
|
||||
|
||||
task_list_push func(list @mut TaskList, id i32, priority i32, duration i32) bool {
|
||||
if list.len == list.capacity {
|
||||
new_capacity usize = 2
|
||||
new_capacity usize := 2
|
||||
if list.capacity != 0 {
|
||||
new_capacity = list.capacity * 2
|
||||
}
|
||||
@@ -92,7 +92,7 @@ task_list_push func(list @mut TaskList, id i32, priority i32, duration i32) bool
|
||||
}
|
||||
|
||||
if (list.ids and list.priorities and list.durations) |ids, priorities, durations| {
|
||||
index usize = list.len
|
||||
index usize := list.len
|
||||
ids[index] = id
|
||||
priorities[index] = priority
|
||||
durations[index] = duration
|
||||
@@ -113,11 +113,11 @@ task_list_best_id func(list @mut TaskList) i32 {
|
||||
}
|
||||
|
||||
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
|
||||
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])
|
||||
score i32 := task_score(priorities[i], durations[i])
|
||||
if score > best_score {
|
||||
best_score = score
|
||||
best_index = i
|
||||
@@ -130,9 +130,9 @@ task_list_best_id func(list @mut TaskList) i32 {
|
||||
}
|
||||
|
||||
task_list_total_duration func(list @mut TaskList) i32 {
|
||||
total i32 = 0
|
||||
total i32 := 0
|
||||
if list.durations |durations| {
|
||||
i usize = 0
|
||||
i usize := 0
|
||||
while i < list.len : i += 1 {
|
||||
total += durations[i]
|
||||
}
|
||||
@@ -144,15 +144,15 @@ 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 = none
|
||||
list.priorities = none
|
||||
list.durations = none
|
||||
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)
|
||||
tasks TaskList := task_list_init(mem.c_allocator)
|
||||
defer task_list_deinit(&tasks)
|
||||
|
||||
if task_list_push(&tasks, 101, 3, 5) == false {
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
mem :: import "@std/mem"
|
||||
|
||||
hide probe_count func(context ?@mut anyopaque) void {
|
||||
@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 {
|
||||
@hide probe_alloc func(context ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||
probe_count(context)
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
hide probe_realloc func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||
@hide probe_realloc func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||
probe_count(context)
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
hide probe_free func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
|
||||
@hide probe_free func(context ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {
|
||||
probe_count(context)
|
||||
}
|
||||
|
||||
hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
@hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
alloc = probe_alloc,
|
||||
realloc = probe_realloc,
|
||||
free = probe_free,
|
||||
@@ -30,8 +30,8 @@ hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
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_calls [1]mut usize := [0]
|
||||
second_calls [1]mut usize := [0]
|
||||
first_allocator mem.Allocator :: mem.Allocator {
|
||||
context = &first_calls,
|
||||
vtable = &probe_vtable,
|
||||
@@ -42,14 +42,14 @@ typed_allocator_test func() i32 {
|
||||
}
|
||||
|
||||
_ = mem.raw_alloc(first_allocator, 1, 1)
|
||||
_ = mem.raw_realloc(first_allocator, none, 0, 1, 1)
|
||||
mem.raw_free(first_allocator, none, 0, 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 |_| {
|
||||
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]
|
||||
}
|
||||
@@ -57,9 +57,9 @@ typed_allocator_test func() i32 {
|
||||
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_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]
|
||||
}
|
||||
@@ -68,22 +68,22 @@ typed_allocator_test func() i32 {
|
||||
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 |_| {
|
||||
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
|
||||
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 bool := false
|
||||
typed []mut i32 := mem.alloc(mem.c_allocator, 4) catch |_| {
|
||||
typed_failed = true
|
||||
yield (&i32_fallback).ptr[..0]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -24,6 +24,12 @@ Config :: struct {
|
||||
maybe ?i32
|
||||
payload Payload
|
||||
}
|
||||
Count :: distinct i32
|
||||
Byte :: distinct u8
|
||||
Ratio :: distinct f32
|
||||
Inner :: distinct u32
|
||||
Outer :: distinct Inner
|
||||
|
||||
|
||||
first Config :: Config {
|
||||
enabled = true,
|
||||
@@ -54,7 +60,7 @@ different Config :: Config {
|
||||
state = .idle,
|
||||
values = [21, 21],
|
||||
pair = Pair {7, false},
|
||||
maybe = none,
|
||||
maybe = null,
|
||||
payload = .empty,
|
||||
}
|
||||
|
||||
@@ -80,8 +86,8 @@ main func(init process.Init) i32 {
|
||||
}
|
||||
|
||||
writer io.Writer :: io.stdout(init.io)
|
||||
positive f64 = 1.0
|
||||
zero f64 = 0.0
|
||||
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", {
|
||||
@@ -105,6 +111,19 @@ main func(init process.Init) i32 {
|
||||
}) catch |_| {
|
||||
return 3
|
||||
}
|
||||
io.print(writer, "{} {d} {b} {o} {x} {X} {c} {e} {}\n", {
|
||||
Count(i32(-42)),
|
||||
Count(i32(-42)),
|
||||
Byte(u8(10)),
|
||||
Byte(u8(10)),
|
||||
Byte(u8(255)),
|
||||
Byte(u8(255)),
|
||||
Byte(u8('A')),
|
||||
Ratio(f32(1.5)),
|
||||
Outer(Inner(u32(7))),
|
||||
}) catch |_| {
|
||||
return 4
|
||||
}
|
||||
debug.print("debug={} {x}\n", {State.idle, 42})
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ Point :: struct {
|
||||
x i32
|
||||
}
|
||||
|
||||
counter int = 0
|
||||
ratio float = 1
|
||||
span range = 0..2
|
||||
point Point = Point { x = 1 }
|
||||
values [_]mut i32 = [10, 20]
|
||||
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
|
||||
@@ -19,7 +19,7 @@ main func() i32 {
|
||||
point.x += counter
|
||||
values[1] = point.x
|
||||
|
||||
total i32 = counter + point.x + values[1]
|
||||
total i32 := counter + point.x + values[1]
|
||||
for span |i| {
|
||||
total += i
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
main func() i32 {
|
||||
value i32 = 1
|
||||
value := i32(1)
|
||||
value = value + 2
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
main func() void {
|
||||
value i8 = 127
|
||||
value i8 := 127
|
||||
_ = value + 1
|
||||
}
|
||||
|
||||
@@ -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,5 +1,5 @@
|
||||
main func() i32 {
|
||||
flag bool = true
|
||||
flag bool := true
|
||||
value :: i32(flag)
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ format func() []u8 {
|
||||
}
|
||||
|
||||
sum func($T type, value T) i32 {
|
||||
total i32 = 0
|
||||
total i32 := 0
|
||||
match typeinfo!(T) {
|
||||
.record |record|: expand for record.fields |field| {
|
||||
.record |record|: inline for record.fields |field| {
|
||||
total += i32(field!(value, field.name))
|
||||
}
|
||||
else: compile_error!("sum requires a record")
|
||||
@@ -20,9 +20,9 @@ sum func($T type, value T) i32 {
|
||||
}
|
||||
|
||||
static_control func($T type, value T) i32 {
|
||||
total i32 = 0
|
||||
total i32 := 0
|
||||
match typeinfo!(T) {
|
||||
.record |record|: expand for record.fields |field| {
|
||||
.record |record|: inline for record.fields |field| {
|
||||
{
|
||||
if field.index == 1 {
|
||||
continue
|
||||
@@ -49,15 +49,15 @@ row_value func(row Row) i32 {
|
||||
}
|
||||
|
||||
static_aggregates func() i32 {
|
||||
total i32 = 0
|
||||
expand for {Row {value = 2}, Row {value = 40}} |row| {
|
||||
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}
|
||||
numbers Numbers := Numbers {1, 2, 39}
|
||||
singleton :: {42,}
|
||||
empty :: {}
|
||||
block_value :: {
|
||||
|
||||
@@ -4,6 +4,6 @@ Val :: union {
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
x Val = Val{ n = 42 }
|
||||
x Val := Val{ n = 42 }
|
||||
return x.n
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
warn_only func(value i32, unused i32) i32 {
|
||||
local i32 = 1
|
||||
write_only i32 = 2
|
||||
local i32 := 1
|
||||
write_only i32 := 2
|
||||
write_only = 3
|
||||
consumed i32 = value
|
||||
consumed i32 := value
|
||||
_ = consumed
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
# 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
|
||||
}
|
||||
@@ -33,9 +33,9 @@ main func() i32 {
|
||||
|
||||
# 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 {
|
||||
body_k u32 = 100
|
||||
body_k u32 := 100
|
||||
if body_k == 100 {
|
||||
total = total + 2
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -93,13 +93,13 @@ vif_expression func(old_entries []u8) usize {
|
||||
# --- 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
|
||||
@@ -108,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
|
||||
@@ -123,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
|
||||
@@ -164,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
|
||||
@@ -199,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
|
||||
@@ -207,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| {
|
||||
@@ -225,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
|
||||
@@ -235,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
|
||||
@@ -247,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
|
||||
@@ -259,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| {
|
||||
@@ -273,7 +273,7 @@ stmt_block_escape func() i32 {
|
||||
|
||||
# A labeled block can also be exited through an ordinary nested block.
|
||||
stmt_block_nested func() i32 {
|
||||
hits i32 = 0
|
||||
hits i32 := 0
|
||||
outer: {
|
||||
{
|
||||
hits = 1
|
||||
@@ -284,11 +284,11 @@ stmt_block_nested func() i32 {
|
||||
return hits
|
||||
}
|
||||
|
||||
# Item B: a `none` yielded before a concrete `yield :blk` that references a block local.
|
||||
# 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| {
|
||||
@@ -321,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
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
id = "brolang"
|
||||
name = "Brolang"
|
||||
version = "0.1.1"
|
||||
schema_version = 1
|
||||
authors = ["Brolang contributors"]
|
||||
description = "Brolang language support"
|
||||
repository = "ssh://git@gitea.hl-valdemar.dev:2222/hl-valdemar/brolang.git"
|
||||
languages = ["languages/brolang"]
|
||||
|
||||
[grammars.brolang]
|
||||
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
||||
rev = "2e09864adfe63d1076b219f80adad66e519fffe2"
|
||||
path = "tree-sitter-brolang"
|
||||
@@ -1,14 +0,0 @@
|
||||
name = "Brolang"
|
||||
grammar = "brolang"
|
||||
path_suffixes = ["bro", "hon"]
|
||||
line_comments = ["# "]
|
||||
hard_tabs = true
|
||||
tab_size = 4
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||
]
|
||||
@@ -1,128 +0,0 @@
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(string)
|
||||
(multiline_string)
|
||||
] @string
|
||||
|
||||
(character) @string
|
||||
(escape_sequence) @string.escape
|
||||
|
||||
[
|
||||
(integer)
|
||||
(float)
|
||||
] @number
|
||||
|
||||
(boolean) @boolean
|
||||
|
||||
[
|
||||
(none)
|
||||
(undefined)
|
||||
] @constant.builtin
|
||||
|
||||
(builtin_type) @type.builtin
|
||||
(named_type) @type
|
||||
|
||||
(named_type
|
||||
(qualified_identifier
|
||||
(identifier) @function .)
|
||||
(argument_list))
|
||||
|
||||
(struct_literal
|
||||
type: (qualified_identifier
|
||||
(identifier) @function .)
|
||||
(argument_list))
|
||||
|
||||
(type_declaration name: (identifier) @type)
|
||||
(function_declaration name: (identifier) @function)
|
||||
(test_declaration name: (identifier) @function)
|
||||
(parameter name: (identifier) @variable.parameter)
|
||||
|
||||
(intrinsic_call_expression function: (identifier) @function.builtin)
|
||||
(call_expression function: (expression (identifier) @function))
|
||||
(call_expression function: (expression (field_expression field: (identifier) @function)))
|
||||
(field_expression field: (identifier) @property)
|
||||
(field_expression field: (integer) @property)
|
||||
(field_initializer name: (identifier) @property)
|
||||
(keyed_field_initializer name: (identifier) @property)
|
||||
(record_field name: (identifier) @property)
|
||||
(enum_member name: (identifier) @property)
|
||||
(enum_literal name: (identifier) @property)
|
||||
|
||||
(import_declaration alias: (identifier) @variable)
|
||||
(opaque_type) @keyword
|
||||
|
||||
[
|
||||
"func"
|
||||
"test"
|
||||
"c_func"
|
||||
"struct"
|
||||
"c_struct"
|
||||
"union"
|
||||
"enum"
|
||||
"distinct"
|
||||
"alias"
|
||||
"import"
|
||||
"hide"
|
||||
"return"
|
||||
"try"
|
||||
"catch"
|
||||
"mut"
|
||||
"orelse"
|
||||
"and"
|
||||
"or"
|
||||
"if"
|
||||
"while"
|
||||
"for"
|
||||
"expand"
|
||||
"break"
|
||||
"continue"
|
||||
"defer"
|
||||
"errdefer"
|
||||
"yield"
|
||||
"match"
|
||||
"else"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"::"
|
||||
"="
|
||||
"+="
|
||||
"-="
|
||||
"*="
|
||||
"/="
|
||||
"=="
|
||||
"!="
|
||||
"<"
|
||||
"<="
|
||||
">"
|
||||
">="
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"!"
|
||||
"&"
|
||||
"@"
|
||||
"?"
|
||||
"^"
|
||||
".."
|
||||
"..="
|
||||
"|"
|
||||
] @operator
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
","
|
||||
"."
|
||||
":"
|
||||
";"
|
||||
] @punctuation.delimiter
|
||||
@@ -101,10 +101,10 @@ print_usage :: proc() {
|
||||
" brolang translate-c|--translate-c <header.h>... [--output-dir <dir>] [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang build [root] (reads root/build.bro; writes root/build/name)",
|
||||
" brolang build [root] (reads exactly one of root/build.bro or root/build.hon; writes root/build/name)",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang test [root] (reads root/build.bro; writes and runs root/build/name-test)",
|
||||
" brolang test [root] (reads exactly one of root/build.bro or root/build.hon; writes and runs root/build/name-test)",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang new <project-path>",
|
||||
@@ -142,17 +142,22 @@ is_version_command :: proc(arg: string) -> bool {
|
||||
}
|
||||
|
||||
template_root_valid :: proc(root: string) -> bool {
|
||||
std_build, std_error := filepath.join({root, "std", "build", "build.bro"})
|
||||
if std_error != nil {
|
||||
std_build_bro, bro_error := filepath.join({root, "std", "build", "build.bro"})
|
||||
if bro_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(std_build)
|
||||
defer delete(std_build_bro)
|
||||
std_build_hon, hon_error := filepath.join({root, "std", "build", "build.hon"})
|
||||
if hon_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(std_build_hon)
|
||||
ffi_stdio, ffi_error := filepath.join({root, "ffi", "c", "stdio.bro"})
|
||||
if ffi_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(ffi_stdio)
|
||||
return os.exists(std_build) && os.exists(ffi_stdio)
|
||||
return (os.exists(std_build_bro) || os.exists(std_build_hon)) && os.exists(ffi_stdio)
|
||||
}
|
||||
|
||||
find_template_root :: proc(allocator := context.allocator) -> (string, bool) {
|
||||
|
||||
@@ -28,7 +28,7 @@ reserve func($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.All
|
||||
return
|
||||
}
|
||||
|
||||
new_capacity usize = 8
|
||||
new_capacity usize := 8
|
||||
if list.capacity >= 8 {
|
||||
half usize :: divtrunc!(list.capacity, 2)
|
||||
if list.capacity > maxval!(usize) - half {
|
||||
@@ -62,6 +62,13 @@ append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||
return
|
||||
}
|
||||
|
||||
pop func($T type, list @mut ArrayList(T)) ?T {
|
||||
if (list.items.len == 0) return null
|
||||
value :: list.items[list.items.len - 1]
|
||||
list.items = list.items.ptr[..list.items.len - 1]
|
||||
return value
|
||||
}
|
||||
|
||||
clear func($T type, list @mut ArrayList(T)) void {
|
||||
list.items = list.items.ptr[..0]
|
||||
}
|
||||
@@ -2,18 +2,17 @@ import "@std/mem"
|
||||
import "@std/testing"
|
||||
|
||||
handles_append test {
|
||||
list ArrayList(i32) = init(mem.c_allocator)
|
||||
list ArrayList(i32) := init(mem.c_allocator)
|
||||
defer deinit(&list)
|
||||
|
||||
try append(&list, 42)
|
||||
|
||||
try testing.expect_type(usize, list.items.len)
|
||||
try testing.expect_equal(1, list.items.len)
|
||||
try testing.expect_equal(42, list.items[0])
|
||||
}
|
||||
|
||||
handles_clear test {
|
||||
list ArrayList(i32) = init(mem.c_allocator)
|
||||
list ArrayList(i32) := init(mem.c_allocator)
|
||||
defer deinit(&list)
|
||||
|
||||
try append(&list, 42)
|
||||
@@ -23,7 +22,7 @@ handles_clear test {
|
||||
}
|
||||
|
||||
handles_reserve test {
|
||||
list ArrayList(i32) = init(mem.c_allocator)
|
||||
list ArrayList(i32) := init(mem.c_allocator)
|
||||
defer deinit(&list)
|
||||
|
||||
try reserve(&list, 10)
|
||||
@@ -1,17 +0,0 @@
|
||||
# Build configuration surface for `brolang build` (v0).
|
||||
#
|
||||
# A project's `build.bro` imports this module and declares a top-level constant
|
||||
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
|
||||
# build.bro, reads the config, and writes root/build/name.
|
||||
#
|
||||
# Declarative and literal-only: one executable per build. List fields take an
|
||||
# address-of an array literal (`&["raylib"]`) and default to empty.
|
||||
BuildConfig :: struct {
|
||||
name []u8 # output executable name under root/build
|
||||
source []u8 # program package directory, relative to build.bro
|
||||
libraries [][]u8 = &[] # library names to link (-l)
|
||||
lib_paths [][]u8 = &[] # library search directories (-L)
|
||||
includes [][]u8 = &[] # C include directories (-I)
|
||||
defines [][]u8 = &[] # C preprocessor defines (name or name=value)
|
||||
links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Build configuration surface for `brolang build` (v0).
|
||||
#
|
||||
# A project's `build.bro` or `build.hon` imports this module and declares a
|
||||
# top-level constant named `config` of type `BuildConfig`. `brolang build
|
||||
# [root]` accepts exactly one of those files, reads the config, and writes
|
||||
# root/build/name.
|
||||
#
|
||||
# Declarative and literal-only: one executable per build. List fields take an
|
||||
# address-of an array literal (`&["raylib"]`) and default to empty.
|
||||
BuildConfig :: struct {
|
||||
name []u8 # output executable name under root/build
|
||||
source []u8 # program package directory, relative to the build file
|
||||
libraries [][]u8 = &[] # library names to link (-l)
|
||||
lib_paths [][]u8 = &[] # library search directories (-L)
|
||||
includes [][]u8 = &[] # C include directories (-I)
|
||||
defines [][]u8 = &[] # C preprocessor defines (name or name=value)
|
||||
links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs)
|
||||
}
|
||||
@@ -3,15 +3,15 @@ import "@std/io"
|
||||
|
||||
print func($format []u8, $Args type, args Args) void {
|
||||
writer io.Writer :: io.Writer{
|
||||
context = none,
|
||||
context = null,
|
||||
handle = io.Handle{ file_desc = c_int(io.Stream.stderr) },
|
||||
write = write,
|
||||
}
|
||||
io.print(writer, format, Args, args) catch |_| {}
|
||||
}
|
||||
|
||||
hide write func(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
|
||||
request usize = bytes.len
|
||||
@hide write func(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
|
||||
request usize := bytes.len
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
@@ -0,0 +1,45 @@
|
||||
import "@std/meta"
|
||||
|
||||
EnumMap func($E, $V type) type {
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: return struct {
|
||||
present [info.fields.len]mut bool # fixme: replace with bitset
|
||||
values [info.fields.len]mut V
|
||||
}
|
||||
else: compile_error!("EnumMap key must be an enum")
|
||||
}
|
||||
}
|
||||
|
||||
init func(
|
||||
$E, $V type,
|
||||
values meta.EnumFieldStruct(E, ?V, some!(null)),
|
||||
) EnumMap(E, V) {
|
||||
map EnumMap(E, V) := undefined
|
||||
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: inline for info.fields |field, i| {
|
||||
map.present[i] = false
|
||||
|
||||
if field!(values, field.name) |value| {
|
||||
map.present[i] = true
|
||||
map.values[i] = value
|
||||
}
|
||||
}
|
||||
else: compile_error!("EnumMap key must be an enum")
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
get func($E, $V type, map @EnumMap(E, V), key E) ?V {
|
||||
# fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: inline for info.fields |field, i| {
|
||||
if key == field!(E, field.name) {
|
||||
if (map.present[i]) return map.values[i]
|
||||
return null
|
||||
}
|
||||
}
|
||||
else: compile_error!("EnumMap key must be an enum")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import "@std/mem"
|
||||
import "@std/testing"
|
||||
|
||||
TestEnum :: enum(u8) {
|
||||
ident = 3
|
||||
int = 8
|
||||
eof = 21
|
||||
}
|
||||
|
||||
handles_sparse_enum_get test {
|
||||
names EnumMap(TestEnum, []u8) := init({
|
||||
ident = "identifier",
|
||||
int = "integer",
|
||||
})
|
||||
|
||||
ident :: get(&names, TestEnum.ident)
|
||||
|
||||
try testing.expect_type(?[]u8, ident)
|
||||
try testing.expect(mem.eql("identifier", ident?))
|
||||
|
||||
eof :: get(&names, TestEnum.eof)
|
||||
|
||||
try testing.expect_type(?[]u8, eof)
|
||||
try testing.expect_equal(null, eof)
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import "@std/mem"
|
||||
|
||||
PutError :: enum { key_exists }
|
||||
|
||||
Entry func($K, $V type) type {
|
||||
return struct {
|
||||
# hash = 0 means empty
|
||||
hash usize = 0
|
||||
key K
|
||||
value V
|
||||
}
|
||||
}
|
||||
|
||||
HashMap func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
) type {
|
||||
return struct {
|
||||
entries []mut Entry(K, V)
|
||||
count usize
|
||||
allocator mem.Allocator
|
||||
}
|
||||
}
|
||||
|
||||
StringHashMap func($V type) type {
|
||||
return HashMap([]u8, V, str_hash, str_eql)
|
||||
}
|
||||
|
||||
init func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
allocator mem.Allocator,
|
||||
) HashMap(K, V, hash_key, keys_eql) {
|
||||
return HashMap(K, V, hash_key, keys_eql){
|
||||
entries = mem.empty(Entry(K, V)),
|
||||
count = 0,
|
||||
allocator = allocator,
|
||||
}
|
||||
}
|
||||
|
||||
#! free the entries in the hash map.
|
||||
#! note: this operation invalidates the map.
|
||||
deinit func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
map @HashMap(K, V, hash_key, keys_eql),
|
||||
) void { mem.free(map.allocator, map.entries) }
|
||||
|
||||
get func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
map @HashMap(K, V, hash_key, keys_eql),
|
||||
key K,
|
||||
) ?V {
|
||||
if (map.count == 0) return null
|
||||
|
||||
hash :: normalize(hash_key(key))
|
||||
idx usize := hash & (map.entries.len - 1)
|
||||
|
||||
while true {
|
||||
entry :: map.entries[idx]
|
||||
if (entry.hash == 0) return null
|
||||
|
||||
if (entry.hash == hash and keys_eql(entry.key, key)) {
|
||||
return entry.value
|
||||
}
|
||||
|
||||
idx = (idx + 1) & (map.entries.len - 1)
|
||||
}
|
||||
}
|
||||
|
||||
put func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
map @mut HashMap(K, V, hash_key, keys_eql),
|
||||
key K,
|
||||
value V,
|
||||
) void ! (PutError | mem.AllocError) {
|
||||
# check grow
|
||||
threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
|
||||
if (map.entries.len == 0 or map.count + 1 > threshold) {
|
||||
old_entries :: map.entries
|
||||
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
|
||||
new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
|
||||
|
||||
# zero new entries
|
||||
for 0..new_entries.len |i| {
|
||||
new_entries[i].hash = 0
|
||||
}
|
||||
|
||||
# move old entries
|
||||
for old_entries |entry| {
|
||||
if (entry.hash == 0) continue
|
||||
|
||||
# find an empty slot
|
||||
idx usize := entry.hash & (new_entries.len - 1)
|
||||
while new_entries[idx].hash != 0 {
|
||||
idx = (idx + 1) & (new_entries.len - 1)
|
||||
}
|
||||
|
||||
new_entries[idx] = entry
|
||||
}
|
||||
|
||||
map.entries = new_entries
|
||||
mem.free(map.allocator, old_entries)
|
||||
}
|
||||
|
||||
# put new entry
|
||||
hash :: normalize(hash_key(key))
|
||||
idx usize := hash & (map.entries.len - 1)
|
||||
|
||||
while true {
|
||||
entry :: map.entries[idx]
|
||||
if entry.hash == 0 {
|
||||
map.entries[idx] = Entry(K, V){
|
||||
hash = hash,
|
||||
key = key,
|
||||
value = value,
|
||||
}
|
||||
map.count += 1
|
||||
return
|
||||
}
|
||||
|
||||
if (entry.hash == hash and keys_eql(entry.key, key)) {
|
||||
return .key_exists
|
||||
}
|
||||
|
||||
idx = (idx + 1) & (map.entries.len - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@hide normalize func(hash usize) usize {
|
||||
# mapping both 0 and 1 to 1 is safe because equality resolves
|
||||
# collisions (since hash and key must both be equal).
|
||||
if (hash == 0) return 1
|
||||
return hash
|
||||
}
|
||||
|
||||
#! FNV-1a hash implementation.
|
||||
#! note: vulnerable to collision attacks.
|
||||
@hide str_hash func(key []u8) usize {
|
||||
hash u32 := 2166136261 # offset basis
|
||||
prime u32 := 16777619
|
||||
|
||||
for key |byte| {
|
||||
product u64 :: u64(hash xor u32(byte)) * prime
|
||||
hash = u32(product & u64(maxval!(u32)))
|
||||
}
|
||||
|
||||
return usize(hash)
|
||||
}
|
||||
|
||||
@hide str_eql func(a, b []u8) bool {
|
||||
return mem.eql(a, b)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import "@std/mem"
|
||||
import "@std/testing"
|
||||
|
||||
handles_put_and_get test {
|
||||
map StringHashMap(u32) := init(mem.c_allocator)
|
||||
defer deinit(&map)
|
||||
|
||||
try put(&map, "key", 42)
|
||||
value :: get(&map, "key")
|
||||
|
||||
try testing.expect_type(?u32, value)
|
||||
try testing.expect_equal(42, value?)
|
||||
}
|
||||
@@ -46,8 +46,8 @@ writer func(file File) Writer {
|
||||
}
|
||||
}
|
||||
|
||||
hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
|
||||
request usize = buffer.len
|
||||
@hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
|
||||
request usize := buffer.len
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
@@ -68,8 +68,8 @@ hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize !
|
||||
}
|
||||
}
|
||||
|
||||
hide system_write func(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
|
||||
request usize = bytes.len
|
||||
@hide system_write func(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
|
||||
request usize := bytes.len
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
@@ -90,8 +90,8 @@ hide system_write func(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! Wri
|
||||
}
|
||||
}
|
||||
|
||||
hide system_open func(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError {
|
||||
flags c_int = c.O_RDONLY
|
||||
@hide system_open func(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError {
|
||||
flags c_int := c.O_RDONLY
|
||||
match mode {
|
||||
.read_only: flags = c.O_RDONLY
|
||||
.write_only: flags = c.O_WRONLY
|
||||
@@ -108,25 +108,25 @@ hide system_open func(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! Op
|
||||
}
|
||||
}
|
||||
|
||||
hide system_close func(_ ?@mut anyopaque, handle Handle) void ! CloseError {
|
||||
@hide system_close func(_ ?@mut anyopaque, handle Handle) void ! CloseError {
|
||||
if c.close(handle.file_desc) != 0 {
|
||||
return .close_failed
|
||||
}
|
||||
}
|
||||
|
||||
hide system_stdin func(_ ?@mut anyopaque) Handle {
|
||||
@hide system_stdin func(_ ?@mut anyopaque) Handle {
|
||||
return Handle {file_desc = c_int(Stream.stdin)}
|
||||
}
|
||||
|
||||
hide system_stdout func(_ ?@mut anyopaque) Handle {
|
||||
@hide system_stdout func(_ ?@mut anyopaque) Handle {
|
||||
return Handle {file_desc = c_int(Stream.stdout)}
|
||||
}
|
||||
|
||||
hide system_stderr func(_ ?@mut anyopaque) Handle {
|
||||
@hide system_stderr func(_ ?@mut anyopaque) Handle {
|
||||
return Handle {file_desc = c_int(Stream.stderr)}
|
||||
}
|
||||
|
||||
hide system_vtable IoVTable :: IoVTable {
|
||||
@hide system_vtable IoVTable :: IoVTable {
|
||||
read = system_read,
|
||||
write = system_write,
|
||||
open = system_open,
|
||||
@@ -136,9 +136,9 @@ hide system_vtable IoVTable :: IoVTable {
|
||||
stderr = system_stderr,
|
||||
}
|
||||
|
||||
hide system func() Io {
|
||||
@hide system func() Io {
|
||||
return Io {
|
||||
context = none,
|
||||
context = null,
|
||||
vtable = &system_vtable,
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ write func(output Writer, bytes []u8) usize ! WriteError {
|
||||
}
|
||||
|
||||
write_all func(output Writer, bytes []u8) void ! WriteError {
|
||||
offset usize = 0
|
||||
offset usize := 0
|
||||
while offset < bytes.len {
|
||||
count usize :: write(output, bytes[offset..]) catch |err| {
|
||||
return err
|
||||
@@ -111,7 +111,7 @@ stderr func(io Io) Writer {
|
||||
}
|
||||
|
||||
print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError {
|
||||
expand for parse_format(format.len, format, Args) |token| {
|
||||
inline for parse_format(format.len, format, Args) |token| {
|
||||
match token.kind {
|
||||
.unused: break
|
||||
.literal: try write_all(output, format[token.start..token.end])
|
||||
@@ -128,13 +128,13 @@ print func(output Writer, $format []u8, $Args type, args Args) void ! WriteError
|
||||
}
|
||||
}
|
||||
|
||||
hide write_integer_signed func(output Writer, value i64, base u64, uppercase bool) void ! WriteError {
|
||||
buffer [65]mut u8 = undefined
|
||||
end usize = buffer.len
|
||||
current i64 = value
|
||||
@hide write_integer_signed func(output Writer, value i64, base u64, uppercase bool) void ! WriteError {
|
||||
buffer [65]mut u8 := undefined
|
||||
end usize := buffer.len
|
||||
current i64 := value
|
||||
while true {
|
||||
digit_value i64 :: rem!(current, i64(base))
|
||||
digit u8 = 0
|
||||
digit u8 := 0
|
||||
if digit_value < 0 {
|
||||
digit = u8(-digit_value)
|
||||
} else {
|
||||
@@ -161,10 +161,10 @@ hide write_integer_signed func(output Writer, value i64, base u64, uppercase boo
|
||||
return
|
||||
}
|
||||
|
||||
hide write_integer_unsigned func(output Writer, value u64, base u64, uppercase bool) void ! WriteError {
|
||||
buffer [65]mut u8 = undefined
|
||||
end usize = buffer.len
|
||||
current u64 = value
|
||||
@hide write_integer_unsigned func(output Writer, value u64, base u64, uppercase bool) void ! WriteError {
|
||||
buffer [65]mut u8 := undefined
|
||||
end usize := buffer.len
|
||||
current u64 := value
|
||||
while true {
|
||||
digit u8 :: u8(rem!(current, base))
|
||||
end -= 1
|
||||
@@ -184,7 +184,7 @@ hide write_integer_unsigned func(output Writer, value u64, base u64, uppercase b
|
||||
return
|
||||
}
|
||||
|
||||
hide FormatTokenKind :: enum {
|
||||
@hide FormatTokenKind :: enum {
|
||||
unused
|
||||
literal
|
||||
default
|
||||
@@ -198,19 +198,19 @@ hide FormatTokenKind :: enum {
|
||||
scientific
|
||||
}
|
||||
|
||||
hide FormatToken :: struct {
|
||||
@hide FormatToken :: struct {
|
||||
kind FormatTokenKind
|
||||
start usize
|
||||
end usize
|
||||
field []u8
|
||||
}
|
||||
|
||||
hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
tokens [N]mut FormatToken = undefined
|
||||
@hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
tokens [N]mut FormatToken := undefined
|
||||
for (usize(0))..format.len |index| {
|
||||
tokens[index] = FormatToken {kind = .unused, start = 0, end = 0, field = ""}
|
||||
}
|
||||
field_count usize = 0
|
||||
field_count usize := 0
|
||||
match typeinfo!(Args) {
|
||||
.record |record|: {
|
||||
if !record.is_tuple {
|
||||
@@ -221,10 +221,10 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
else: compile_error!("io.print arguments must be a tuple")
|
||||
}
|
||||
|
||||
token_count usize = 0
|
||||
argument_count usize = 0
|
||||
literal_start usize = 0
|
||||
cursor usize = 0
|
||||
token_count usize := 0
|
||||
argument_count usize := 0
|
||||
literal_start usize := 0
|
||||
cursor usize := 0
|
||||
while cursor < format.len {
|
||||
byte :: format[cursor]
|
||||
if byte == '{' {
|
||||
@@ -243,8 +243,8 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
literal_start = cursor
|
||||
continue
|
||||
}
|
||||
kind FormatTokenKind = .default
|
||||
width usize = 2
|
||||
kind FormatTokenKind := .default
|
||||
width usize := 2
|
||||
if next != '}' {
|
||||
if cursor + 2 >= format.len or format[cursor + 2] != '}' {
|
||||
compile_error!("io.print format expects a one-character specifier")
|
||||
@@ -310,31 +310,51 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
return tokens
|
||||
}
|
||||
|
||||
hide format_field_name func($T type, index usize) []u8 {
|
||||
@hide format_field_name func($T type, index usize) []u8 {
|
||||
match typeinfo!(T) {
|
||||
.record |record|: return record.fields[index].name
|
||||
else: compile_error!("io.print arguments must be a tuple")
|
||||
}
|
||||
}
|
||||
@hide distinct_value func($Backing, $Distinct type, value Distinct) Backing {
|
||||
return ptrcast!(Backing, &value)^
|
||||
}
|
||||
|
||||
hide write_integer func(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
|
||||
@hide scalar_or_distinct_type func($T type) bool {
|
||||
match typeinfo!(T) {
|
||||
.bool: return true
|
||||
.integer: return true
|
||||
.float: return true
|
||||
.distinct: return true
|
||||
else: return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@hide write_integer func(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
|
||||
match typeinfo!(T) {
|
||||
.integer: if minval!(T) < 0 {
|
||||
try write_integer_signed(output, i64(value), base, uppercase)
|
||||
} else {
|
||||
try write_integer_unsigned(output, u64(value), base, uppercase)
|
||||
}
|
||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
||||
try write_integer(output, distinct_value(backing, T, value), base, uppercase)
|
||||
} else {
|
||||
compile_error!("io.print integer format requires an integer argument")
|
||||
}
|
||||
else: compile_error!("io.print integer format requires an integer argument")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
|
||||
hide write_float func(output Writer, $T type, value T, scientific bool) void ! WriteError {
|
||||
@hide write_float func(output Writer, $T type, value T, scientific bool) void ! WriteError {
|
||||
match typeinfo!(T) {
|
||||
.float: {
|
||||
buffer [64]mut u8 = undefined
|
||||
count c_int = 0
|
||||
buffer [64]mut u8 := undefined
|
||||
count c_int := 0
|
||||
if sizeof!(T) == 4 {
|
||||
if scientific {
|
||||
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value)
|
||||
@@ -351,35 +371,50 @@ hide write_float func(output Writer, $T type, value T, scientific bool) void ! W
|
||||
}
|
||||
try write_all(output, buffer[0..usize(count)])
|
||||
}
|
||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
||||
try write_float(output, distinct_value(backing, T, value), scientific)
|
||||
} else {
|
||||
compile_error!("io.print float format requires a float argument")
|
||||
}
|
||||
else: compile_error!("io.print float format requires a float argument")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
hide write_decimal func(output Writer, $T type, value T) void ! WriteError {
|
||||
@hide write_decimal func(output Writer, $T type, value T) void ! WriteError {
|
||||
match typeinfo!(T) {
|
||||
.integer: try write_integer(output, value, 10, false)
|
||||
.float: try write_float(output, value, false)
|
||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
||||
try write_decimal(output, distinct_value(backing, T, value))
|
||||
} else {
|
||||
compile_error!("io.print '{d}' requires an integer or float argument")
|
||||
}
|
||||
else: compile_error!("io.print '{d}' requires an integer or float argument")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
hide write_character func(output Writer, $T type, value T) void ! WriteError {
|
||||
@hide write_character func(output Writer, $T type, value T) void ! WriteError {
|
||||
match typeinfo!(T) {
|
||||
.integer: {
|
||||
if minval!(T) < 0 or maxval!(T) > 255 {
|
||||
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
||||
}
|
||||
buffer [1]u8 = [u8(value)]
|
||||
buffer [1]u8 := [u8(value)]
|
||||
try write_all(output, buffer[..])
|
||||
}
|
||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
||||
try write_character(output, distinct_value(backing, T, value))
|
||||
} else {
|
||||
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
||||
}
|
||||
else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
hide write_default func(output Writer, $T type, value T) void ! WriteError {
|
||||
@hide write_default func(output Writer, $T type, value T) void ! WriteError {
|
||||
match typeinfo!(T) {
|
||||
.bool: if value {
|
||||
try write_all(output, "true")
|
||||
@@ -392,7 +427,7 @@ hide write_default func(output Writer, $T type, value T) void ! WriteError {
|
||||
.pointer: try write_all(output, value)
|
||||
.slice: try write_all(output, value)
|
||||
.enum |enum_info|: {
|
||||
expand for enum_info.fields |field| {
|
||||
inline for enum_info.fields |field| {
|
||||
if value == field!(T, field.name) {
|
||||
try write_all(output, ".")
|
||||
try write_all(output, field.name)
|
||||
@@ -401,6 +436,11 @@ hide write_default func(output Writer, $T type, value T) void ! WriteError {
|
||||
}
|
||||
return .write_failed
|
||||
}
|
||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
||||
try write_default(output, distinct_value(backing, T, value))
|
||||
} else {
|
||||
compile_error!("io.print '{}' does not support this argument type")
|
||||
}
|
||||
else: compile_error!("io.print '{}' does not support this argument type")
|
||||
}
|
||||
return
|
||||
@@ -28,44 +28,25 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
||||
}
|
||||
|
||||
eql func($T type, left, right []T) bool {
|
||||
if left.len != right.len {
|
||||
if (left.len != right.len) return false
|
||||
for (0..left.len) |i| if (left[i] != right[i]) {
|
||||
return false
|
||||
}
|
||||
|
||||
i usize = 0
|
||||
while i < left.len : i += 1 {
|
||||
if left[i] != right[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
hide empty_storage [1]mut u64 = [0]
|
||||
|
||||
hide empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
empty func($T type) []mut T {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
|
||||
#! allocate memory for a slice of type `T` with `count` elements.
|
||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
if count == 0 {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
if (count == 0) return empty_slice(T, 0)
|
||||
|
||||
element_size usize :: sizeof!(T)
|
||||
if element_size == 0 {
|
||||
return empty_slice(T, count)
|
||||
}
|
||||
if (element_size == 0) return empty_slice(T, count)
|
||||
|
||||
if count > divtrunc!(maxval!(usize), element_size) {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
memory ?*mut u8 := raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..count]
|
||||
@@ -73,10 +54,14 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
#! reallocate memory for a slice of type `T` with `new_count` elements.
|
||||
#! reallocating with `new_count == 0` will free the memory and return an empty slice.
|
||||
#! note: memory must be reallocated with the same allocator that was used to allocate it.
|
||||
realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
|
||||
if new_count == memory.len {
|
||||
return memory
|
||||
}
|
||||
|
||||
if new_count == 0 {
|
||||
free(allocator, memory)
|
||||
return empty_slice(T, 0)
|
||||
@@ -90,13 +75,13 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
old_memory ?*mut u8 = none
|
||||
old_size usize = 0
|
||||
old_memory ?*mut u8 := null
|
||||
old_size usize := 0
|
||||
if memory.len != 0 {
|
||||
old_memory = ptrcast!(u8, memory.ptr)
|
||||
old_size = memory.len * element_size
|
||||
}
|
||||
resized ?*mut u8 = raw_realloc(
|
||||
resized ?*mut u8 := raw_realloc(
|
||||
allocator,
|
||||
old_memory,
|
||||
old_size,
|
||||
@@ -107,60 +92,68 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..new_count]
|
||||
}
|
||||
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
free func($T type, allocator Allocator, memory []mut T) void {
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
raw_free(allocator, ptrcast!(u8, memory.ptr), memory.len * sizeof!(T), alignof!(T))
|
||||
}
|
||||
#! free memory allocated for a slice of type `T`.
|
||||
#! note: memory must be freed with the same allocator that was used to allocate it.
|
||||
free func($T type, allocator Allocator, memory []T) void {
|
||||
if (memory.len == 0 or sizeof!(T) == 0) return
|
||||
raw_free(allocator, ptrcast!(
|
||||
u8,
|
||||
constcast!(memory).ptr),
|
||||
memory.len * sizeof!(T),
|
||||
alignof!(T),
|
||||
)
|
||||
}
|
||||
|
||||
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
|
||||
hide power_of_two func(value usize) bool {
|
||||
if value == 0 {
|
||||
return false
|
||||
#! get an empty slice of type `T` with `count` elements.
|
||||
empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
current usize = value
|
||||
#! get an empty slice of type `T` with 0 elements.
|
||||
empty func($T type) []mut T {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
|
||||
@hide empty_storage [1]mut u64 := [0]
|
||||
|
||||
@hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
|
||||
@hide power_of_two func(value usize) bool {
|
||||
if (value == 0) return false
|
||||
current usize := value
|
||||
while current > 1 {
|
||||
half usize = divtrunc!(current, 2)
|
||||
if half * 2 != current {
|
||||
return false
|
||||
}
|
||||
half usize := divtrunc!(current, 2)
|
||||
if (half * 2 != current) return false
|
||||
current = half
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
if power_of_two(alignment) == false {
|
||||
return none
|
||||
}
|
||||
@hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
if (power_of_two(alignment) == false) return null
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [none]
|
||||
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
if status != 0 {
|
||||
return none
|
||||
}
|
||||
memory [1]mut ?*mut anyopaque := [null]
|
||||
status c_int := c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
if (status != 0) return null
|
||||
|
||||
return ptrcast!(u8, memory[0])
|
||||
}
|
||||
|
||||
hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
if power_of_two(alignment) == false {
|
||||
return none
|
||||
}
|
||||
@hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
if (power_of_two(alignment) == false) return null
|
||||
|
||||
if new_size == 0 {
|
||||
c.free(memory)
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
if memory |old_memory| {
|
||||
@@ -168,35 +161,30 @@ hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 = c_alloc(none, new_size, alignment)
|
||||
new_memory ?*mut u8 := c_alloc(null, new_size, alignment)
|
||||
if new_memory |new_bytes| {
|
||||
copy_size usize = old_size
|
||||
if new_size < copy_size {
|
||||
copy_size = new_size
|
||||
}
|
||||
i usize = 0
|
||||
while i < copy_size : i += 1 {
|
||||
new_bytes[i] = old_memory[i]
|
||||
}
|
||||
copy_size usize := old_size
|
||||
if (new_size < copy_size) copy_size = new_size
|
||||
memcopy!(new_bytes[..copy_size], old_memory[..copy_size])
|
||||
c.free(old_memory)
|
||||
}
|
||||
return new_memory
|
||||
}
|
||||
|
||||
return c_alloc(none, new_size, alignment)
|
||||
return c_alloc(null, new_size, alignment)
|
||||
}
|
||||
|
||||
hide c_free func(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||
@hide c_free func(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||
c.free(memory)
|
||||
}
|
||||
|
||||
hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||
@hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||
alloc = c_alloc,
|
||||
realloc = c_realloc,
|
||||
free = c_free,
|
||||
}
|
||||
|
||||
c_allocator Allocator :: Allocator {
|
||||
context = none,
|
||||
context = null,
|
||||
vtable = &c_vtable,
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
Layout :: enum {
|
||||
auto
|
||||
c
|
||||
Layout :: enum { auto, c }
|
||||
|
||||
ArrayInfo :: struct {
|
||||
child type
|
||||
len usize
|
||||
}
|
||||
|
||||
FieldInfo :: struct {
|
||||
@@ -23,11 +25,12 @@ EnumInfo :: struct {
|
||||
TypeInfo :: union(enum) {
|
||||
invalid void
|
||||
void void
|
||||
noreturn void
|
||||
anyopaque void
|
||||
bool void
|
||||
integer void
|
||||
float void
|
||||
array void
|
||||
array ArrayInfo
|
||||
pointer void
|
||||
slice void
|
||||
range void
|
||||
@@ -37,16 +40,16 @@ TypeInfo :: union(enum) {
|
||||
record RecordInfo
|
||||
union void
|
||||
fallible void
|
||||
distinct void
|
||||
distinct type
|
||||
}
|
||||
|
||||
EnumFieldStruct func($E, $Field type, $default ?Field) type {
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: {
|
||||
names [field!(typeinfo!(E), "enum").fields.len]mut []u8 = undefined
|
||||
field_types [field!(typeinfo!(E), "enum").fields.len]mut type = undefined
|
||||
defaults [field!(typeinfo!(E), "enum").fields.len]mut ?Field = undefined
|
||||
expand for info.fields |field, index| {
|
||||
names [info.fields.len]mut []u8 := undefined
|
||||
field_types [info.fields.len]mut type := undefined
|
||||
defaults [info.fields.len]mut ?Field := undefined
|
||||
inline for info.fields |field, index| {
|
||||
names[index] = field.name
|
||||
field_types[index] = Field
|
||||
defaults[index] = default
|
||||
@@ -1,34 +0,0 @@
|
||||
testing :: import "@std/testing"
|
||||
|
||||
TestTokenKind :: enum(u8) {
|
||||
ident = 3
|
||||
int = 8
|
||||
eof = 21
|
||||
}
|
||||
|
||||
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(none))
|
||||
|
||||
enum_field_struct_defaults test {
|
||||
names TestNames = {
|
||||
ident = "identifier",
|
||||
int = "integer",
|
||||
}
|
||||
if field!(names, "ident") |value| {
|
||||
try testing.expect(value.len == 10)
|
||||
} else {
|
||||
try testing.expect(false)
|
||||
}
|
||||
if field!(names, "int") |value| {
|
||||
try testing.expect(value.len == 7)
|
||||
} else {
|
||||
try testing.expect(false)
|
||||
}
|
||||
if field!(names, "eof") |_| {
|
||||
try testing.expect(false)
|
||||
}
|
||||
|
||||
empty TestNames = {}
|
||||
if field!(empty, "ident") |_| {
|
||||
try testing.expect(false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
testing :: import "@std/testing"
|
||||
|
||||
TestTokenKind :: enum(u8) {
|
||||
ident = 3
|
||||
int = 8
|
||||
eof = 21
|
||||
}
|
||||
|
||||
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
|
||||
TestArrayAlias :: alias [3]u16
|
||||
TestInner :: distinct u16
|
||||
TestOuter :: distinct TestInner
|
||||
TestOuterAlias :: alias TestOuter
|
||||
|
||||
|
||||
@hide array_info_matches func($Array, $Child type, $len usize) bool {
|
||||
match typeinfo!(Array) {
|
||||
.array |info|: return info.child == Child and info.len == len
|
||||
else: return false
|
||||
}
|
||||
}
|
||||
@hide distinct_info_matches func($Distinct, $Backing type) bool {
|
||||
match typeinfo!(Distinct) {
|
||||
.distinct |backing|: return backing == Backing
|
||||
else: return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
array_reflection_exposes_child_and_logical_length test {
|
||||
try testing.expect($(array_info_matches([4]i32, i32, 4)))
|
||||
try testing.expect($(array_info_matches([0]bool, bool, 0)))
|
||||
try testing.expect($(array_info_matches(TestArrayAlias, u16, 3)))
|
||||
try testing.expect($(array_info_matches([2]mut i64, i64, 2)))
|
||||
try testing.expect($(array_info_matches([2;0]u8, u8, 2)))
|
||||
}
|
||||
distinct_reflection_exposes_immediate_backing test {
|
||||
try testing.expect($(distinct_info_matches(TestInner, u16)))
|
||||
try testing.expect($(distinct_info_matches(TestOuter, TestInner)))
|
||||
try testing.expect($(distinct_info_matches(TestOuterAlias, TestInner)))
|
||||
}
|
||||
|
||||
|
||||
enum_field_struct_defaults test {
|
||||
names TestNames := {
|
||||
ident = "identifier",
|
||||
int = "integer",
|
||||
}
|
||||
if field!(names, "ident") |value| {
|
||||
try testing.expect(value.len == 10)
|
||||
} else {
|
||||
try testing.expect(false)
|
||||
}
|
||||
if field!(names, "int") |value| {
|
||||
try testing.expect(value.len == 7)
|
||||
} else {
|
||||
try testing.expect(false)
|
||||
}
|
||||
if field!(names, "eof") |_| {
|
||||
try testing.expect(false)
|
||||
}
|
||||
|
||||
empty TestNames := {}
|
||||
if field!(empty, "ident") |_| {
|
||||
try testing.expect(false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import "@std/mem"
|
||||
|
||||
StaticStringMap func($V type) type {
|
||||
return struct {
|
||||
keys [][]u8
|
||||
values []V
|
||||
len_indexes []u32
|
||||
min_len u32
|
||||
max_len u32
|
||||
}
|
||||
}
|
||||
|
||||
@hide Pair func($V type) type {
|
||||
return struct { []u8, V }
|
||||
}
|
||||
|
||||
init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
||||
if N > usize(maxval!(u32)) {
|
||||
compile_error!("static string map has too many entries")
|
||||
}
|
||||
|
||||
keys [N]mut []u8 := undefined
|
||||
values [N]mut V := undefined
|
||||
|
||||
# assert no duplicate keys
|
||||
for entries |entry, i| {
|
||||
if entry.0.len > usize(maxval!(u32)) {
|
||||
compile_error!("static string map key is too long")
|
||||
}
|
||||
|
||||
for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) {
|
||||
compile_error!("duplicate static string map key")
|
||||
}
|
||||
|
||||
keys[i] = entry.0
|
||||
values[i] = entry.1
|
||||
}
|
||||
|
||||
if N == 0 {
|
||||
len_indexes [0]u32 := undefined
|
||||
return StaticStringMap(V){
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = 0,
|
||||
max_len = 0,
|
||||
}
|
||||
}
|
||||
|
||||
# fixme: insertion sort is compile-time O(N^2); replace if large maps affect builds
|
||||
for 1..N |i| {
|
||||
key :: keys[i]
|
||||
value :: values[i]
|
||||
j usize := i
|
||||
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||
keys[j] = keys[j - 1]
|
||||
values[j] = values[j - 1]
|
||||
}
|
||||
keys[j] = key
|
||||
values[j] = value
|
||||
}
|
||||
|
||||
min_len u32 :: u32(keys[0].len)
|
||||
max_len u32 :: u32(keys[N - 1].len)
|
||||
len_indexes [usize(max_len) + 1]mut u32 := undefined
|
||||
entry_index usize := 0
|
||||
for 0..=usize(max_len) |length| {
|
||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||
len_indexes[length] = u32(entry_index)
|
||||
}
|
||||
|
||||
return StaticStringMap(V) {
|
||||
keys = keys[..],
|
||||
values = values[..],
|
||||
len_indexes = len_indexes[..],
|
||||
min_len = min_len,
|
||||
max_len = max_len,
|
||||
}
|
||||
}
|
||||
|
||||
get func($V type, map @StaticStringMap(V), key []u8) ?V {
|
||||
if (map.keys.len == 0 or key.len > maxval!(u32)) return null
|
||||
|
||||
length u32 := u32(key.len)
|
||||
if (length < map.min_len or length > map.max_len) return null
|
||||
|
||||
idx usize := usize(map.len_indexes[usize(length)])
|
||||
while idx < map.keys.len : idx += 1 {
|
||||
candidate :: map.keys[idx]
|
||||
if (candidate.len != key.len) return null
|
||||
if mem.eql(u8, candidate, key) return map.values[idx]
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import "io"
|
||||
import "arraylist"
|
||||
|
||||
Io :: alias io.Io
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import "io"
|
||||
import "enums"
|
||||
import "hashmap"
|
||||
import "arraylist"
|
||||
import "static_string_map"
|
||||
|
||||
Io :: alias io.Io
|
||||
EnumMap :: alias enums.EnumMap
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
StringHashMap :: alias hashmap.StringHashMap
|
||||
StaticStringMap :: alias static_string_map.StaticStringMap
|
||||
@@ -1,42 +0,0 @@
|
||||
import "@std/debug"
|
||||
|
||||
Error :: enum {
|
||||
expectation_failed
|
||||
}
|
||||
|
||||
SourceLocation :: struct {
|
||||
file []u8
|
||||
line usize
|
||||
column usize
|
||||
}
|
||||
|
||||
expect func(condition bool, location SourceLocation) void ! Error {
|
||||
if !condition {
|
||||
debug.print("{s}:{d}:{d}: expectation failed\n", {location.file, location.line, location.column})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
|
||||
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
|
||||
if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
|
||||
expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error {
|
||||
try expect($(Expected == Actual), location)
|
||||
}
|
||||
|
||||
run func(name []u8, callback *func() void ! Error) bool {
|
||||
callback() catch |_| {
|
||||
debug.print("{s} [failed]\n", {name,})
|
||||
return false
|
||||
}
|
||||
debug.print("{s} [ok]\n", {name,})
|
||||
return true
|
||||
}
|
||||
|
||||
summary func(passed, failed i32) void {
|
||||
debug.print("{d} passed, {d} failed\n", {passed, failed})
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import "@std/debug"
|
||||
import "@std/mem"
|
||||
|
||||
Error :: enum {
|
||||
expectation_failed
|
||||
}
|
||||
|
||||
SourceLocation :: struct {
|
||||
file []u8
|
||||
line usize
|
||||
column usize
|
||||
}
|
||||
|
||||
expect func(condition bool, location SourceLocation) void ! Error {
|
||||
if !condition {
|
||||
debug.print("{s}:{d}:{d}: expectation failed\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
|
||||
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
|
||||
match typeinfo!(T) {
|
||||
.optional: {
|
||||
if expected |expected_value| {
|
||||
if actual |actual_value| {
|
||||
try expect_equal(expected_value, actual_value, location)
|
||||
return
|
||||
}
|
||||
debug.print("{s}:{d}:{d}: expected an optional value, found null\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
if actual |_| {
|
||||
debug.print("{s}:{d}:{d}: expected null, found an optional value\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
.slice: if !mem.eql(expected, actual) {
|
||||
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
else: if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {
|
||||
location.file,
|
||||
location.line,
|
||||
location.column,
|
||||
expected,
|
||||
actual,
|
||||
})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error {
|
||||
try expect($(Expected == Actual), location)
|
||||
}
|
||||
|
||||
run func(name []u8, callback *func() void ! Error) bool {
|
||||
callback() catch |_| {
|
||||
debug.print("{s}...[\x1b[91mfailure\x1b[0m]\n", {name,})
|
||||
return false
|
||||
}
|
||||
debug.print("{s}...[\x1b[92mok\x1b[0m]\n", {name,})
|
||||
return true
|
||||
}
|
||||
|
||||
summary func(passed, failed i32) void {
|
||||
debug.print("{d} passed, {d} failed\n", {passed, failed})
|
||||
}
|
||||
+13
-13
@@ -38,7 +38,7 @@ Ball :: struct {
|
||||
}
|
||||
|
||||
# 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 none).
|
||||
# the data that action needs (or `void` when it needs null).
|
||||
Command :: union(enum) {
|
||||
spawn Vector2 # spawn a shape at this point
|
||||
push struct { dx f32, dy f32 } # blow every shape this way
|
||||
@@ -77,8 +77,8 @@ read_command func() Command {
|
||||
if IsMouseButtonPressed(MOUSE_BUTTON_LEFT) return .spawn{ GetMousePosition() }
|
||||
if IsKeyPressed(KEY_SPACE) return .clear
|
||||
|
||||
fx f32 = 0.0
|
||||
fy f32 = 0.0
|
||||
fx f32 := 0.0
|
||||
fy f32 := 0.0
|
||||
if IsKeyDown(KEY_A) fx -= FORCE
|
||||
if IsKeyDown(KEY_D) fx += FORCE
|
||||
if IsKeyDown(KEY_W) fy -= FORCE
|
||||
@@ -128,14 +128,14 @@ step func(b @mut Ball) void {
|
||||
|
||||
draw_ball func(b @Ball, highlight bool) void {
|
||||
col :: color_for(b.kind)
|
||||
center Vector2 = Vector2{ x = b.x, y = b.y }
|
||||
center Vector2 := Vector2{ x = b.x, y = b.y }
|
||||
match b.kind {
|
||||
.circle: DrawCircleV(center, b.radius, col)
|
||||
.square: DrawPoly(center, 4, b.radius, 45.0, col)
|
||||
.triangle: DrawPoly(center, 3, b.radius, 0.0, col)
|
||||
}
|
||||
if highlight {
|
||||
ring Color = Color{ r = 250, g = 245, b = 200, a = 255 }
|
||||
ring Color := Color{ r = 250, g = 245, b = 200, a = 255 }
|
||||
DrawPoly(center, 24, b.radius + RING_PAD, 0.0, ring)
|
||||
}
|
||||
}
|
||||
@@ -150,11 +150,11 @@ main func() i32 {
|
||||
`[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
|
||||
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 :: Color{ r = 24, g = 26, b = 34, a = 255 }
|
||||
text :: Color{ r = 225, g = 225, b = 230, a = 255 }
|
||||
@@ -209,9 +209,9 @@ main func() i32 {
|
||||
# --- which shape is under the cursor? (optional via a value-loop) ---
|
||||
mouse :: GetMousePosition()
|
||||
sel :: for 0..(count) |i| blk: {
|
||||
c Vector2 = Vector2{ x = balls[i].x, y = balls[i].y }
|
||||
c Vector2 := Vector2{ x = balls[i].x, y = balls[i].y }
|
||||
if CheckCollisionPointCircle(mouse, c, balls[i].radius) yield :blk i
|
||||
yield none
|
||||
yield null
|
||||
}
|
||||
|
||||
# --- draw -----------------------------------------------------------
|
||||
@@ -220,7 +220,7 @@ main func() i32 {
|
||||
|
||||
for (&balls) |@b, i| {
|
||||
if (i >= count) break
|
||||
hot bool = false
|
||||
hot bool := false
|
||||
if sel |s| {
|
||||
if (s == i) hot = true # true only for the hovered ball
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@ Token :: struct {
|
||||
kind Kind
|
||||
}
|
||||
|
||||
hide is_alpha func(value u8) bool {
|
||||
@hide is_alpha func(value u8) bool {
|
||||
return value == '_' or
|
||||
value >= 'a' and value <= 'z' or
|
||||
value >= 'A' and value <= 'Z'
|
||||
}
|
||||
|
||||
hide is_digit func(value u8) bool {
|
||||
@hide is_digit func(value u8) bool {
|
||||
return value >= '0' and value <= '9'
|
||||
}
|
||||
|
||||
hide word_kind func(word []u8) Kind {
|
||||
@hide word_kind func(word []u8) Kind {
|
||||
# ponytail: enough keywords for the demo; add the full language set when a parser needs it.
|
||||
if mem.eql(word, "func") or mem.eql(word, "void") {
|
||||
return .keyword
|
||||
@@ -38,7 +38,7 @@ hide word_kind func(word []u8) Kind {
|
||||
return .identifier
|
||||
}
|
||||
|
||||
hide append_token func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError {
|
||||
@hide append_token func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError {
|
||||
try arraylist.append(tokens, Token {
|
||||
start = start,
|
||||
length = end - start,
|
||||
@@ -48,7 +48,7 @@ hide append_token func(tokens @mut std.ArrayList(Token), kind Kind, start, end u
|
||||
}
|
||||
|
||||
lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
||||
cursor usize = 0
|
||||
cursor usize := 0
|
||||
while cursor < source.len {
|
||||
value u8 :: source[cursor]
|
||||
if value == ' ' or value == '\t' or value == '\r' {
|
||||
@@ -95,7 +95,7 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
|
||||
return
|
||||
}
|
||||
|
||||
hide kind_name func(kind Kind) *c_char {
|
||||
@hide kind_name func(kind Kind) *c_char {
|
||||
return match kind {
|
||||
.invalid: "invalid"
|
||||
.eof: "eof"
|
||||
@@ -108,11 +108,11 @@ hide kind_name func(kind Kind) *c_char {
|
||||
}
|
||||
}
|
||||
|
||||
hide print_token func(source []u8, token Token) void {
|
||||
@hide print_token func(source []u8, token Token) void {
|
||||
_ = c.printf("%-11s", kind_name(token.kind))
|
||||
if token.length != 0 {
|
||||
_ = c.printf(" `")
|
||||
i usize = 0
|
||||
i usize := 0
|
||||
while i < token.length : i += 1 {
|
||||
value u8 :: source[token.start + i]
|
||||
if value == '\n' {
|
||||
@@ -132,7 +132,7 @@ main func() i32 {
|
||||
` hello()
|
||||
`}
|
||||
|
||||
tokens std.ArrayList(Token) = arraylist.init(mem.c_allocator)
|
||||
tokens std.ArrayList(Token) := arraylist.init(mem.c_allocator)
|
||||
defer arraylist.deinit(&tokens)
|
||||
|
||||
lex(source, &tokens) catch |_| {
|
||||
|
||||
@@ -28,7 +28,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem
|
||||
return
|
||||
}
|
||||
|
||||
new_capacity usize = 8
|
||||
new_capacity usize := 8
|
||||
if list.capacity >= 8 {
|
||||
half usize :: divtrunc!(list.capacity, 2)
|
||||
if list.capacity > maxval!(usize) - half {
|
||||
|
||||
@@ -61,7 +61,7 @@ write func(writer Writer, bytes []u8) usize ! WriteError {
|
||||
}
|
||||
|
||||
write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
offset usize = 0
|
||||
offset usize := 0
|
||||
while offset < bytes.len {
|
||||
count usize :: write(writer, bytes[offset..]) catch |err| {
|
||||
return err
|
||||
@@ -74,8 +74,8 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
return
|
||||
}
|
||||
|
||||
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||
request usize = buffer.len
|
||||
@hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||
request usize := buffer.len
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
@@ -91,9 +91,9 @@ hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usi
|
||||
}
|
||||
}
|
||||
|
||||
hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||
@hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError {
|
||||
fd c_int :: c_int(stream)
|
||||
request usize = bytes.len
|
||||
request usize := bytes.len
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
if request > maximum {
|
||||
request = maximum
|
||||
@@ -109,14 +109,14 @@ hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize
|
||||
}
|
||||
}
|
||||
|
||||
hide system_vtable IoVTable :: IoVTable {
|
||||
@hide system_vtable IoVTable :: IoVTable {
|
||||
read = system_read,
|
||||
write = system_write,
|
||||
}
|
||||
|
||||
hide system func() Io {
|
||||
@hide system func() Io {
|
||||
return Io {
|
||||
context = none,
|
||||
context = null,
|
||||
vtable = &system_vtable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ eql func($T type, left, right []T) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
i usize = 0
|
||||
i usize := 0
|
||||
while i < left.len : i += 1 {
|
||||
if left[i] != right[i] {
|
||||
return false
|
||||
@@ -41,9 +41,9 @@ eql func($T type, left, right []T) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
hide empty_storage [1]mut u64 = [0]
|
||||
@hide empty_storage [1]mut u64 := [0]
|
||||
|
||||
hide empty_slice func($T type, count usize) []mut T {
|
||||
@hide empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
@@ -65,7 +65,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
memory ?*mut u8 = raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
memory ?*mut u8 := raw_alloc(allocator, count * element_size, alignof!(T))
|
||||
if memory |bytes| {
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..count]
|
||||
@@ -90,13 +90,13 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
old_memory ?*mut u8 = none
|
||||
old_size usize = 0
|
||||
old_memory ?*mut u8 := null
|
||||
old_size usize := 0
|
||||
if memory.len != 0 {
|
||||
old_memory = ptrcast!(u8, memory.ptr)
|
||||
old_size = memory.len * element_size
|
||||
}
|
||||
resized ?*mut u8 = raw_realloc(
|
||||
resized ?*mut u8 := raw_realloc(
|
||||
allocator,
|
||||
old_memory,
|
||||
old_size,
|
||||
@@ -116,16 +116,16 @@ free func($T type, allocator Allocator, memory []mut T) void {
|
||||
}
|
||||
}
|
||||
|
||||
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
@hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
|
||||
hide power_of_two func(value usize) bool {
|
||||
@hide power_of_two func(value usize) bool {
|
||||
if value == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
current usize = value
|
||||
current usize := value
|
||||
while current > 1 {
|
||||
half usize = divtrunc!(current, 2)
|
||||
half usize := divtrunc!(current, 2)
|
||||
if half * 2 != current {
|
||||
return false
|
||||
}
|
||||
@@ -135,32 +135,32 @@ hide power_of_two func(value usize) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
@hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
if power_of_two(alignment) == false {
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
return ptrcast!(u8, c.malloc(c_ulong(size)))
|
||||
}
|
||||
|
||||
memory [1]mut ?*mut anyopaque = [none]
|
||||
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
memory [1]mut ?*mut anyopaque := [null]
|
||||
status c_int := c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||
if status != 0 {
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
return ptrcast!(u8, memory[0])
|
||||
}
|
||||
|
||||
hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
@hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
||||
if power_of_two(alignment) == false {
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
if new_size == 0 {
|
||||
c.free(memory)
|
||||
return none
|
||||
return null
|
||||
}
|
||||
|
||||
if memory |old_memory| {
|
||||
@@ -168,13 +168,13 @@ hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
return ptrcast!(u8, c.realloc(old_memory, c_ulong(new_size)))
|
||||
}
|
||||
|
||||
new_memory ?*mut u8 = c_alloc(none, new_size, alignment)
|
||||
new_memory ?*mut u8 := c_alloc(null, new_size, alignment)
|
||||
if new_memory |new_bytes| {
|
||||
copy_size usize = old_size
|
||||
copy_size usize := old_size
|
||||
if new_size < copy_size {
|
||||
copy_size = new_size
|
||||
}
|
||||
i usize = 0
|
||||
i usize := 0
|
||||
while i < copy_size : i += 1 {
|
||||
new_bytes[i] = old_memory[i]
|
||||
}
|
||||
@@ -183,20 +183,20 @@ hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
return new_memory
|
||||
}
|
||||
|
||||
return c_alloc(none, new_size, alignment)
|
||||
return c_alloc(null, new_size, alignment)
|
||||
}
|
||||
|
||||
hide c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||
@hide c_free func(_ ?@mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
|
||||
c.free(memory)
|
||||
}
|
||||
|
||||
hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||
@hide c_vtable AllocatorVTable :: AllocatorVTable {
|
||||
alloc = c_alloc,
|
||||
realloc = c_realloc,
|
||||
free = c_free,
|
||||
}
|
||||
|
||||
c_allocator Allocator :: Allocator {
|
||||
context = none,
|
||||
context = null,
|
||||
vtable = &c_vtable,
|
||||
}
|
||||
|
||||
+4
-4
@@ -25,7 +25,7 @@ tier_bonus func(tier Tier) i32 {
|
||||
}
|
||||
|
||||
projected_score func(player Player) i32 {
|
||||
total i32 = player.score
|
||||
total i32 := player.score
|
||||
total += tier_bonus(player.tier)
|
||||
if player.active and player.streak > 2 {
|
||||
total += player.streak * 3
|
||||
@@ -42,8 +42,8 @@ apply_decay func(players []mut Player) void {
|
||||
}
|
||||
|
||||
best_player func(players []mut Player) ?@mut Player {
|
||||
best ?@mut Player = none
|
||||
best_score i32 = 0
|
||||
best ?@mut Player := null
|
||||
best_score i32 := 0
|
||||
|
||||
for players |@player| {
|
||||
score :: projected_score(player^)
|
||||
@@ -62,7 +62,7 @@ best_player func(players []mut Player) ?@mut Player {
|
||||
}
|
||||
|
||||
main func() i32 {
|
||||
players [4]mut Player = [
|
||||
players [4]mut Player := [
|
||||
Player { id = PlayerID(1), name = "Ada", tier = .gold, score = 41, streak = 4, active = true },
|
||||
Player { id = PlayerID(2), name = "Ken", tier = .silver, score = 56, streak = 1, active = true },
|
||||
Player { id = PlayerID(3), name = "Edsger", tier = .bronze, score = 64, streak = 0, active = false },
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user