Compare commits
9 Commits
729488e702
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 378eb637c6 | |||
| c2343b54bb | |||
| fa53ca2219 | |||
| ba6052eef3 | |||
| 5244bfbf1b | |||
| 8785912b73 | |||
| a7a32894f3 | |||
| 572ffe7d07 | |||
| 6688822de2 |
+16
-13
@@ -16,8 +16,9 @@ roadmap and milestone history.
|
||||
- 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 package-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
|
||||
@@ -36,15 +37,16 @@ roadmap and milestone history.
|
||||
- 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
|
||||
- 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, plus raw backtick multiline strings
|
||||
- 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 `null`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
|
||||
- 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
|
||||
- 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)`
|
||||
- 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, 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
|
||||
|
||||
@@ -61,8 +63,9 @@ nominal result type: checked integer `+`, `-`, `*`, unary `-`, bitwise operators
|
||||
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 also work as indices and slice bounds;
|
||||
`minval!` / `maxval!` return the distinct type. Runtime and comptime behavior match.
|
||||
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.
|
||||
@@ -115,9 +118,9 @@ 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
|
||||
- `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
|
||||
@@ -152,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
|
||||
|
||||
@@ -263,7 +266,7 @@ exactly once. Bare functions named `memcopy` or `memset` remain ordinary user fu
|
||||
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
|
||||
- `std/meta` reflection records plus `EnumFieldStruct(E, Field, default ?Field)`, implemented with `struct_type!`; it produces a record with one field per native enum member in declaration order, where outer `null` means no field default
|
||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, recursively scalar-backed distinct values, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
||||
- 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
|
||||
|
||||
@@ -208,14 +208,18 @@ 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 package; sibling files can use it, but importing packages
|
||||
cannot. 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:
|
||||
@@ -230,7 +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/indexing, reflection, and recursive standard formatting
|
||||
- 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
|
||||
|
||||
+10
-10
@@ -202,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.
|
||||
@@ -243,12 +242,13 @@ Function :: struct {
|
||||
generated: bool,
|
||||
analysis_root: bool,
|
||||
test: bool,
|
||||
package_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,
|
||||
@@ -265,7 +265,7 @@ Global :: struct {
|
||||
immutable: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
package_hidden: bool,
|
||||
visibility: types.Visibility,
|
||||
expr: Expr_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
@@ -313,7 +313,7 @@ Declaration_Alias :: struct {
|
||||
target_pkg: Package_Id,
|
||||
target: u32,
|
||||
kind: Declaration_Alias_Kind,
|
||||
package_hidden: bool,
|
||||
visibility: types.Visibility,
|
||||
valid: bool,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
|
||||
+497
-228
File diff suppressed because it is too large
Load Diff
+181
-66
@@ -291,6 +291,7 @@ Ct_Binding :: struct {
|
||||
value: Ct_Value_Id,
|
||||
cell: Ct_Cell_Id,
|
||||
mutable: bool,
|
||||
open_integer: bool,
|
||||
}
|
||||
|
||||
Ct_Error_Refinement :: struct {
|
||||
@@ -473,9 +474,23 @@ ct_extend_place :: proc(
|
||||
return ct_add_place(state, base.cell, value_type, writable, extended[:])
|
||||
}
|
||||
|
||||
ct_bind_value :: proc(state: ^Ct_State, name: symbol.Id, value_type: types.Type, value: Ct_Value_Id, mutable: bool) {
|
||||
ct_bind_value :: proc(
|
||||
state: ^Ct_State,
|
||||
name: symbol.Id,
|
||||
value_type: types.Type,
|
||||
value: Ct_Value_Id,
|
||||
mutable: bool,
|
||||
open_integer := false,
|
||||
) {
|
||||
cell := ct_add_cell(state, value, mutable)
|
||||
append(&state.bindings, Ct_Binding{name=name, type=value_type, value=value, cell=cell, mutable=mutable})
|
||||
append(&state.bindings, Ct_Binding{
|
||||
name=name,
|
||||
type=value_type,
|
||||
value=value,
|
||||
cell=cell,
|
||||
mutable=mutable,
|
||||
open_integer=open_integer,
|
||||
})
|
||||
}
|
||||
|
||||
ct_pop_bindings :: proc(state: ^Ct_State, start: int) {
|
||||
@@ -581,6 +596,32 @@ ct_binding_value :: proc(state: ^Ct_State, index: int) -> Ct_Value_Id {
|
||||
return binding.value
|
||||
}
|
||||
|
||||
ct_contextualize_open_integer_binding :: proc(state: ^Ct_State, index: int, expected: types.Type) {
|
||||
if expected != types.USIZE || index < 0 || index >= len(state.bindings) ||
|
||||
!state.bindings[index].open_integer {
|
||||
return
|
||||
}
|
||||
value_id := ct_binding_value(state, index)
|
||||
if value_id == INVALID_CT_VALUE || int(value_id) >= len(state.values) {
|
||||
return
|
||||
}
|
||||
value := state.values[value_id]
|
||||
if value.kind != .Integer ||
|
||||
!fits_integer_type(value.integer, expected, state.checker.target) {
|
||||
return
|
||||
}
|
||||
value.type = expected
|
||||
contextual := ct_add_value(state, value)
|
||||
binding := &state.bindings[index]
|
||||
binding.type = expected
|
||||
binding.value = contextual
|
||||
binding.open_integer = false
|
||||
if binding.cell != INVALID_CT_CELL && int(binding.cell) < len(state.cells) &&
|
||||
state.cells[binding.cell].live {
|
||||
state.cells[binding.cell].value = contextual
|
||||
}
|
||||
}
|
||||
|
||||
ct_binding_place :: proc(state: ^Ct_State, index: int) -> Ct_Place_Id {
|
||||
if index < 0 || index >= len(state.bindings) {
|
||||
return INVALID_CT_PLACE
|
||||
@@ -1315,6 +1356,7 @@ ct_eval_expr :: proc(
|
||||
case .Name:
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if index, ok := ct_find_binding_index(state, expr.name); ok {
|
||||
ct_contextualize_open_integer_binding(state, index, expected)
|
||||
return ct_observe_value(state, ct_binding_value(state, index), expr.span)
|
||||
}
|
||||
if value, ok := current_comptime_value(checker, expr.name); ok {
|
||||
@@ -1452,16 +1494,19 @@ ct_eval_expr :: proc(
|
||||
}
|
||||
return ct_eval_field_value(state, base_id, expr.name, expr.span)
|
||||
case .Index:
|
||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
||||
if !index_ok || index_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, index_flow, index_ok
|
||||
}
|
||||
index_type := state.values[index_id].type
|
||||
index_representation := types.runtime_representation(index_type, store)
|
||||
index_literal := is_numeric_constant_expr(checker, expr.right)
|
||||
if !types.is_concrete_integer(index_representation) ||
|
||||
!index_literal && !can_implicitly_convert_type(checker, index_representation, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be coercible to usize")
|
||||
if !types.equal(index_type, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
checker.ast_module.exprs[expr.right].span,
|
||||
"index must have type usize, got %s",
|
||||
type_label(checker, index_type),
|
||||
)
|
||||
}
|
||||
index_value, index_is_int := ct_integer_value(state, index_id)
|
||||
if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) {
|
||||
@@ -1962,16 +2007,19 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
if bound == ast.INVALID_EXPR {
|
||||
continue
|
||||
}
|
||||
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.INVALID, depth+1)
|
||||
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1)
|
||||
if !bound_ok || bound_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, bound_flow, bound_ok
|
||||
}
|
||||
bound_type := state.values[value].type
|
||||
bound_representation := types.runtime_representation(bound_type, store)
|
||||
bound_literal := is_numeric_constant_expr(checker, bound)
|
||||
if !types.is_concrete_integer(bound_representation) ||
|
||||
!bound_literal && !can_implicitly_convert_type(checker, bound_representation, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be coercible to usize")
|
||||
if !types.equal(bound_type, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
checker.ast_module.exprs[bound].span,
|
||||
"slice bound must have type usize, got %s",
|
||||
type_label(checker, bound_type),
|
||||
)
|
||||
}
|
||||
integer, integer_ok := ct_integer_value(state, value)
|
||||
if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) {
|
||||
@@ -2352,6 +2400,16 @@ ct_eval_place :: proc(
|
||||
if !index_ok || index_flow.kind != .Normal {
|
||||
return INVALID_CT_PLACE, types.INVALID, false, index_flow, index_ok
|
||||
}
|
||||
index_type := state.values[index_value].type
|
||||
if !types.equal(index_type, types.USIZE) {
|
||||
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
checker.ast_module.exprs[expr.right].span,
|
||||
"index must have type usize, got %s",
|
||||
type_label(checker, index_type),
|
||||
)
|
||||
}
|
||||
index_int, int_ok := ct_integer_value(state, index_value)
|
||||
if !int_ok || index_int < 0 || index_int > i128(0x7fff_ffff) {
|
||||
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer")
|
||||
@@ -4983,6 +5041,11 @@ ct_exec_statements :: proc(
|
||||
} else {
|
||||
declared := type_from_syntax(checker, statement.type, state.pkg, state.file, active_state=state)
|
||||
expected := declared if types.is_valid(declared) && !types.is_void(declared) else types.INVALID
|
||||
open_integer := false
|
||||
if !is_runtime_type(checker, declared) {
|
||||
constant := eval_integer_constant_in_context(checker, statement.expr, state.pkg, state.file)
|
||||
open_integer = constant.kind == .Value
|
||||
}
|
||||
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, expected, depth+1)
|
||||
ok = expr_ok
|
||||
flow = expr_flow
|
||||
@@ -4991,7 +5054,14 @@ ct_exec_statements :: proc(
|
||||
value, ok = ct_coerce_value(state, value, expected, statement.span)
|
||||
}
|
||||
if ok && statement.name != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable)
|
||||
ct_bind_value(
|
||||
state,
|
||||
statement.name,
|
||||
state.values[value].type,
|
||||
value,
|
||||
!statement.immutable,
|
||||
open_integer=open_integer,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5258,6 +5328,70 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_eval_conditional_unwrap :: proc(
|
||||
state: ^Ct_State,
|
||||
statement: ast.Stmt,
|
||||
keyword: string,
|
||||
depth: int,
|
||||
) -> (matched: bool, scope_start: int, flow: Ct_Flow, ok: bool) {
|
||||
checker := state.checker
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
defer delete(operands)
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||
if len(operands) != len(statement.captures) {
|
||||
return false, len(state.bindings), ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'%s' unwrap capture count mismatch",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
scope_start = len(state.bindings)
|
||||
matched = true
|
||||
for operand, index in operands {
|
||||
value, operand_flow, operand_ok := ct_eval_expr(state, operand, types.INVALID, depth+1)
|
||||
if !operand_ok || operand_flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, operand_flow, operand_ok
|
||||
}
|
||||
v := state.values[value]
|
||||
if v.kind == .Null {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
if v.kind != .Optional_Some {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'%s' unwrap requires an optional value",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
children := ct_child_slice(state, v)
|
||||
if len(children) > 0 && statement.captures[index] != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.captures[index], state.values[children[0]].type, children[0], false)
|
||||
}
|
||||
}
|
||||
if matched && statement.guard != ast.INVALID_EXPR {
|
||||
guard, guard_flow, guard_ok := ct_eval_expr(state, statement.guard, types.BOOL, depth+1)
|
||||
if !guard_ok || guard_flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, guard_flow, guard_ok
|
||||
}
|
||||
guard_value, bool_ok := ct_bool_value(state, guard)
|
||||
if !bool_ok {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return false, scope_start, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, statement.span,
|
||||
"'%s' unwrap guard must be a bool",
|
||||
keyword,
|
||||
)
|
||||
}
|
||||
matched = guard_value
|
||||
}
|
||||
return matched, scope_start, ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if yield_returns && statement.else_body == nil {
|
||||
@@ -5284,47 +5418,9 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d
|
||||
}
|
||||
return ct_exec_statements(state, body, false, depth+1)
|
||||
}
|
||||
operands: [dynamic]ast.Expr_Id
|
||||
operands.allocator = checker.allocator
|
||||
defer delete(operands)
|
||||
flatten_conditional_unwrap_operands(checker.ast_module, statement.expr, &operands)
|
||||
if len(operands) != len(statement.captures) {
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap capture count mismatch")
|
||||
}
|
||||
scope_start := len(state.bindings)
|
||||
matched := true
|
||||
for operand, index in operands {
|
||||
value, flow, ok := ct_eval_expr(state, operand, types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return flow, ok
|
||||
}
|
||||
v := state.values[value]
|
||||
if v.kind == .Null {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
if v.kind != .Optional_Some {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap requires an optional value")
|
||||
}
|
||||
children := ct_child_slice(state, v)
|
||||
if len(children) > 0 && statement.captures[index] != checker.sink_symbol {
|
||||
ct_bind_value(state, statement.captures[index], state.values[children[0]].type, children[0], false)
|
||||
}
|
||||
}
|
||||
if matched && statement.guard != ast.INVALID_EXPR {
|
||||
guard, flow, ok := ct_eval_expr(state, statement.guard, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return flow, ok
|
||||
}
|
||||
guard_value, guard_ok := ct_bool_value(state, guard)
|
||||
if !guard_ok {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' unwrap guard must be a bool")
|
||||
}
|
||||
matched = guard_value
|
||||
matched, scope_start, unwrap_flow, unwrap_ok := ct_eval_conditional_unwrap(state, statement, "if", depth)
|
||||
if !unwrap_ok || unwrap_flow.kind != .Normal {
|
||||
return unwrap_flow, unwrap_ok
|
||||
}
|
||||
body := statement.body if matched else statement.else_body
|
||||
flow := ct_flow(.Normal)
|
||||
@@ -5357,34 +5453,53 @@ ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
||||
if !ct_step(state, statement.span) {
|
||||
return ct_flow(.Normal), false
|
||||
}
|
||||
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
}
|
||||
value, bool_ok := ct_bool_value(state, condition)
|
||||
if !bool_ok {
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool")
|
||||
}
|
||||
if !value {
|
||||
return ct_flow(.Normal), true
|
||||
scope_start := len(state.bindings)
|
||||
if len(statement.captures) > 0 {
|
||||
matched, unwrap_scope_start, flow, ok :=
|
||||
ct_eval_conditional_unwrap(state, statement, "while", depth)
|
||||
scope_start = unwrap_scope_start
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
}
|
||||
if !matched {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
} else {
|
||||
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
}
|
||||
value, bool_ok := ct_bool_value(state, condition)
|
||||
if !bool_ok {
|
||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool")
|
||||
}
|
||||
if !value {
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
}
|
||||
body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
||||
if !body_ok {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return body_flow, false
|
||||
}
|
||||
if ct_loop_consumes_flow(body_flow, statement.label, false) {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return ct_flow(.Normal), true
|
||||
}
|
||||
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, true) {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return body_flow, true
|
||||
}
|
||||
if statement.update != ast.INVALID_STMT {
|
||||
update := [1]ast.Stmt_Id{statement.update}
|
||||
update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1)
|
||||
if !update_ok || update_flow.kind != .Normal {
|
||||
ct_pop_bindings(state, scope_start)
|
||||
return update_flow, update_ok
|
||||
}
|
||||
}
|
||||
ct_pop_bindings(state, scope_start)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,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.
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -26,7 +30,6 @@ 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
|
||||
@@ -320,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
|
||||
}
|
||||
|
||||
+38
-5
@@ -1117,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)
|
||||
@@ -1134,7 +1143,7 @@ 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 .Null:
|
||||
item, ok := types.node(&emitter.module.types, instruction.type)
|
||||
@@ -1334,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:
|
||||
@@ -2593,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")
|
||||
}
|
||||
|
||||
+40
-19
@@ -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 {
|
||||
if function.pkg == pkg && function.name == name &&
|
||||
declaration_visible_in_file(function.visibility, function.file, file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for global in module.globals {
|
||||
if global.pkg == pkg && global.name == name {
|
||||
if global.pkg == pkg && global.name == name &&
|
||||
declaration_visible_in_file(global.visibility, global.file, file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1327,7 +1342,9 @@ find_visible_enum_global :: proc(
|
||||
public_only := false,
|
||||
) -> ast.Global_Id {
|
||||
for global, index in module.globals {
|
||||
if global.pkg == pkg && global.name == name && (!global.package_hidden || !public_only) {
|
||||
if global.pkg == pkg && global.name == name &&
|
||||
((public_only && global.visibility == .Public) ||
|
||||
(!public_only && declaration_visible_in_file(global.visibility, global.file, file))) {
|
||||
return ast.global_id(index)
|
||||
}
|
||||
}
|
||||
@@ -1451,17 +1468,20 @@ resolve_enum_values :: proc(state: ^State) {
|
||||
|
||||
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 {
|
||||
if function.pkg == alias.pkg && function.name == alias.name &&
|
||||
declaration_scopes_overlap(function.visibility, function.file, alias.visibility, alias.file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for global in module.globals {
|
||||
if global.pkg == alias.pkg && global.name == alias.name {
|
||||
if global.pkg == alias.pkg && global.name == alias.name &&
|
||||
declaration_scopes_overlap(global.visibility, global.file, alias.visibility, alias.file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for item in module.type_store.nodes {
|
||||
if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) {
|
||||
if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) &&
|
||||
declaration_scopes_overlap(item.visibility, ast.File_Id(item.file), alias.visibility, alias.file) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1473,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.package_hidden {
|
||||
if function.pkg == pkg && function.name == name && !function.generated && function.visibility == .Public {
|
||||
kind = .Function
|
||||
target = u32(ast.function_id(index))
|
||||
kinds += 1
|
||||
@@ -1481,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.package_hidden {
|
||||
if global.pkg == pkg && global.name == name && global.visibility == .Public {
|
||||
kind = .Global
|
||||
target = u32(ast.global_id(index))
|
||||
kinds += 1
|
||||
@@ -1498,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.package_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.package_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.package_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.package_hidden {
|
||||
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility != .Public {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1524,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.package_hidden {
|
||||
if alias.valid && alias.pkg == pkg && alias.name == name && alias.visibility == .Public {
|
||||
return index
|
||||
}
|
||||
}
|
||||
@@ -1581,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 package-hidden",
|
||||
"package member '%s.%s' is not public",
|
||||
symbol.resolve(state.symbols, alias.qualifier),
|
||||
symbol.resolve(state.symbols, alias.member),
|
||||
)
|
||||
@@ -1611,7 +1631,8 @@ validate_declaration_aliases :: proc(state: ^State) {
|
||||
continue
|
||||
}
|
||||
for previous in state.module.aliases[:index] {
|
||||
if previous.pkg == alias.pkg && previous.name == alias.name {
|
||||
if previous.pkg == alias.pkg && previous.name == alias.name &&
|
||||
declaration_scopes_overlap(previous.visibility, previous.file, alias.visibility, alias.file) {
|
||||
alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name)
|
||||
alias.valid = false
|
||||
break
|
||||
@@ -1661,9 +1682,9 @@ validate_declaration_aliases :: proc(state: ^State) {
|
||||
u32(alias.pkg),
|
||||
u32(alias.name),
|
||||
file=u32(alias.file),
|
||||
package_hidden=alias.package_hidden,
|
||||
visibility=alias.visibility,
|
||||
)
|
||||
if !types.define_alias(&state.module.type_store, id, types.Type(alias.target), alias.package_hidden) {
|
||||
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
|
||||
}
|
||||
|
||||
+235
-140
@@ -924,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 {
|
||||
@@ -1136,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{
|
||||
@@ -1282,12 +1287,16 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
condition := lower_expr(state, statement.expr)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br, span=statement.span, type=types.VOID,
|
||||
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if len(statement.unwraps) > 0 {
|
||||
lower_conditional_unwrap_header(state, statement, body_lbl, exit_lbl)
|
||||
} else {
|
||||
condition := lower_expr(state, statement.expr)
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Cond_Br, span=statement.span, type=types.VOID,
|
||||
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
@@ -1752,41 +1761,49 @@ 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))
|
||||
if !main_ok || !provider_ok {
|
||||
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
|
||||
provider_call := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Call,
|
||||
type=hir_module.functions[provider_index].result,
|
||||
target=ir.function_ref(ir.Function_Id(provider_index)),
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
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
|
||||
args: []ir.Instruction_Id
|
||||
if param_index >= 0 {
|
||||
provider_call := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Call,
|
||||
type=hir_module.functions[provider_index].result,
|
||||
target=ir.function_ref(ir.Function_Id(provider_index)),
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
init_args := make([]ir.Instruction_Id, 1, allocator)
|
||||
init_args[0] = provider_call
|
||||
init_value := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Aggregate,
|
||||
type=main_function.locals[param_index].type,
|
||||
args=init_args,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
args = make([]ir.Instruction_Id, 1, allocator)
|
||||
args[0] = init_value
|
||||
}
|
||||
init_args := make([]ir.Instruction_Id, 1, allocator)
|
||||
init_args[0] = provider_call
|
||||
init_value := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Aggregate,
|
||||
type=main_function.locals[param_index].type,
|
||||
args=init_args,
|
||||
target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION,
|
||||
b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
args := make([]ir.Instruction_Id, 1, allocator)
|
||||
args[0] = init_value
|
||||
main_call := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Call,
|
||||
@@ -1797,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,
|
||||
})
|
||||
} else {
|
||||
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=.Store, type=main_function.result, target=ir.INVALID_REF,
|
||||
a=channel_slot, b=main_call, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
code := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Union_Tag, type=types.U16, target=ir.INVALID_REF,
|
||||
a=channel_slot, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
zero_tag := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Const, type=types.U16, integer=0, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
ok := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Compare, type=types.BOOL, integer=i64(ir.Compare_Predicate.Eq),
|
||||
target=ir.INVALID_REF, a=code, b=zero_tag,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Cond_Br, type=types.VOID, integer=1, target=ir.Ref(0), a=ok,
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Label, type=types.VOID, integer=0, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
failure := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Const, type=types.I32, integer=1, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Return, type=types.I32, target=ir.INVALID_REF, a=failure,
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Label, type=types.VOID, integer=1, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
success_value := ir.INVALID_INSTRUCTION
|
||||
if types.is_void(success) {
|
||||
success_value = ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Const, type=types.I32, integer=0, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
payload := ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Field_Address, type=success, integer=0, target=ir.INVALID_REF,
|
||||
a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
success_value = ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Load, type=success, target=ir.INVALID_REF, a=payload,
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Return, type=types.I32, target=ir.INVALID_REF, a=success_value,
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
exit_value := main_call
|
||||
if types.is_void(main_function.result) {
|
||||
exit_value = ir.instruction_id(len(instructions))
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Const, type=types.I32, integer=0, target=ir.INVALID_REF,
|
||||
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
append(&instructions, ir.Instruction{
|
||||
op=.Return, type=types.I32, target=ir.INVALID_REF, a=exit_value,
|
||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
problematic := main_function.problematic
|
||||
if provider_ok {
|
||||
problematic = problematic || hir_module.functions[provider_index].problematic
|
||||
}
|
||||
append(&module.functions, ir.Function{
|
||||
link_name=fmt.aprintf("main", allocator=allocator),
|
||||
calling_convention=.C,
|
||||
implementation=.Definition,
|
||||
linkage=.External,
|
||||
is_main=true,
|
||||
result=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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+180
-82
@@ -31,7 +31,12 @@ Parser :: struct {
|
||||
capture_pipe: bool,
|
||||
// A parenthesized `if` condition ends before a leading-dot brace-less body.
|
||||
if_condition: bool,
|
||||
hidden_names: [dynamic]symbol.Id,
|
||||
local_names: [dynamic]Local_Name,
|
||||
}
|
||||
|
||||
Local_Name :: struct {
|
||||
name: symbol.Id,
|
||||
visibility: types.Visibility,
|
||||
}
|
||||
|
||||
MAX_EXPRESSION_NESTING :: 256
|
||||
@@ -43,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)]
|
||||
}
|
||||
|
||||
package_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 {
|
||||
@@ -60,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:
|
||||
}
|
||||
@@ -186,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 {
|
||||
@@ -403,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 {
|
||||
@@ -437,7 +480,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
u32(name.symbol),
|
||||
u32(qualifier),
|
||||
u32(parser.file),
|
||||
!symbol.is_valid(qualifier) && package_hidden_name(parser, name.symbol),
|
||||
types.Visibility.Public if symbol.is_valid(qualifier) else declaration_visibility(parser, name.symbol),
|
||||
)
|
||||
if token_text(parser, name) == "struct_type" &&
|
||||
current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
|
||||
@@ -530,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
|
||||
@@ -2180,19 +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
|
||||
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
|
||||
parse_conditional_captures :: proc(parser: ^Parser) -> ([]symbol.Id, ast.Expr_Id) {
|
||||
captures: [dynamic]symbol.Id
|
||||
captures.allocator = parser.module.allocator
|
||||
guard := ast.INVALID_EXPR
|
||||
@@ -2218,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
|
||||
@@ -2231,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,
|
||||
@@ -2559,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
|
||||
@@ -2582,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,
|
||||
@@ -2589,7 +2659,7 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
return id
|
||||
}
|
||||
|
||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, package_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'")
|
||||
@@ -2600,10 +2670,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, package_hidden
|
||||
}
|
||||
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 {
|
||||
@@ -2620,12 +2687,13 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, package_hidden
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=c_abi,
|
||||
package_hidden=package_hidden,
|
||||
visibility=visibility,
|
||||
has_body=false,
|
||||
variadic=variadic,
|
||||
params=params,
|
||||
result=result,
|
||||
error=error_type,
|
||||
infer_error=infer_error,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return
|
||||
@@ -2639,12 +2707,13 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, package_hidden
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=c_abi,
|
||||
package_hidden=package_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,
|
||||
})
|
||||
@@ -2661,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)
|
||||
}
|
||||
@@ -2687,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,
|
||||
})
|
||||
@@ -2825,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, package_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), package_hidden=package_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
|
||||
@@ -2861,7 +2928,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, package_hidde
|
||||
"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, package_hidden=package_hidden) {
|
||||
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 {
|
||||
@@ -2898,7 +2965,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, package_hidde
|
||||
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, package_hidden=package_hidden) {
|
||||
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 {
|
||||
@@ -2917,10 +2984,10 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, package_hidde
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
parse_opaque :: proc(parser: ^Parser, name: token.Token, package_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), package_hidden=package_hidden)
|
||||
if !types.define_record(&parser.module.type_store, id, nil, false, true, false, package_hidden=package_hidden) {
|
||||
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 {
|
||||
@@ -2945,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, package_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), package_hidden=package_hidden)
|
||||
if !types.define_distinct(&parser.module.type_store, id, child, package_hidden) {
|
||||
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) {
|
||||
@@ -2958,7 +3025,7 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token, package_hidden: bool)
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
|
||||
parse_alias :: proc(parser: ^Parser, name: token.Token, package_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 {
|
||||
@@ -2975,7 +3042,7 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) {
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
target_pkg=ast.INVALID_PACKAGE,
|
||||
package_hidden=package_hidden,
|
||||
visibility=visibility,
|
||||
valid=true,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
@@ -2986,8 +3053,8 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, package_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), package_hidden=package_hidden)
|
||||
if !types.define_alias(&parser.module.type_store, id, child, package_hidden) {
|
||||
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) {
|
||||
@@ -3130,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, package_hidden: bool) {
|
||||
parse_enum :: proc(parser: ^Parser, name: token.Token, visibility: types.Visibility) {
|
||||
start := advance(parser)
|
||||
explicit_backing := false
|
||||
backing := types.INVALID
|
||||
@@ -3152,8 +3219,8 @@ parse_enum :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) {
|
||||
_ = finish_statement(parser)
|
||||
return
|
||||
}
|
||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), package_hidden=package_hidden)
|
||||
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing, package_hidden) {
|
||||
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 {
|
||||
@@ -3186,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:
|
||||
}
|
||||
}
|
||||
@@ -3282,10 +3352,38 @@ parse_test :: proc(parser: ^Parser, name: token.Token) {
|
||||
}
|
||||
|
||||
parse_top_level :: proc(parser: ^Parser) {
|
||||
hide_token, package_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 package_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'
|
||||
@@ -3293,15 +3391,15 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
return
|
||||
}
|
||||
if current(parser).kind == .Keyword_Import {
|
||||
if package_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 package_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)
|
||||
@@ -3311,8 +3409,8 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
}
|
||||
name := advance(parser)
|
||||
if current(parser).kind == .Keyword_Test {
|
||||
if package_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
|
||||
@@ -3322,8 +3420,8 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
advance(parser)
|
||||
skip_newlines(parser)
|
||||
if current(parser).kind == .Keyword_Import {
|
||||
if package_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)
|
||||
@@ -3333,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, package_hidden)
|
||||
parse_function(parser, name, c_abi, visibility)
|
||||
return
|
||||
}
|
||||
type_syntax := types.INVALID
|
||||
@@ -3362,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, package_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, package_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, package_hidden)
|
||||
parse_opaque(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union {
|
||||
parse_struct(parser, name, false, package_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, package_hidden)
|
||||
parse_enum(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
|
||||
parse_distinct(parser, name, package_hidden)
|
||||
parse_distinct(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias {
|
||||
parse_alias(parser, name, package_hidden)
|
||||
parse_alias(parser, name, visibility)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3398,7 +3496,7 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
name=name.symbol,
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
package_hidden=package_hidden,
|
||||
visibility=visibility,
|
||||
type=type_syntax,
|
||||
immutable=operator.kind == .Colon_Colon,
|
||||
expr=expr,
|
||||
@@ -3419,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)
|
||||
@@ -3446,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)
|
||||
|
||||
@@ -175,7 +175,7 @@ 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")
|
||||
|
||||
@@ -72,7 +72,6 @@ Kind :: enum u8 {
|
||||
Keyword_Distinct,
|
||||
Keyword_Alias,
|
||||
Keyword_Import,
|
||||
Keyword_Hide,
|
||||
Keyword_Return,
|
||||
Keyword_Try,
|
||||
Keyword_Catch,
|
||||
|
||||
+100
-23
@@ -56,6 +56,12 @@ Numeric_Category :: enum u8 {
|
||||
Float,
|
||||
}
|
||||
|
||||
Visibility :: enum u8 {
|
||||
Public,
|
||||
Package,
|
||||
File,
|
||||
}
|
||||
|
||||
Kind :: enum u8 {
|
||||
Invalid,
|
||||
Void,
|
||||
@@ -109,7 +115,7 @@ Node :: struct {
|
||||
tuple: bool,
|
||||
opaque: bool,
|
||||
declared: bool,
|
||||
package_hidden: bool,
|
||||
visibility: Visibility,
|
||||
explicit_backing: bool,
|
||||
}
|
||||
|
||||
@@ -193,13 +199,21 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
|
||||
return id
|
||||
}
|
||||
|
||||
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff, package_hidden := false) -> Type {
|
||||
normalized_file := file if qualifier != 0 else u32(0)
|
||||
named :: proc(
|
||||
store: ^Store,
|
||||
pkg, name: u32,
|
||||
qualifier: u32 = 0,
|
||||
file: u32 = 0xffff_ffff,
|
||||
visibility := Visibility.Public,
|
||||
) -> Type {
|
||||
normalized_file := file if qualifier != 0 || visibility == .File else u32(0)
|
||||
for existing, index in store.nodes {
|
||||
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
|
||||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
|
||||
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
|
||||
(qualifier == 0 || existing.file == normalized_file) {
|
||||
((qualifier != 0 && existing.file == normalized_file) ||
|
||||
(qualifier == 0 && visibility == .File && existing.visibility == .File && existing.file == normalized_file) ||
|
||||
(qualifier == 0 && visibility != .File && existing.visibility != .File)) {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
}
|
||||
@@ -209,22 +223,41 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf
|
||||
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 &&
|
||||
(!existing.package_hidden || file != 0xffff_ffff) {
|
||||
return DYNAMIC_START+Type(index)
|
||||
if (existing.kind != .Named && existing.kind != .Alias && existing.kind != .Distinct &&
|
||||
existing.kind != .Enum && existing.kind != .Struct && existing.kind != .Union) ||
|
||||
existing.pkg != pkg || existing.name != name || existing.qualifier != qualifier {
|
||||
continue
|
||||
}
|
||||
if qualifier != 0 {
|
||||
if existing.file == file {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch existing.visibility {
|
||||
case .Public:
|
||||
fallback = DYNAMIC_START+Type(index)
|
||||
case .Package:
|
||||
if file != 0xffff_ffff {
|
||||
fallback = DYNAMIC_START+Type(index)
|
||||
}
|
||||
case .File:
|
||||
if existing.file == file {
|
||||
return DYNAMIC_START+Type(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
return INVALID
|
||||
return fallback
|
||||
}
|
||||
|
||||
define_alias :: proc(store: ^Store, id, child: Type, package_hidden := false) -> 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
|
||||
@@ -232,12 +265,12 @@ define_alias :: proc(store: ^Store, id, child: Type, package_hidden := false) ->
|
||||
index := int(id-DYNAMIC_START)
|
||||
store.nodes[index].kind = .Alias
|
||||
store.nodes[index].child = child
|
||||
store.nodes[index].package_hidden = package_hidden
|
||||
store.nodes[index].visibility = visibility
|
||||
store.nodes[index].declared = true
|
||||
return true
|
||||
}
|
||||
|
||||
define_distinct :: proc(store: ^Store, id, child: Type, package_hidden := false) -> 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
|
||||
@@ -245,12 +278,12 @@ define_distinct :: proc(store: ^Store, id, child: Type, package_hidden := false)
|
||||
index := int(id-DYNAMIC_START)
|
||||
store.nodes[index].kind = .Distinct
|
||||
store.nodes[index].child = child
|
||||
store.nodes[index].package_hidden = package_hidden
|
||||
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, package_hidden := false) -> 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
|
||||
@@ -261,7 +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].package_hidden = package_hidden
|
||||
store.nodes[index].visibility = visibility
|
||||
store.nodes[index].declared = true
|
||||
append(&store.enum_members, ..members)
|
||||
return true
|
||||
@@ -278,7 +311,7 @@ define_record :: proc(
|
||||
tag: Type = INVALID,
|
||||
declared_tag: Type = INVALID,
|
||||
tuple := false,
|
||||
package_hidden := false,
|
||||
visibility := Visibility.Public,
|
||||
) -> bool {
|
||||
existing, ok := node(store, id)
|
||||
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
|
||||
@@ -290,7 +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].package_hidden = package_hidden
|
||||
store.nodes[index].visibility = visibility
|
||||
store.nodes[index].declared = true
|
||||
store.nodes[index].explicit_size = explicit_size
|
||||
store.nodes[index].explicit_alignment = explicit_alignment
|
||||
@@ -636,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 {
|
||||
@@ -1709,11 +1779,18 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||
}
|
||||
offset: u64
|
||||
max_align: u64 = 1
|
||||
for field in fields_for(store, value) {
|
||||
field_align := u64(alignment_of(field.type, store, selected))
|
||||
offset = (offset+field_align-1)/field_align*field_align
|
||||
offset += size(field.type, store, selected)
|
||||
max_align = max(max_align, field_align)
|
||||
if !item.c_layout {
|
||||
for field in fields_for(store, value) {
|
||||
offset += size(field.type, store, selected)
|
||||
max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
|
||||
}
|
||||
} else {
|
||||
for field in fields_for(store, value) {
|
||||
field_align := u64(alignment_of(field.type, store, selected))
|
||||
offset = (offset+field_align-1)/field_align*field_align
|
||||
offset += size(field.type, store, selected)
|
||||
max_align = max(max_align, field_align)
|
||||
}
|
||||
}
|
||||
return (offset+max_align-1)/max_align*max_align
|
||||
case .Union:
|
||||
|
||||
+428
-30
@@ -1257,6 +1257,29 @@ main func() void {}
|
||||
testing.expect_value(t, module.imports[2].path, "dir\"name\\tail")
|
||||
}
|
||||
|
||||
@(test)
|
||||
honey_hex_byte_string_escapes_decode_to_bytes :: proc(t: ^testing.T) {
|
||||
text := `value :: "\x1b[31m\x00\xFf"
|
||||
main func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.hon", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, len(module.strings), 1)
|
||||
testing.expect_value(t, len(module.strings[0]), 7)
|
||||
testing.expect_value(t, module.strings[0][0], byte(0x1b))
|
||||
testing.expect_value(t, module.strings[0][5], byte(0))
|
||||
testing.expect_value(t, module.strings[0][6], byte(0xff))
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_accepts_chained_field_access :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
@@ -1278,7 +1301,7 @@ parser_accepts_chained_field_access :: proc(t: ^testing.T) {
|
||||
|
||||
@(test)
|
||||
lexer_diagnoses_invalid_import_strings :: proc(t: ^testing.T) {
|
||||
text := "import \"bad\\q\"\nimport \"unterminated\n"
|
||||
text := "import \"bad\\q\"\nimport \"bad\\xg0\"\nimport \"bad\\x1\"\nimport \"unterminated\n"
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
@@ -1287,7 +1310,7 @@ lexer_diagnoses_invalid_import_strings :: proc(t: ^testing.T) {
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 2)
|
||||
testing.expect_value(t, len(diagnostics.items), 4)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -1867,6 +1890,92 @@ many_item_pointer_slices_require_end_bound :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, end_bound_errors, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
indices_and_slice_bounds_require_usize :: proc(t: ^testing.T) {
|
||||
text := `Index :: distinct u32
|
||||
main func() void {
|
||||
values [2]i32 := [1, 2]
|
||||
index := 1
|
||||
start := 0
|
||||
end := 1
|
||||
small u32 := 0
|
||||
id Index := Index(0)
|
||||
_ = values[index]
|
||||
_ = values[start..end]
|
||||
_ = values[small]
|
||||
_ = values[id]
|
||||
_ = values[small..]
|
||||
_ = values[..id]
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
index_errors, bound_errors := 0, 0
|
||||
for diagnostic in diagnostics.items {
|
||||
index_errors += 1 if strings.contains(diagnostic.message, "index must have type usize") else 0
|
||||
bound_errors += 1 if strings.contains(diagnostic.message, "slice bound must have type usize") else 0
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 4)
|
||||
testing.expect_value(t, index_errors, 2)
|
||||
testing.expect_value(t, bound_errors, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
comptime_indices_infer_usize_and_reject_explicit_integer_types :: proc(t: ^testing.T) {
|
||||
text := `good func($items [2]u8) u8 {
|
||||
start := 0
|
||||
end := 1
|
||||
part :: items[start..end]
|
||||
return part[start]
|
||||
}
|
||||
bad_index func($items [2]u8) u8 {
|
||||
cursor u32 := 0
|
||||
return items[cursor]
|
||||
}
|
||||
bad_bound func($items [2]u8) u8 {
|
||||
start u32 := 0
|
||||
return items[start..][0]
|
||||
}
|
||||
GOOD :: $good([7, 8])
|
||||
BAD_INDEX :: $bad_index([7, 8])
|
||||
BAD_BOUND :: $bad_bound([7, 8])
|
||||
main func() void {
|
||||
_ = GOOD
|
||||
_ = BAD_INDEX
|
||||
_ = BAD_BOUND
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 2)
|
||||
index_error, bound_error := false, false
|
||||
for diagnostic in diagnostics.items {
|
||||
index_error = index_error || strings.contains(diagnostic.message, "index must have type usize, got u32")
|
||||
bound_error = bound_error || strings.contains(diagnostic.message, "slice bound must have type usize, got u32")
|
||||
}
|
||||
testing.expect(t, index_error && bound_error)
|
||||
}
|
||||
|
||||
@(test)
|
||||
layout_builtins_compile_and_run :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-layout-builtins"
|
||||
@@ -1887,6 +1996,18 @@ Bool_Last :: struct {
|
||||
flag bool
|
||||
}
|
||||
|
||||
Optimized :: struct {
|
||||
prefix u8
|
||||
wide u64
|
||||
suffix u8
|
||||
}
|
||||
|
||||
Source_Order_C :: c_struct {
|
||||
prefix c_uchar
|
||||
wide c_ulong
|
||||
suffix c_uchar
|
||||
}
|
||||
|
||||
Opaque :: opaque
|
||||
Color :: enum {
|
||||
red
|
||||
@@ -1937,12 +2058,19 @@ main func() i32 {
|
||||
if (sizeof!(bool) != 1) return 15
|
||||
if (sizeof!(Bool_First) != 2) return 16
|
||||
if (sizeof!(Bool_Last) != 2) return 17
|
||||
optimized Optimized := Optimized{prefix = 40, wide = 72623859790382856, suffix = 41}
|
||||
if (sizeof!(Optimized) != 16) return 18
|
||||
if (alignof!(Optimized) != 8) return 19
|
||||
if (sizeof!(Source_Order_C) != 24) return 20
|
||||
if (optimized.prefix != 40) return 21
|
||||
if (optimized.wide != 72623859790382856) return 22
|
||||
if (optimized.suffix != 41) return 23
|
||||
first :: make_bool_first()
|
||||
if (!first.flag) return 18
|
||||
if (first.byte != 41) return 19
|
||||
if (!first.flag) return 24
|
||||
if (first.byte != 41) return 25
|
||||
last :: make_bool_last()
|
||||
if (!last.flag) return 20
|
||||
if (last.byte != 42) return 21
|
||||
if (!last.flag) return 26
|
||||
if (last.byte != 42) return 27
|
||||
return 0
|
||||
}
|
||||
`
|
||||
@@ -3110,6 +3238,200 @@ bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) {
|
||||
testing.expect(t, !strings.contains(llvm_text, "declare i32 @main()"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
fallible_main_maps_success_and_error_channels_to_exit_codes :: proc(t: ^testing.T) {
|
||||
Case :: struct {
|
||||
name: string,
|
||||
source: string,
|
||||
exit_code: int,
|
||||
}
|
||||
cases := [?]Case{
|
||||
{
|
||||
name="void-success",
|
||||
source=`Error :: enum { failed }
|
||||
succeed func() void ! Error {}
|
||||
main func() void ! Error { try succeed() }
|
||||
`,
|
||||
exit_code=0,
|
||||
},
|
||||
{
|
||||
name="void-error",
|
||||
source=`Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
main func() void ! Error { try fail() }
|
||||
`,
|
||||
exit_code=1,
|
||||
},
|
||||
{
|
||||
name="int-success",
|
||||
source=`Error :: enum { failed }
|
||||
value func() i32 ! Error { return 7 }
|
||||
main func() int ! Error { return try value() }
|
||||
`,
|
||||
exit_code=7,
|
||||
},
|
||||
{
|
||||
name="process-error",
|
||||
source=`process :: import "@std/process"
|
||||
Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
main func(_ process.Init) void ! Error { try fail() }
|
||||
`,
|
||||
exit_code=1,
|
||||
},
|
||||
{
|
||||
name="inferred-main-try",
|
||||
source=`Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
main func() void! { try fail() }
|
||||
`,
|
||||
exit_code=1,
|
||||
},
|
||||
{
|
||||
name="inferred-main-typed-return",
|
||||
source=`Error :: enum { failed }
|
||||
main func() void! {
|
||||
err Error := .failed
|
||||
return err
|
||||
}
|
||||
`,
|
||||
exit_code=1,
|
||||
},
|
||||
{
|
||||
name="inferred-hidden-sum",
|
||||
source=`A :: enum { a }
|
||||
B :: enum { b }
|
||||
fail_a func() void ! A { return .a }
|
||||
fail_b func() void ! B { return .b }
|
||||
@hide dispatch func(selector i32) void! {
|
||||
if selector == 1 {
|
||||
try fail_a()
|
||||
return
|
||||
}
|
||||
try fail_b()
|
||||
}
|
||||
main func() void! { try dispatch(2) }
|
||||
`,
|
||||
exit_code=1,
|
||||
},
|
||||
{
|
||||
name="inferred-process-main",
|
||||
source=`process :: import "@std/process"
|
||||
Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
main func(_ process.Init) void! { try fail() }
|
||||
`,
|
||||
exit_code=1,
|
||||
},
|
||||
}
|
||||
for test_case in cases {
|
||||
directory := fmt.tprintf("/tmp/brolang-test-fallible-main-%s", test_case.name)
|
||||
output := fmt.tprintf("%s/app", directory)
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(
|
||||
fmt.tprintf("%s/main.bro", directory),
|
||||
transmute([]byte)test_case.source,
|
||||
))
|
||||
status := compiler_core.compile_package(
|
||||
directory,
|
||||
output,
|
||||
nil,
|
||||
target.DEFAULT,
|
||||
cimport.Options{},
|
||||
".",
|
||||
)
|
||||
testing.expect_value(t, status, 0)
|
||||
state, stdout, stderr, _ := os2.process_exec(
|
||||
os2.Process_Desc{command=[]string{output}},
|
||||
context.allocator,
|
||||
)
|
||||
defer delete(stdout)
|
||||
defer delete(stderr)
|
||||
testing.expect_value(t, state.exit_code, test_case.exit_code)
|
||||
testing.expect_value(t, len(stdout), 0)
|
||||
testing.expect_value(t, len(stderr), 0)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
inferred_error_channels_reject_unstable_or_untyped_contracts :: proc(t: ^testing.T) {
|
||||
Case :: struct {
|
||||
source: string,
|
||||
message: string,
|
||||
absent: string,
|
||||
}
|
||||
cases := [?]Case{
|
||||
{
|
||||
source=`Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
visible func() void! { try fail() }
|
||||
main func() void {}
|
||||
`,
|
||||
message="inferred error channels are only allowed on local functions and root main",
|
||||
},
|
||||
{
|
||||
source=`Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
@hide untyped func(flag bool) void! {
|
||||
if flag {
|
||||
try fail()
|
||||
return
|
||||
}
|
||||
return .failed
|
||||
}
|
||||
main func() void { untyped(false) catch |_| {} }
|
||||
`,
|
||||
message="inferred error returns require a concretely typed error value",
|
||||
},
|
||||
{
|
||||
source=`@hide empty func() void! {}
|
||||
main func() void { empty() catch |_| {} }
|
||||
`,
|
||||
message="could not infer a named error channel for 'empty'",
|
||||
},
|
||||
{
|
||||
source=`Error :: enum { failed }
|
||||
fail func() void ! Error { return .failed }
|
||||
value func() i32 { return 1 }
|
||||
main func() void! {
|
||||
fail() catch |_| {
|
||||
return
|
||||
}
|
||||
_ = try value()
|
||||
}
|
||||
`,
|
||||
message="could not infer a named error channel for 'main'",
|
||||
absent="non-void function must return a value",
|
||||
},
|
||||
}
|
||||
for test_case in cases {
|
||||
source_file := source.Source{path="test.bro", text=test_case.source}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
symbols := symbol.init_table()
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
|
||||
found := false
|
||||
unexpected := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, test_case.message)
|
||||
unexpected = unexpected ||
|
||||
len(test_case.absent) > 0 && strings.contains(diagnostic.message, test_case.absent)
|
||||
}
|
||||
testing.expect(t, found)
|
||||
testing.expect(t, !unexpected)
|
||||
|
||||
hir.destroy_module(&hir_module)
|
||||
ast.destroy_module(&ast_module)
|
||||
delete(stream.items)
|
||||
symbol.destroy_table(&symbols)
|
||||
source.destroy_diagnostics(&diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
@@ -3364,11 +3686,11 @@ system func() Io { return Io {value = 0} }
|
||||
process_source=`io :: import "@std/io"
|
||||
Init :: struct { io io.Io }
|
||||
`,
|
||||
message="does not provide the required 'hide system func() Io'",
|
||||
message="does not provide the required '@hide system func() Io'",
|
||||
},
|
||||
{
|
||||
io_source=`Io :: struct { value i32 }
|
||||
hide system func() Io { return Io {value = 0} }
|
||||
@hide system func() Io { return Io {value = 0} }
|
||||
`,
|
||||
process_source=`io :: import "@std/io"
|
||||
Init :: struct { io io.Io, extra i32 }
|
||||
@@ -5894,7 +6216,7 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||
}
|
||||
cases := [?]Case{
|
||||
{"examples/programs/index_signed_error", "/tmp/brolang-test-index-signed-error"},
|
||||
{"examples/programs/index_int_constraint_error", "/tmp/brolang-test-index-int-constraint-error"},
|
||||
{"examples/programs/index_unsigned_error", "/tmp/brolang-test-index-unsigned-error"},
|
||||
{"examples/programs/scalar_cast_error", "/tmp/brolang-test-scalar-cast-error"},
|
||||
{"examples/programs/array_const_size_error", "/tmp/brolang-test-array-const-size-error"},
|
||||
}
|
||||
@@ -10151,14 +10473,18 @@ imports_are_file_local :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
@(test)
|
||||
hide_is_rejected_outside_named_top_level_declarations :: proc(t: ^testing.T) {
|
||||
visibility_modifiers_are_rejected_outside_named_top_level_declarations :: proc(t: ^testing.T) {
|
||||
cases := [?]string{
|
||||
`hide import "../dep"`,
|
||||
`hide dep :: import "../dep"`,
|
||||
`hide func() void {}`,
|
||||
`main func(hide value i32) void {}`,
|
||||
`Box :: struct { hide i32 }`,
|
||||
`main func() void { hide value i32 := 1 }`,
|
||||
`@hide import "../dep"`,
|
||||
`@hide:file dep :: import "../dep"`,
|
||||
`@hide func() void {}`,
|
||||
`main func(@hide value i32) void {}`,
|
||||
`Box :: struct { @hide i32 }`,
|
||||
`main func() void { @hide value i32 := 1 }`,
|
||||
`@hide: value :: 1`,
|
||||
`@hide:module value :: 1`,
|
||||
`@hide:"file" value :: 1`,
|
||||
`@hide(file) value :: 1`,
|
||||
}
|
||||
for text in cases {
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -10176,17 +10502,17 @@ hide_is_rejected_outside_named_top_level_declarations :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
@(test)
|
||||
hide_declarations_are_package_hidden :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-hidden-declarations"
|
||||
local_declarations_resolve_at_their_declared_scope :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-local-declarations"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/packages/hidden_valid/app", output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 9)
|
||||
testing.expect_value(t, state.exit_code, 15)
|
||||
}
|
||||
|
||||
@(test)
|
||||
package_hidden_declarations_resolve_locally_but_not_through_imports :: proc(t: ^testing.T) {
|
||||
package_and_file_local_visibility_is_enforced :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
@@ -10204,18 +10530,27 @@ package_hidden_declarations_resolve_locally_but_not_through_imports :: proc(t: ^
|
||||
found_sibling_type := false
|
||||
found_import := false
|
||||
found_collision := false
|
||||
found_file_sibling := false
|
||||
found_file_sibling_value := false
|
||||
found_file_sibling_type := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_sibling = found_sibling || strings.contains(diagnostic.message, "unknown symbol 'sibling'")
|
||||
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unknown symbol 'sibling_value'")
|
||||
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type 'Sibling'")
|
||||
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member 'secret'")
|
||||
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function 'collision'")
|
||||
found_file_sibling = found_file_sibling || strings.contains(diagnostic.message, "unknown symbol 'file_sibling'")
|
||||
found_file_sibling_value = found_file_sibling_value || strings.contains(diagnostic.message, "unknown symbol 'file_sibling_value'")
|
||||
found_file_sibling_type = found_file_sibling_type || strings.contains(diagnostic.message, "unknown symbol 'File_Sibling'")
|
||||
}
|
||||
testing.expect(t, !found_sibling)
|
||||
testing.expect(t, !found_sibling_value)
|
||||
testing.expect(t, !found_sibling_type)
|
||||
testing.expect(t, found_import)
|
||||
testing.expect(t, found_collision)
|
||||
testing.expect(t, found_file_sibling)
|
||||
testing.expect(t, found_file_sibling_value)
|
||||
testing.expect(t, found_file_sibling_type)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -10342,7 +10677,7 @@ MaybePoint :: alias ?@dep.Point
|
||||
Concrete :: alias dep.Box(i32)
|
||||
`
|
||||
hidden_text := `dep :: import "../dep"
|
||||
hide local_answer_alias :: alias dep.answer
|
||||
@hide local_answer_alias :: alias dep.answer
|
||||
`
|
||||
top_text := `facade :: import "../facade"
|
||||
Box :: alias facade.RenamedBox
|
||||
@@ -10466,7 +10801,7 @@ declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) {
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
}
|
||||
dep_text := `visible func() i32 { return 1 }
|
||||
hide hidden_target func() i32 { return 2 }
|
||||
@hide hidden_target func() i32 { return 2 }
|
||||
ambiguous func() i32 { return 3 }
|
||||
ambiguous i32 :: 4
|
||||
`
|
||||
@@ -10510,7 +10845,7 @@ main func() void {}
|
||||
testing.expect(t, loaded)
|
||||
wants := []string{
|
||||
"has no member 'missing'",
|
||||
"is package-hidden",
|
||||
"is not public",
|
||||
"unknown symbol 'nope'",
|
||||
"unavailable imported package 'gone'",
|
||||
"package member 'dep.ambiguous' is ambiguous",
|
||||
@@ -11288,6 +11623,39 @@ braceless_while_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
|
||||
@(test)
|
||||
while_unwrap_compiles_and_runs_at_runtime_and_comptime :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-while-unwrap"
|
||||
main_path := "/tmp/brolang-test-while-unwrap/main.bro"
|
||||
output := "/tmp/brolang-test-while-unwrap-output"
|
||||
text := `next func(index @mut usize, limit usize) ?i32 {
|
||||
if (index^ == limit) return null
|
||||
index^ += 1
|
||||
return i32(index^)
|
||||
}
|
||||
sum func(limit, cap usize) i32 {
|
||||
index usize := 0
|
||||
total i32 := 0
|
||||
while next(&index, limit) |value : usize(value) <= cap| : total += value {}
|
||||
return total
|
||||
}
|
||||
COMPTIME_SUM :: $sum(4, 4)
|
||||
COMPTIME_GUARDED :: $sum(5, 3)
|
||||
main func() i32 {
|
||||
if COMPTIME_SUM != 10 or COMPTIME_GUARDED != 6 { return 1 }
|
||||
return sum(4, 4) + sum(5, 3) - 16
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
@@ -13939,7 +14307,6 @@ main func() i32 {
|
||||
@(test)
|
||||
keywords_are_valid_enum_members_and_tagged_union_variants :: proc(t: ^testing.T) {
|
||||
testing.expect(t, token.is_keyword(.Keyword_Func))
|
||||
testing.expect(t, token.is_keyword(.Keyword_Hide))
|
||||
testing.expect(t, token.is_keyword(.Keyword_C_Longdouble))
|
||||
testing.expect(t, !token.is_keyword(.Identifier))
|
||||
testing.expect(t, !token.is_keyword(.Underscore))
|
||||
@@ -13954,6 +14321,9 @@ Token :: union(TokenKind) {
|
||||
else void
|
||||
return i32
|
||||
}
|
||||
hide func() i32 { return 0 }
|
||||
package func() i32 { return 0 }
|
||||
file func() i32 { return 0 }
|
||||
kind func(value bool) TokenKind {
|
||||
if value {
|
||||
return .if
|
||||
@@ -13966,7 +14336,7 @@ main func() i32 {
|
||||
a Token := Token{ if = 1 }
|
||||
b Token := Token{ else }
|
||||
c Token := .return{2}
|
||||
total i32 := a.if + c.return
|
||||
total i32 := a.if + c.return + hide() + package() + file()
|
||||
match first {
|
||||
.if: total = total + 1
|
||||
.else: total = total + 2
|
||||
@@ -14816,6 +15186,34 @@ missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T)
|
||||
testing.expect(t, !strings.contains(formatted, "could not resolve the 'int' constraint"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
missing_unqualified_signature_type_reports_one_root_error :: proc(t: ^testing.T) {
|
||||
text := `render_stmt func(node NodeId) void {}
|
||||
main func() void {
|
||||
render_stmt(0)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="renderer.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 1)
|
||||
testing.expect_value(t, diagnostics.items[0].message, "unknown symbol 'NodeId'")
|
||||
formatted := source.format(&diagnostics, source.Diagnostic_Id(0))
|
||||
defer delete(formatted)
|
||||
testing.expect(t, strings.contains(formatted, "renderer.bro:1:23"))
|
||||
testing.expect(t, strings.contains(formatted, "^^^^^^ unknown symbol"))
|
||||
testing.expect(t, !strings.contains(formatted, "could not resolve specialization"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
poisoned_global_and_local_types_do_not_create_inference_fallbacks :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-poisoned-declarations"
|
||||
@@ -15193,12 +15591,12 @@ dependency_passes test {
|
||||
defer delete(stderr)
|
||||
output := string(stderr)
|
||||
testing.expect_value(t, state.exit_code, 1)
|
||||
testing.expect(t, strings.contains(output, "root.root_passes...[ok]"))
|
||||
testing.expect(t, strings.contains(output, "root.root_fails...[failed]"))
|
||||
testing.expect(t, strings.contains(output, "root.root_passes...[\x1b[92mok\x1b[0m]"))
|
||||
testing.expect(t, strings.contains(output, "root.root_fails...[\x1b[91mfailure\x1b[0m]"))
|
||||
testing.expect(t, strings.contains(output, "expected 42, found 41"))
|
||||
testing.expect(t, strings.contains(output, "root.root_continues...[ok]"))
|
||||
testing.expect(t, strings.contains(output, "root.root_errors...[failed]"))
|
||||
testing.expect(t, strings.contains(output, "dependency.dependency_passes...[ok]"))
|
||||
testing.expect(t, strings.contains(output, "root.root_continues...[\x1b[92mok\x1b[0m]"))
|
||||
testing.expect(t, strings.contains(output, "root.root_errors...[\x1b[91mfailure\x1b[0m]"))
|
||||
testing.expect(t, strings.contains(output, "dependency.dependency_passes...[\x1b[92mok\x1b[0m]"))
|
||||
testing.expect(t, strings.contains(output, root_path))
|
||||
testing.expect(t, strings.contains(output, "3 passed, 2 failed"))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@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
|
||||
}
|
||||
|
||||
@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 }
|
||||
return helper() + local_foreign() + i32(record.value)
|
||||
thing File_Thing := File_Thing { value = file_value }
|
||||
return helper() + local_foreign() + file_helper() + thing.value + i32(record.value)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,21 @@ Box :: struct {
|
||||
_value i32
|
||||
}
|
||||
|
||||
@hide:file file_helper func() i32 {
|
||||
return 2
|
||||
}
|
||||
|
||||
@hide:file File_Thing :: struct {
|
||||
value i32
|
||||
}
|
||||
|
||||
@hide:file file_value :: 2
|
||||
|
||||
from_b func(_input i32) i32 {
|
||||
_local Box := Box { _value = _input }
|
||||
record _C_Record := _C_Record { value = 0 }
|
||||
thing Thing := Thing { value = value }
|
||||
return helper() + thing.value + _local._value + _foreign() + i32(record.value) + dep._visible()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -19,23 +19,23 @@ init func(allocator mem.Allocator) State {
|
||||
}
|
||||
}
|
||||
|
||||
hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||
@hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||
return null
|
||||
}
|
||||
|
||||
hide fail_realloc func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize, _ usize) ?*mut u8 {
|
||||
@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_free func(_ ?@mut anyopaque, _ ?*mut u8, _ usize, _ usize) void {}
|
||||
|
||||
hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
@hide fail_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
|
||||
alloc = fail_alloc,
|
||||
realloc = fail_realloc,
|
||||
free = fail_free,
|
||||
}
|
||||
|
||||
hide fail_allocator mem.Allocator :: mem.Allocator {
|
||||
@hide fail_allocator mem.Allocator :: mem.Allocator {
|
||||
context = null,
|
||||
vtable = &fail_vtable,
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ main func() i32 {
|
||||
if bits != 255 { return 10 }
|
||||
|
||||
values [10]u8 := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
if values[LocalID(4)] != 4 { return 11 }
|
||||
section []u8 := values[LocalID(2)..LocalID(5)]
|
||||
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 }
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
main func() i32 {
|
||||
items [3]mut i32 := undefined
|
||||
i int := 1
|
||||
i u32 := 1
|
||||
items[i] = 42
|
||||
return 0
|
||||
}
|
||||
@@ -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,26 +13,26 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 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 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,
|
||||
|
||||
@@ -52,7 +52,7 @@ main func() i32 {
|
||||
items[0] = 10
|
||||
items[1] = 20
|
||||
|
||||
idx u8 := 2
|
||||
idx usize := 2
|
||||
items[idx] = items[0] + items[1]
|
||||
if (items[2] != 30) return 1
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ print func($format []u8, $Args type, args Args) void {
|
||||
io.print(writer, format, Args, args) catch |_| {}
|
||||
}
|
||||
|
||||
hide write func(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
|
||||
@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 {
|
||||
|
||||
@@ -134,7 +134,7 @@ put func(
|
||||
}
|
||||
}
|
||||
|
||||
hide normalize func(hash usize) usize {
|
||||
@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
|
||||
@@ -143,7 +143,7 @@ hide normalize func(hash usize) usize {
|
||||
|
||||
#! FNV-1a hash implementation.
|
||||
#! note: vulnerable to collision attacks.
|
||||
hide str_hash func(key []u8) usize {
|
||||
@hide str_hash func(key []u8) usize {
|
||||
hash u32 := 2166136261 # offset basis
|
||||
prime u32 := 16777619
|
||||
|
||||
@@ -155,6 +155,6 @@ hide str_hash func(key []u8) usize {
|
||||
return usize(hash)
|
||||
}
|
||||
|
||||
hide str_eql func(a, b []u8) bool {
|
||||
@hide str_eql func(a, b []u8) bool {
|
||||
return mem.eql(a, b)
|
||||
}
|
||||
|
||||
+9
-9
@@ -46,7 +46,7 @@ writer func(file File) Writer {
|
||||
}
|
||||
}
|
||||
|
||||
hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
|
||||
@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 {
|
||||
@@ -68,7 +68,7 @@ hide system_read func(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize !
|
||||
}
|
||||
}
|
||||
|
||||
hide system_write func(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
|
||||
@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 {
|
||||
@@ -90,7 +90,7 @@ 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 {
|
||||
@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
|
||||
@@ -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,7 +136,7 @@ hide system_vtable IoVTable :: IoVTable {
|
||||
stderr = system_stderr,
|
||||
}
|
||||
|
||||
hide system func() Io {
|
||||
@hide system func() Io {
|
||||
return Io {
|
||||
context = null,
|
||||
vtable = &system_vtable,
|
||||
|
||||
+13
-13
@@ -128,7 +128,7 @@ 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 {
|
||||
@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
|
||||
@@ -161,7 +161,7 @@ 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 {
|
||||
@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
|
||||
@@ -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,14 +198,14 @@ 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 {
|
||||
@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 = ""}
|
||||
@@ -310,17 +310,17 @@ 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 {
|
||||
@hide distinct_value func($Backing, $Distinct type, value Distinct) Backing {
|
||||
return ptrcast!(Backing, &value)^
|
||||
}
|
||||
|
||||
hide scalar_or_distinct_type func($T type) bool {
|
||||
@hide scalar_or_distinct_type func($T type) bool {
|
||||
match typeinfo!(T) {
|
||||
.bool: return true
|
||||
.integer: return true
|
||||
@@ -332,7 +332,7 @@ hide scalar_or_distinct_type func($T type) bool {
|
||||
|
||||
|
||||
|
||||
hide write_integer func(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
|
||||
@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)
|
||||
@@ -350,7 +350,7 @@ hide write_integer func(output Writer, $T type, value T, base u64, uppercase boo
|
||||
}
|
||||
|
||||
# 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
|
||||
@@ -381,7 +381,7 @@ hide write_float func(output Writer, $T type, value T, scientific bool) void ! W
|
||||
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)
|
||||
@@ -395,7 +395,7 @@ hide write_decimal func(output Writer, $T type, value T) void ! WriteError {
|
||||
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 {
|
||||
@@ -414,7 +414,7 @@ hide write_character func(output Writer, $T type, value T) void ! WriteError {
|
||||
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")
|
||||
|
||||
+7
-7
@@ -119,11 +119,11 @@ empty func($T type) []mut T {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
|
||||
hide empty_storage [1]mut u64 := [0]
|
||||
@hide empty_storage [1]mut u64 := [0]
|
||||
|
||||
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
|
||||
while current > 1 {
|
||||
@@ -134,7 +134,7 @@ 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 null
|
||||
|
||||
if alignment <= malloc_alignment {
|
||||
@@ -148,7 +148,7 @@ hide c_alloc func(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
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 null
|
||||
|
||||
if new_size == 0 {
|
||||
@@ -174,11 +174,11 @@ hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
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,
|
||||
|
||||
@@ -13,13 +13,13 @@ TestOuter :: distinct TestInner
|
||||
TestOuterAlias :: alias TestOuter
|
||||
|
||||
|
||||
hide array_info_matches func($Array, $Child type, $len usize) bool {
|
||||
@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 {
|
||||
@hide distinct_info_matches func($Distinct, $Backing type) bool {
|
||||
match typeinfo!(Distinct) {
|
||||
.distinct |backing|: return backing == Backing
|
||||
else: return false
|
||||
|
||||
@@ -10,7 +10,7 @@ StaticStringMap func($V type) type {
|
||||
}
|
||||
}
|
||||
|
||||
hide Pair func($V type) type {
|
||||
@hide Pair func($V type) type {
|
||||
return struct { []u8, V }
|
||||
}
|
||||
|
||||
|
||||
@@ -73,10 +73,10 @@ expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) voi
|
||||
|
||||
run func(name []u8, callback *func() void ! Error) bool {
|
||||
callback() catch |_| {
|
||||
debug.print("{s}...[failed]\n", {name,})
|
||||
debug.print("{s}...[\x1b[91mfailure\x1b[0m]\n", {name,})
|
||||
return false
|
||||
}
|
||||
debug.print("{s}...[ok]\n", {name,})
|
||||
debug.print("{s}...[\x1b[92mok\x1b[0m]\n", {name,})
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -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,7 +108,7 @@ 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(" `")
|
||||
|
||||
@@ -74,7 +74,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
|
||||
return
|
||||
}
|
||||
|
||||
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
|
||||
@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 {
|
||||
@@ -91,7 +91,7 @@ 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
|
||||
maximum usize :: usize(maxval!(c_long))
|
||||
@@ -109,12 +109,12 @@ 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 = null,
|
||||
vtable = &system_vtable,
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -116,9 +116,9 @@ 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
|
||||
}
|
||||
@@ -135,7 +135,7 @@ 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 null
|
||||
}
|
||||
@@ -153,7 +153,7 @@ hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
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 null
|
||||
}
|
||||
@@ -186,11 +186,11 @@ hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
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,
|
||||
|
||||
@@ -27,9 +27,9 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
|
||||
allocator.vtable.free(allocator.context, memory, size, alignment)
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
@@ -61,9 +61,9 @@ 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
|
||||
}
|
||||
@@ -80,7 +80,7 @@ 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 null
|
||||
}
|
||||
@@ -98,7 +98,7 @@ hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
||||
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 null
|
||||
}
|
||||
@@ -131,11 +131,11 @@ hide c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user