Compare commits

..

11 Commits

38 changed files with 2002 additions and 746 deletions
+17 -14
View File
@@ -16,8 +16,9 @@ roadmap and milestone history.
- file-local relative imports, import aliases, and qualified member access - file-local relative imports, import aliases, and qualified member access
- transparent declaration aliases with `Name :: alias package.Member`; functions/type factories, - transparent declaration aliases with `Name :: alias package.Member`; functions/type factories,
named types, and globals retain their original declaration or storage identity 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, - bare `@hide` and explicit `@hide:package` make a named top-level declaration package-local;
leading underscores are ordinary identifier characters, and imports are always file-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 - relative `.h` imports as synthetic C header package namespaces
- native `name test { ... }` declarations with fallible-void results inferred from `testing.Error` - native `name test { ... }` declarations with fallible-void results inferred from `testing.Error`
and errors propagated by `try`, plus anonymous 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 - 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 - `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 - 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 - 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 - 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 - 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 - 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 - native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield` - fallible channel types `T ! E`, where `E` is a native enum, native struct, tagged union, or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
- bodyful local functions and root `main` may write `T!` to infer a specialization-local error channel from propagated `try` expressions and concretely typed error returns; inference composes only existing named error types, never synthesizes variants, and requires at least one inferred error
#### distinct types #### distinct types
@@ -61,8 +63,9 @@ nominal result type: checked integer `+`, `-`, `*`, unary `-`, bitwise operators
comparisons, and compound assignments; float arithmetic, unary `-`, comparisons, and compound comparisons, and compound assignments; float arithmetic, unary `-`, comparisons, and compound
assignments; and boolean equality/inequality. Integer literals and float literals are contextual. assignments; and boolean equality/inequality. Integer literals and float literals are contextual.
Typed backing values and separate distinct identities remain incompatible in ordinary operations; 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; an explicit constructor is required to cross that boundary. Distinct integers require an explicit
`minval!` / `maxval!` return the distinct type. Runtime and comptime behavior match. `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 `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. 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` - 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 - 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 - 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 - 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 - `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 - `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 - bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
@@ -152,9 +155,9 @@ and
or or
``` ```
Each level is left-associative. Because `|` also delimits `if` and `for` captures, a bitwise-OR Each level is left-associative. Because `|` also delimits `if`, `while`, and `for` captures, a
header expression must be parenthesized before a capture list, for example bitwise-OR header expression must be parenthesized before a capture list, for example
`if (flags | mask) |value| { ... }`. `while (flags | mask) |value| { ... }`.
#### division #### 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/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/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 - `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/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 - `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 an alias of exactly `@std/testing` receive compiler-injected source locations
+11 -7
View File
@@ -208,14 +208,18 @@ mem :: import "@std/mem"
value :: math.sum(other_math.value, 1) value :: math.sum(other_math.value, 1)
``` ```
Top-level declarations are public by default. Prefix a declaration with `hide` Top-level declarations are public by default. Prefix a declaration with bare `@hide`
to keep it local to its package; sibling files can use it, but importing packages to make it package-local, or spell the scope explicitly with `@hide:package` or
cannot. Leading underscores have no visibility meaning. Imports are always file-local `@hide:file`. The qualifier is contextual, so `hide`, `package`, and `file` remain
and cannot be hidden or re-exported: available as identifiers. Imports are always file-local and cannot use visibility
qualifiers or be re-exported:
```bro ```bro
hide helper func() i32 { return 42 } @hide
hide State :: struct { value i32 } shared_helper func() i32 { return 42 }
@hide:file
implementation_detail func() i32 { return shared_helper() }
``` ```
Current prototype features: 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 - 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 - 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 - 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 - Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces - 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 - 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
View File
@@ -202,13 +202,12 @@ Stmt :: struct {
assignment_op: Assignment_Op, assignment_op: Assignment_Op,
target: Expr_Id, target: Expr_Id,
expr: Expr_Id, expr: Expr_Id,
// `If` statements use `expr` as the condition, `captures` as optional // `If` and `While` statements use `expr` as the condition, `captures` as
// unwrap binding names, `guard` as the optional post-unwrap boolean // optional unwrap binding names, and `guard` as the optional post-unwrap
// condition, `body` as the then-block, and `else_body` as the else-block. // boolean condition. `If` uses `body` as the then-block and `else_body` as
// An `else if` chain is represented as an `else_body` holding a single // the else-block; an `else if` chain is represented as an `else_body`
// nested `If` statement. // holding a single nested `If` statement. `While` uses `body` as the loop
// `While` statements use `expr` as the condition, `body` as the loop body, // body and `update` as the optional post-iteration statement.
// and `update` as the optional post-iteration statement.
// `For` statements use `expr` as the iterable, `name` as the item capture, // `For` statements use `expr` as the iterable, `name` as the item capture,
// `index_name` as the optional index capture, and `pointer_capture` to // `index_name` as the optional index capture, and `pointer_capture` to
// distinguish `|@item|` from copy capture. // distinguish `|@item|` from copy capture.
@@ -243,12 +242,13 @@ Function :: struct {
generated: bool, generated: bool,
analysis_root: bool, analysis_root: bool,
test: bool, test: bool,
package_hidden: bool, visibility: types.Visibility,
has_body: bool, has_body: bool,
variadic: bool, variadic: bool,
params: []Param, params: []Param,
result: Type_Syntax, result: Type_Syntax,
error: Type_Syntax, error: Type_Syntax,
infer_error: bool,
body: []Stmt_Id, body: []Stmt_Id,
link_name: string, link_name: string,
unsupported_reason: string, unsupported_reason: string,
@@ -265,7 +265,7 @@ Global :: struct {
immutable: bool, immutable: bool,
external: bool, external: bool,
writable: bool, writable: bool,
package_hidden: bool, visibility: types.Visibility,
expr: Expr_Id, expr: Expr_Id,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
} }
@@ -313,7 +313,7 @@ Declaration_Alias :: struct {
target_pkg: Package_Id, target_pkg: Package_Id,
target: u32, target: u32,
kind: Declaration_Alias_Kind, kind: Declaration_Alias_Kind,
package_hidden: bool, visibility: types.Visibility,
valid: bool, valid: bool,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
} }
File diff suppressed because it is too large Load Diff
+194 -66
View File
@@ -291,6 +291,7 @@ Ct_Binding :: struct {
value: Ct_Value_Id, value: Ct_Value_Id,
cell: Ct_Cell_Id, cell: Ct_Cell_Id,
mutable: bool, mutable: bool,
open_integer: bool,
} }
Ct_Error_Refinement :: struct { Ct_Error_Refinement :: struct {
@@ -473,9 +474,23 @@ ct_extend_place :: proc(
return ct_add_place(state, base.cell, value_type, writable, extended[:]) 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) 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) { 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 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 { ct_binding_place :: proc(state: ^Ct_State, index: int) -> Ct_Place_Id {
if index < 0 || index >= len(state.bindings) { if index < 0 || index >= len(state.bindings) {
return INVALID_CT_PLACE return INVALID_CT_PLACE
@@ -1315,6 +1356,7 @@ ct_eval_expr :: proc(
case .Name: case .Name:
if !symbol.is_valid(expr.qualifier) { if !symbol.is_valid(expr.qualifier) {
if index, ok := ct_find_binding_index(state, expr.name); ok { 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) return ct_observe_value(state, ct_binding_value(state, index), expr.span)
} }
if value, ok := current_comptime_value(checker, expr.name); ok { 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) return ct_eval_field_value(state, base_id, expr.name, expr.span)
case .Index: 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 { if !index_ok || index_flow.kind != .Normal {
return INVALID_CT_VALUE, index_flow, index_ok return INVALID_CT_VALUE, index_flow, index_ok
} }
index_type := state.values[index_id].type index_type := state.values[index_id].type
index_representation := types.runtime_representation(index_type, store) if !types.equal(index_type, types.USIZE) {
index_literal := is_numeric_constant_expr(checker, expr.right) return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
if !types.is_concrete_integer(index_representation) || state,
!index_literal && !can_implicitly_convert_type(checker, index_representation, types.USIZE) { .Not_Comptime,
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be coercible to usize") 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) index_value, index_is_int := ct_integer_value(state, index_id)
if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) { 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 { if bound == ast.INVALID_EXPR {
continue 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 { if !bound_ok || bound_flow.kind != .Normal {
return INVALID_CT_VALUE, bound_flow, bound_ok return INVALID_CT_VALUE, bound_flow, bound_ok
} }
bound_type := state.values[value].type bound_type := state.values[value].type
bound_representation := types.runtime_representation(bound_type, store) if !types.equal(bound_type, types.USIZE) {
bound_literal := is_numeric_constant_expr(checker, bound) return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
if !types.is_concrete_integer(bound_representation) || state,
!bound_literal && !can_implicitly_convert_type(checker, bound_representation, types.USIZE) { .Not_Comptime,
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be coercible to usize") 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) integer, integer_ok := ct_integer_value(state, value)
if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) { if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) {
@@ -2352,6 +2400,16 @@ ct_eval_place :: proc(
if !index_ok || index_flow.kind != .Normal { if !index_ok || index_flow.kind != .Normal {
return INVALID_CT_PLACE, types.INVALID, false, index_flow, index_ok 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) index_int, int_ok := ct_integer_value(state, index_value)
if !int_ok || index_int < 0 || index_int > i128(0x7fff_ffff) { 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") 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")
@@ -4487,6 +4545,19 @@ ct_return_value :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, span: source.Spa
if expr.kind == .Enum_Literal && types.sum_has_name(&checker.module.types, error_type, u32(expr.name)) { if expr.kind == .Enum_Literal && types.sum_has_name(&checker.module.types, error_type, u32(expr.name)) {
error_path = true error_path = true
expected = error_type expected = error_type
} else if expr.kind == .Struct_Literal {
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, true)
named := types.find_named(
&checker.module.types,
u32(target_pkg),
u32(expr.name),
file=u32(expr_lookup_file(expr, state.file)),
) if available else types.INVALID
named = types.resolve_alias(named, &checker.module.types)
if can_implicitly_convert_type(checker, named, error_type) {
error_path = true
expected = error_type
}
} else if expr.kind == .Name && !symbol.is_valid(expr.qualifier) { } else if expr.kind == .Name && !symbol.is_valid(expr.qualifier) {
if index, found := ct_find_binding_index(state, expr.name); found { if index, found := ct_find_binding_index(state, expr.name); found {
actual := state.bindings[index].type actual := state.bindings[index].type
@@ -4970,6 +5041,11 @@ ct_exec_statements :: proc(
} else { } else {
declared := type_from_syntax(checker, statement.type, state.pkg, state.file, active_state=state) 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 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) value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, expected, depth+1)
ok = expr_ok ok = expr_ok
flow = expr_flow flow = expr_flow
@@ -4978,7 +5054,14 @@ ct_exec_statements :: proc(
value, ok = ct_coerce_value(state, value, expected, statement.span) value, ok = ct_coerce_value(state, value, expected, statement.span)
} }
if ok && statement.name != checker.sink_symbol { 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,
)
} }
} }
} }
@@ -5245,6 +5328,70 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
return ct_flow(.Normal), true 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) { ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
checker := state.checker checker := state.checker
if yield_returns && statement.else_body == nil { if yield_returns && statement.else_body == nil {
@@ -5271,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) return ct_exec_statements(state, body, false, depth+1)
} }
operands: [dynamic]ast.Expr_Id matched, scope_start, unwrap_flow, unwrap_ok := ct_eval_conditional_unwrap(state, statement, "if", depth)
operands.allocator = checker.allocator if !unwrap_ok || unwrap_flow.kind != .Normal {
defer delete(operands) return unwrap_flow, unwrap_ok
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
} }
body := statement.body if matched else statement.else_body body := statement.body if matched else statement.else_body
flow := ct_flow(.Normal) flow := ct_flow(.Normal)
@@ -5344,34 +5453,53 @@ ct_exec_while :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
if !ct_step(state, statement.span) { if !ct_step(state, statement.span) {
return ct_flow(.Normal), false return ct_flow(.Normal), false
} }
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1) scope_start := len(state.bindings)
if !ok || flow.kind != .Normal { if len(statement.captures) > 0 {
return flow, ok matched, unwrap_scope_start, flow, ok :=
} ct_eval_conditional_unwrap(state, statement, "while", depth)
value, bool_ok := ct_bool_value(state, condition) scope_start = unwrap_scope_start
if !bool_ok { if !ok || flow.kind != .Normal {
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'while' condition must be a bool") return flow, ok
} }
if !value { if !matched {
return ct_flow(.Normal), true 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) body_flow, body_ok := ct_exec_statements(state, statement.body, yield_returns, depth+1)
if !body_ok { if !body_ok {
ct_pop_bindings(state, scope_start)
return body_flow, false return body_flow, false
} }
if ct_loop_consumes_flow(body_flow, statement.label, false) { if ct_loop_consumes_flow(body_flow, statement.label, false) {
ct_pop_bindings(state, scope_start)
return ct_flow(.Normal), true return ct_flow(.Normal), true
} }
if body_flow.kind != .Normal && !ct_loop_consumes_flow(body_flow, statement.label, 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 return body_flow, true
} }
if statement.update != ast.INVALID_STMT { if statement.update != ast.INVALID_STMT {
update := [1]ast.Stmt_Id{statement.update} update := [1]ast.Stmt_Id{statement.update}
update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1) update_flow, update_ok := ct_exec_statements(state, update[:], false, depth+1)
if !update_ok || update_flow.kind != .Normal { if !update_ok || update_flow.kind != .Normal {
ct_pop_bindings(state, scope_start)
return update_flow, update_ok return update_flow, update_ok
} }
} }
ct_pop_bindings(state, scope_start)
} }
} }
+6 -6
View File
@@ -229,12 +229,12 @@ Stmt :: struct {
// by computing the target address once, loading its current value, applying // by computing the target address once, loading its current value, applying
// the operation to `expr`, and storing through the original address. // the operation to `expr`, and storing through the original address.
assignment_op: Assignment_Op, assignment_op: Assignment_Op,
// Boolean `If` statements use `expr` as the condition. Conditional unwraps // Boolean `If` and `While` statements use `expr` as the condition.
// use `unwraps` for the ordered optional expressions and capture locals, and // Conditional unwraps use `unwraps` for the ordered optional expressions
// `guard` for the optional boolean checked after every unwrap succeeds. // and capture locals, and `guard` for the optional boolean checked after
// Both forms use `then_body`/`else_body` as the branch statement lists. // every unwrap succeeds. `If` uses `then_body`/`else_body` as its branches.
// `While` statements use `expr` as the condition, `then_body` as the loop // `While` uses `then_body` as its loop body and `update` as the optional
// body, and `update` as the optional post-iteration statement. // post-iteration statement.
// `For` statements use `expr` as the iterable, `local` as the item capture, // `For` statements use `expr` as the iterable, `local` as the item capture,
// `index_local` as the optional sequence index, and `iterator_type` as the // `index_local` as the optional sequence index, and `iterator_type` as the
// normalized many-item pointer type for sequence iteration. // normalized many-item pointer type for sequence iteration.
+17 -6
View File
@@ -8,6 +8,10 @@ is_identifier_start :: proc(value: byte) -> bool {
return value == '_' || value >= 'a' && value <= 'z' || value >= 'A' && value <= 'Z' 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 { is_identifier_continue :: proc(value: byte) -> bool {
return is_identifier_start(value) || value >= '0' && value <= '9' 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 "distinct": return .Keyword_Distinct
case "alias": return .Keyword_Alias case "alias": return .Keyword_Alias
case "import": return .Keyword_Import case "import": return .Keyword_Import
case "hide": return .Keyword_Hide
case "return": return .Keyword_Return case "return": return .Keyword_Return
case "try": return .Keyword_Try case "try": return .Keyword_Try
case "catch": return .Keyword_Catch case "catch": return .Keyword_Catch
@@ -320,13 +323,21 @@ lex :: proc(
for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' { for cursor < len(bytes) && bytes[cursor] != '"' && bytes[cursor] != '\n' {
if bytes[cursor] == '\\' { if bytes[cursor] == '\\' {
cursor += 1 cursor += 1
if cursor >= len(bytes) || valid_escape := cursor < len(bytes) &&
(bytes[cursor] != '\\' && bytes[cursor] != '"' && bytes[cursor] != 'n' && (bytes[cursor] == '\\' || bytes[cursor] == '"' || bytes[cursor] == 'n' ||
bytes[cursor] != 'r' && bytes[cursor] != 't' && bytes[cursor] != '0') { 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( source.add(
diagnostics, diagnostics,
source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+1, len(bytes)))}, 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', and '\\0' escapes", "strings only support '\\\\', '\\\"', '\\n', '\\r', '\\t', '\\0', and '\\xNN' escapes",
) )
valid = false valid = false
} }
+54 -5
View File
@@ -1050,6 +1050,16 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset) fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset)
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%fallible_payload%d, ptr %%fallible_error_payload%d, i64 %d, i1 false)\n", instruction_index, instruction_index, error_payload_size) fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%fallible_payload%d, ptr %%fallible_error_payload%d, i64 %d, i1 false)\n", instruction_index, instruction_index, error_payload_size)
} }
} else if types.is_struct(error_type, &emitter.module.types) {
if !valid_value(instructions, instruction.args[0], error_type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid fallible struct error operand")
continue
}
fmt.sbprintf(&emitter.builder, " store i16 1, ptr %%fallible_slot%d\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset)
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(error_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.args[0], error_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %%fallible_payload%d\n", instruction_index)
} }
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_slot%d\n", instruction_index, type_name, instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_slot%d\n", instruction_index, type_name, instruction_index)
continue continue
@@ -1107,6 +1117,15 @@ emit_instruction_stream :: proc(
} else if item.kind == .Range && arg_index == 2 { } else if item.kind == .Range && arg_index == 2 {
element_type = types.BOOL 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 final := arg_index == total-1
if final { if final {
fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name) fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name)
@@ -1124,7 +1143,7 @@ emit_instruction_stream :: proc(
} else { } else {
write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types) 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: case .Null:
item, ok := types.node(&emitter.module.types, instruction.type) item, ok := types.node(&emitter.module.types, instruction.type)
@@ -1324,13 +1343,19 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference") emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference")
continue 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) { 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)) 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 { } else {
fmt.sbprintf( fmt.sbprintf(
&emitter.builder, &emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", " %%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: case .Load:
@@ -1389,6 +1414,12 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_error_slot%d\n", instruction_index, type_name, instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_error_slot%d\n", instruction_index, type_name, instruction_index)
continue continue
} }
if types.is_struct(error_type, &emitter.module.types) {
source_offset := types.fallible_payload_offset(channel_type, &emitter.module.types, emitter.module.target)
fmt.sbprintf(&emitter.builder, " %%fallible_error_source%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, source_offset)
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_error_source%d\n", instruction_index, llvm_type(error_type, &emitter.module.types), instruction_index)
continue
}
emit_recovery_value(emitter, instruction_index, instruction, "unsupported fallible error type") emit_recovery_value(emitter, instruction_index, instruction, "unsupported fallible error type")
case .Store: case .Store:
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) || if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) ||
@@ -2577,12 +2608,30 @@ emit_types :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, " }\n") strings.write_string(&emitter.builder, " }\n")
continue continue
} }
fields := types.fields_for(&emitter.module.types, id)
strings.write_string(&emitter.builder, "{ ") strings.write_string(&emitter.builder, "{ ")
for field, field_index in types.fields_for(&emitter.module.types, id) { previous_index := -1
if field_index > 0 { 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, ", ")
} }
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") strings.write_string(&emitter.builder, " }\n")
} }
+40 -19
View File
@@ -1249,14 +1249,29 @@ load_package :: proc(
return pkg_id 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 { declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, file: ast.File_Id, name: symbol.Id) -> bool {
for function in module.functions { 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 return true
} }
} }
for global in module.globals { 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 return true
} }
} }
@@ -1327,7 +1342,9 @@ find_visible_enum_global :: proc(
public_only := false, public_only := false,
) -> ast.Global_Id { ) -> ast.Global_Id {
for global, index in module.globals { 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) 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 { alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool {
for function in module.functions { 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 return true
} }
} }
for global in module.globals { 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 return true
} }
} }
for item in module.type_store.nodes { 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 return true
} }
} }
@@ -1473,7 +1493,7 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb
target: u32 target: u32
kinds := 0 kinds := 0
for function, index in module.functions { 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 kind = .Function
target = u32(ast.function_id(index)) target = u32(ast.function_id(index))
kinds += 1 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 { 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 kind = .Global
target = u32(ast.global_id(index)) target = u32(ast.global_id(index))
kinds += 1 kinds += 1
@@ -1498,24 +1518,24 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb
return kind, target, kinds 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 { 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 return true
} }
} }
for global in module.globals { 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 return true
} }
} }
for item in module.type_store.nodes { 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 return true
} }
} }
for alias in module.aliases { 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 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 { find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int {
for alias, index in module.aliases { 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 return index
} }
} }
@@ -1581,11 +1601,11 @@ resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bo
return false 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( alias.diagnostic = source.addf(
state.diagnostics, state.diagnostics,
alias.span, 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.qualifier),
symbol.resolve(state.symbols, alias.member), symbol.resolve(state.symbols, alias.member),
) )
@@ -1611,7 +1631,8 @@ validate_declaration_aliases :: proc(state: ^State) {
continue continue
} }
for previous in state.module.aliases[:index] { 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.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name)
alias.valid = false alias.valid = false
break break
@@ -1661,9 +1682,9 @@ validate_declaration_aliases :: proc(state: ^State) {
u32(alias.pkg), u32(alias.pkg),
u32(alias.name), u32(alias.name),
file=u32(alias.file), 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.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name))
alias.valid = false alias.valid = false
} }
+235 -140
View File
@@ -924,6 +924,101 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
return last 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) { lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
hir_module := state.hir_module hir_module := state.hir_module
for statement_id in statements { 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 false_target := else_lbl if has_else else merge_lbl
if len(statement.unwraps) > 0 { if len(statement.unwraps) > 0 {
// Evaluate each optional exactly once, entering the next operand only lower_conditional_unwrap_header(state, statement, then_lbl, false_target)
// 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,
})
}
} else { } else {
cond := lower_expr(state, statement.expr) cond := lower_expr(state, statement.expr)
append_instruction(state, ir.Instruction{ 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, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
condition := lower_expr(state, statement.expr) if len(statement.unwraps) > 0 {
append_instruction(state, ir.Instruction{ lower_conditional_unwrap_header(state, statement, body_lbl, exit_lbl)
op=.Cond_Br, span=statement.span, type=types.VOID, } else {
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)), condition := lower_expr(state, statement.expr)
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, 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{ append_instruction(state, ir.Instruction{
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl, op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, 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) { 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)) 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)) 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 return
} }
instructions: [dynamic]ir.Instruction instructions: [dynamic]ir.Instruction
instructions.allocator = allocator instructions.allocator = allocator
provider_call := ir.instruction_id(len(instructions)) args: []ir.Instruction_Id
append(&instructions, ir.Instruction{ if param_index >= 0 {
op=.Call, provider_call := ir.instruction_id(len(instructions))
type=hir_module.functions[provider_index].result, append(&instructions, ir.Instruction{
target=ir.function_ref(ir.Function_Id(provider_index)), op=.Call,
a=ir.INVALID_INSTRUCTION, type=hir_module.functions[provider_index].result,
b=ir.INVALID_INSTRUCTION, target=ir.function_ref(ir.Function_Id(provider_index)),
diagnostic=source.INVALID_DIAGNOSTIC, a=ir.INVALID_INSTRUCTION,
}) b=ir.INVALID_INSTRUCTION,
main_function := &hir_module.functions[main_index] diagnostic=source.INVALID_DIAGNOSTIC,
param_index, param_ok := hir.index(main_function.params[0], hir.INVALID_LOCAL, len(main_function.locals)) })
if !param_ok { init_args := make([]ir.Instruction_Id, 1, allocator)
return 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)) main_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{ append(&instructions, ir.Instruction{
op=.Call, op=.Call,
@@ -1797,36 +1814,114 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
b=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, 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{ append(&instructions, ir.Instruction{
op=.Return_Void, op=.Alloca, type=main_function.result, target=ir.INVALID_REF,
type=types.VOID, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
} else {
append(&instructions, ir.Instruction{ append(&instructions, ir.Instruction{
op=.Return, op=.Store, type=main_function.result, target=ir.INVALID_REF,
type=hir_module.functions[main_index].result, a=channel_slot, b=main_call, diagnostic=source.INVALID_DIAGNOSTIC,
target=ir.INVALID_REF, })
a=main_call, code := ir.instruction_id(len(instructions))
b=ir.INVALID_INSTRUCTION, 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, 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{ append(&module.functions, ir.Function{
link_name=fmt.aprintf("main", allocator=allocator), link_name=fmt.aprintf("main", allocator=allocator),
calling_convention=.C, calling_convention=.C,
implementation=.Definition, implementation=.Definition,
linkage=.External, linkage=.External,
is_main=true, is_main=true,
result=hir_module.functions[main_index].result, result=types.I32,
instructions=instructions[:], instructions=instructions[:],
problematic=hir_module.functions[main_index].problematic || problematic=problematic,
hir_module.functions[provider_index].problematic,
}) })
} }
+180 -82
View File
@@ -31,7 +31,12 @@ Parser :: struct {
capture_pipe: bool, capture_pipe: bool,
// A parenthesized `if` condition ends before a leading-dot brace-less body. // A parenthesized `if` condition ends before a leading-dot brace-less body.
if_condition: bool, 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 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)] return parser.source_file.text[int(tok.span.start):int(tok.span.end)]
} }
package_hidden_name :: proc(parser: ^Parser, name: symbol.Id) -> bool { declaration_visibility :: proc(parser: ^Parser, name: symbol.Id) -> types.Visibility {
for hidden in parser.hidden_names { for local in parser.local_names {
if hidden == name { if local.name == name {
return true return local.visibility
} }
} }
return false return .Public
} }
collect_hidden_names :: proc(parser: ^Parser) { collect_local_names :: proc(parser: ^Parser) {
depth := 0 depth := 0
for item, index in parser.tokens.items { for item, index in parser.tokens.items {
#partial switch item.kind { #partial switch item.kind {
@@ -60,10 +65,38 @@ collect_hidden_names :: proc(parser: ^Parser) {
depth += 1 depth += 1
case .Right_Brace: case .Right_Brace:
depth = max(depth-1, 0) depth = max(depth-1, 0)
case .Keyword_Hide: case .At:
if depth == 0 && index+1 < len(parser.tokens.items) && if depth != 0 || index+1 >= len(parser.tokens.items) ||
parser.tokens.items[index+1].kind == .Identifier { parser.tokens.items[index+1].kind != .Identifier ||
append(&parser.hidden_names, parser.tokens.items[index+1].symbol) 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: case:
} }
@@ -186,6 +219,16 @@ decode_character :: proc(parser: ^Parser, tok: token.Token) -> (u64, bool) {
return u64(value), width == len(contents) 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) { parse_type_constant :: proc(parser: ^Parser) -> (u64, bool) {
negative := false negative := false
if _, ok := allow(parser, .Minus); ok { 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") source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters")
} }
result := parse_type(parser) result := parse_type(parser)
if _, ok := allow(parser, .Bang); ok { error_type, _ := parse_function_error(parser, false)
error_type := parse_error_type(parser) if types.is_valid(error_type) {
if c_abi { if c_abi {
source.add(parser.diagnostics, current(parser).span, "c_func pointer types cannot be fallible") source.add(parser.diagnostics, current(parser).span, "c_func pointer types cannot be fallible")
} else { } else {
@@ -437,7 +480,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
u32(name.symbol), u32(name.symbol),
u32(qualifier), u32(qualifier),
u32(parser.file), 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" && if token_text(parser, name) == "struct_type" &&
current(parser).kind == .Bang && peek(parser).kind == .Left_Paren { 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) 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 { skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
start := current(parser) start := current(parser)
depth := 0 depth := 0
@@ -2180,19 +2239,7 @@ parse_control_body :: proc(parser: ^Parser, header: ast.Expr_Id, diagnostic: str
return single return single
} }
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id { parse_conditional_captures :: proc(parser: ^Parser) -> ([]symbol.Id, ast.Expr_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: [dynamic]symbol.Id captures: [dynamic]symbol.Id
captures.allocator = parser.module.allocator captures.allocator = parser.module.allocator
guard := ast.INVALID_EXPR guard := ast.INVALID_EXPR
@@ -2218,9 +2265,9 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Pipe { if current(parser).kind == .Pipe {
source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'") source.add(parser.diagnostics, current(parser).span, "expected a guard expression after ':'")
} else { } else {
saved = parser.no_struct_literal saved := parser.no_struct_literal
parser.no_struct_literal = true parser.no_struct_literal = true
saved_capture_pipe = parser.capture_pipe saved_capture_pipe := parser.capture_pipe
parser.capture_pipe = true parser.capture_pipe = true
guard = parse_expression(parser) guard = parse_expression(parser)
parser.capture_pipe = saved_capture_pipe 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") 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( then_body := parse_control_body(
parser, parser,
condition, condition,
@@ -2559,8 +2623,12 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
skip_newlines(parser) skip_newlines(parser)
saved := parser.no_struct_literal saved := parser.no_struct_literal
parser.no_struct_literal = true parser.no_struct_literal = true
saved_capture_pipe := parser.capture_pipe
parser.capture_pipe = true
condition := parse_expression(parser) condition := parse_expression(parser)
parser.capture_pipe = saved_capture_pipe
parser.no_struct_literal = saved parser.no_struct_literal = saved
captures, guard := parse_conditional_captures(parser)
skip_newlines(parser) skip_newlines(parser)
update := ast.INVALID_STMT update := ast.INVALID_STMT
@@ -2582,6 +2650,8 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
span=span_from(start.span, previous(parser).span), span=span_from(start.span, previous(parser).span),
expr=condition, expr=condition,
body=body, body=body,
captures=captures,
guard=guard,
label=label, label=label,
update=update, update=update,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
@@ -2589,7 +2659,7 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id {
return 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) advance(parser)
if _, ok := allow(parser, .Left_Paren); !ok { if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'") 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) skip_newlines(parser)
result := parse_type(parser) result := parse_type(parser)
error_type := types.INVALID error_type, infer_error := parse_function_error(parser, true)
if _, ok := allow(parser, .Bang); ok {
error_type = parse_error_type(parser)
}
end := previous(parser) end := previous(parser)
ended_by_newline := current(parser).kind == .Newline ended_by_newline := current(parser).kind == .Newline
if 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, pkg=parser.pkg,
file=parser.file, file=parser.file,
c_abi=c_abi, c_abi=c_abi,
package_hidden=package_hidden, visibility=visibility,
has_body=false, has_body=false,
variadic=variadic, variadic=variadic,
params=params, params=params,
result=result, result=result,
error=error_type, error=error_type,
infer_error=infer_error,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
return return
@@ -2639,12 +2707,13 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, package_hidden
pkg=parser.pkg, pkg=parser.pkg,
file=parser.file, file=parser.file,
c_abi=c_abi, c_abi=c_abi,
package_hidden=package_hidden, visibility=visibility,
has_body=true, has_body=true,
variadic=variadic, variadic=variadic,
params=params, params=params,
result=result, result=result,
error=error_type, error=error_type,
infer_error=infer_error,
body=body, body=body,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
@@ -2661,10 +2730,7 @@ parse_function_literal :: proc(parser: ^Parser) -> ast.Expr_Id {
} }
skip_newlines(parser) skip_newlines(parser)
result := parse_type(parser) result := parse_type(parser)
error_type := types.INVALID error_type, infer_error := parse_function_error(parser, true)
if _, ok := allow(parser, .Bang); ok {
error_type = parse_error_type(parser)
}
if current(parser).kind == .Newline { if current(parser).kind == .Newline {
skip_newlines(parser) skip_newlines(parser)
} }
@@ -2687,6 +2753,7 @@ parse_function_literal :: proc(parser: ^Parser) -> ast.Expr_Id {
params=params, params=params,
result=result, result=result,
error=error_type, error=error_type,
infer_error=infer_error,
body=body, body=body,
diagnostic=source.INVALID_DIAGNOSTIC, 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) 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) 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 // 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. // enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
tag := types.INVALID 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", "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)) source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
} }
if !ended_by_newline { 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)) { if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:]) 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)) source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
} else if !c_layout && !is_union && !tuple { } else if !c_layout && !is_union && !tuple {
for value, index in defaults { for value, index in defaults {
@@ -2917,10 +2984,10 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, package_hidde
_ = finish_statement(parser) _ = 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) 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)
if !types.define_record(&parser.module.type_store, id, nil, false, true, false, package_hidden=package_hidden) { 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)) source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
} }
if current(parser).kind == .Left_Brace { 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) 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) start := advance(parser)
child := parse_type(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) 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, package_hidden) { 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)) source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
} }
if !types.is_valid(child) { if !types.is_valid(child) {
@@ -2958,7 +3025,7 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token, package_hidden: bool)
_ = finish_statement(parser) _ = 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) start := advance(parser)
saved := parser.cursor saved := parser.cursor
if current(parser).kind == .Identifier && peek(parser).kind == .Dot { 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, pkg=parser.pkg,
file=parser.file, file=parser.file,
target_pkg=ast.INVALID_PACKAGE, target_pkg=ast.INVALID_PACKAGE,
package_hidden=package_hidden, visibility=visibility,
valid=true, valid=true,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
@@ -2986,8 +3053,8 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) {
} }
parser.cursor = saved parser.cursor = saved
child := parse_type(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) 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, package_hidden) { 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)) source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
} }
if !types.is_valid(child) { 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) 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) start := advance(parser)
explicit_backing := false explicit_backing := false
backing := types.INVALID backing := types.INVALID
@@ -3152,8 +3219,8 @@ parse_enum :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) {
_ = finish_statement(parser) _ = finish_statement(parser)
return return
} }
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)
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing, package_hidden) { 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)) source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
} }
if explicit_backing && deferred { if explicit_backing && deferred {
@@ -3186,6 +3253,9 @@ decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
case 'r': value = '\r' case 'r': value = '\r'
case 't': value = '\t' case 't': value = '\t'
case '0': value = 0 case '0': value = 0
case 'x':
value = decode_hex_digit(text[index+1])*16 + decode_hex_digit(text[index+2])
index += 2
case: case:
} }
} }
@@ -3282,10 +3352,38 @@ parse_test :: proc(parser: ^Parser, name: token.Token) {
} }
parse_top_level :: proc(parser: ^Parser) { 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 current(parser).kind == .Keyword_Test && peek(parser).kind == .Keyword_Import {
if package_hidden { if has_modifier {
source.add(parser.diagnostics, hide_token.span, "test imports cannot use 'hide'") source.add(parser.diagnostics, modifier.span, "test imports cannot use a visibility modifier")
} }
start := advance(parser) start := advance(parser)
advance(parser) // consume 'import' advance(parser) // consume 'import'
@@ -3293,15 +3391,15 @@ parse_top_level :: proc(parser: ^Parser) {
return return
} }
if current(parser).kind == .Keyword_Import { if current(parser).kind == .Keyword_Import {
if package_hidden { if has_modifier {
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'") source.add(parser.diagnostics, modifier.span, "imports are already file-local and cannot use a visibility modifier")
} }
start := advance(parser) start := advance(parser)
parse_import(parser, token.Token{}, start) parse_import(parser, token.Token{}, start)
return return
} }
if current(parser).kind != .Identifier { 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) source.add(parser.diagnostics, current(parser).span, message)
for current(parser).kind != .Newline && current(parser).kind != .Eof { for current(parser).kind != .Newline && current(parser).kind != .Eof {
advance(parser) advance(parser)
@@ -3311,8 +3409,8 @@ parse_top_level :: proc(parser: ^Parser) {
} }
name := advance(parser) name := advance(parser)
if current(parser).kind == .Keyword_Test { if current(parser).kind == .Keyword_Test {
if package_hidden { if has_modifier {
source.add(parser.diagnostics, hide_token.span, "test declarations cannot use 'hide'") source.add(parser.diagnostics, modifier.span, "test declarations cannot use a visibility modifier")
} }
parse_test(parser, name) parse_test(parser, name)
return return
@@ -3322,8 +3420,8 @@ parse_top_level :: proc(parser: ^Parser) {
advance(parser) advance(parser)
skip_newlines(parser) skip_newlines(parser)
if current(parser).kind == .Keyword_Import { if current(parser).kind == .Keyword_Import {
if package_hidden { if has_modifier {
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'") source.add(parser.diagnostics, modifier.span, "imports are already file-local and cannot use a visibility modifier")
} }
start := advance(parser) start := advance(parser)
parse_import(parser, name, start) 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 { if current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func {
c_abi := 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 return
} }
type_syntax := types.INVALID 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), source.add(parser.diagnostics, span_from(name.span, current(parser).span),
"function declarations do not use '::'; write 'name func(...)' or 'name c_func(...)'") "function declarations do not use '::'; write 'name func(...)' or 'name c_func(...)'")
c_abi := 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 return
} }
if operator.kind == .Colon_Colon && if operator.kind == .Colon_Colon &&
(current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) { (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 return
} }
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque { if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque {
parse_opaque(parser, name, package_hidden) parse_opaque(parser, name, visibility)
return return
} }
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union { 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 return
} }
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum { if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
parse_enum(parser, name, package_hidden) parse_enum(parser, name, visibility)
return return
} }
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct { if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
parse_distinct(parser, name, package_hidden) parse_distinct(parser, name, visibility)
return return
} }
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias { if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias {
parse_alias(parser, name, package_hidden) parse_alias(parser, name, visibility)
return return
} }
@@ -3398,7 +3496,7 @@ parse_top_level :: proc(parser: ^Parser) {
name=name.symbol, name=name.symbol,
pkg=parser.pkg, pkg=parser.pkg,
file=parser.file, file=parser.file,
package_hidden=package_hidden, visibility=visibility,
type=type_syntax, type=type_syntax,
immutable=operator.kind == .Colon_Colon, immutable=operator.kind == .Colon_Colon,
expr=expr, expr=expr,
@@ -3419,9 +3517,9 @@ parse :: proc(
diagnostics=diagnostics, diagnostics=diagnostics,
module=ast.init_module(allocator), module=ast.init_module(allocator),
} }
parser.hidden_names.allocator = allocator parser.local_names.allocator = allocator
defer delete(parser.hidden_names) defer delete(parser.local_names)
collect_hidden_names(&parser) collect_local_names(&parser)
skip_newlines(&parser) skip_newlines(&parser)
for current(&parser).kind != .Eof { for current(&parser).kind != .Eof {
parse_top_level(&parser) parse_top_level(&parser)
@@ -3446,9 +3544,9 @@ parse_into :: proc(
pkg=pkg, pkg=pkg,
file=file, file=file,
} }
parser.hidden_names.allocator = module.allocator parser.local_names.allocator = module.allocator
defer delete(parser.hidden_names) defer delete(parser.local_names)
collect_hidden_names(&parser) collect_local_names(&parser)
skip_newlines(&parser) skip_newlines(&parser)
for current(&parser).kind != .Eof { for current(&parser).kind != .Eof {
parse_top_level(&parser) parse_top_level(&parser)
+1 -1
View File
@@ -175,7 +175,7 @@ append_runner :: proc(
for entry, index in tests { for entry, index in tests {
test := module.functions[entry.function] test := module.functions[entry.function]
alias := fmt.tprintf("__brolang_test_%d", index) 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") strings.write_string(&builder, "{\n\t")
fmt.sbprintf(&builder, "%s.%s() catch |_| ", alias, symbol.resolve(symbols, test.name)) fmt.sbprintf(&builder, "%s.%s() catch |_| ", alias, symbol.resolve(symbols, test.name))
strings.write_string(&builder, "{\n\t\treturn .expectation_failed\n\t}\n}\n\n") strings.write_string(&builder, "{\n\t\treturn .expectation_failed\n\t}\n}\n\n")
-1
View File
@@ -72,7 +72,6 @@ Kind :: enum u8 {
Keyword_Distinct, Keyword_Distinct,
Keyword_Alias, Keyword_Alias,
Keyword_Import, Keyword_Import,
Keyword_Hide,
Keyword_Return, Keyword_Return,
Keyword_Try, Keyword_Try,
Keyword_Catch, Keyword_Catch,
+100 -23
View File
@@ -56,6 +56,12 @@ Numeric_Category :: enum u8 {
Float, Float,
} }
Visibility :: enum u8 {
Public,
Package,
File,
}
Kind :: enum u8 { Kind :: enum u8 {
Invalid, Invalid,
Void, Void,
@@ -109,7 +115,7 @@ Node :: struct {
tuple: bool, tuple: bool,
opaque: bool, opaque: bool,
declared: bool, declared: bool,
package_hidden: bool, visibility: Visibility,
explicit_backing: bool, explicit_backing: bool,
} }
@@ -193,13 +199,21 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
return id return id
} }
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff, package_hidden := false) -> Type { named :: proc(
normalized_file := file if qualifier != 0 else u32(0) 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 { for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct || if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) && existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && 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) return DYNAMIC_START+Type(index)
} }
} }
@@ -209,22 +223,41 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf
name=name, name=name,
qualifier=qualifier, qualifier=qualifier,
file=normalized_file, file=normalized_file,
visibility=visibility,
}) })
} }
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type { find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
fallback := INVALID
for existing, index in store.nodes { for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct || if (existing.kind != .Named && existing.kind != .Alias && existing.kind != .Distinct &&
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) && existing.kind != .Enum && existing.kind != .Struct && existing.kind != .Union) ||
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && existing.pkg != pkg || existing.name != name || existing.qualifier != qualifier {
(!existing.package_hidden || file != 0xffff_ffff) { continue
return DYNAMIC_START+Type(index) }
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) existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared { if !ok || existing.kind != .Named || existing.declared {
return false return false
@@ -232,12 +265,12 @@ define_alias :: proc(store: ^Store, id, child: Type, package_hidden := false) ->
index := int(id-DYNAMIC_START) index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Alias store.nodes[index].kind = .Alias
store.nodes[index].child = child store.nodes[index].child = child
store.nodes[index].package_hidden = package_hidden store.nodes[index].visibility = visibility
store.nodes[index].declared = true store.nodes[index].declared = true
return 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) existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared { if !ok || existing.kind != .Named || existing.declared {
return false return false
@@ -245,12 +278,12 @@ define_distinct :: proc(store: ^Store, id, child: Type, package_hidden := false)
index := int(id-DYNAMIC_START) index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Distinct store.nodes[index].kind = .Distinct
store.nodes[index].child = child store.nodes[index].child = child
store.nodes[index].package_hidden = package_hidden store.nodes[index].visibility = visibility
store.nodes[index].declared = true store.nodes[index].declared = true
return 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) existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared { if !ok || existing.kind != .Named || existing.declared {
return false 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_start = u32(len(store.enum_members))
store.nodes[index].field_count = u32(len(members)) store.nodes[index].field_count = u32(len(members))
store.nodes[index].explicit_backing = explicit_backing store.nodes[index].explicit_backing = explicit_backing
store.nodes[index].package_hidden = package_hidden store.nodes[index].visibility = visibility
store.nodes[index].declared = true store.nodes[index].declared = true
append(&store.enum_members, ..members) append(&store.enum_members, ..members)
return true return true
@@ -278,7 +311,7 @@ define_record :: proc(
tag: Type = INVALID, tag: Type = INVALID,
declared_tag: Type = INVALID, declared_tag: Type = INVALID,
tuple := false, tuple := false,
package_hidden := false, visibility := Visibility.Public,
) -> bool { ) -> bool {
existing, ok := node(store, id) existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) || 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].c_layout = c_layout
store.nodes[index].tuple = tuple store.nodes[index].tuple = tuple
store.nodes[index].opaque = opaque 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].declared = true
store.nodes[index].explicit_size = explicit_size store.nodes[index].explicit_size = explicit_size
store.nodes[index].explicit_alignment = explicit_alignment store.nodes[index].explicit_alignment = explicit_alignment
@@ -636,6 +669,43 @@ fields_for :: proc(store: ^Store, value: Type) -> []Field {
return store.fields[start:end] 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 { params_for :: proc(store: ^Store, value: Type) -> []Field {
item, ok := node(store, value) item, ok := node(store, value)
if !ok || item.kind != .Function { if !ok || item.kind != .Function {
@@ -1709,11 +1779,18 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
} }
offset: u64 offset: u64
max_align: u64 = 1 max_align: u64 = 1
for field in fields_for(store, value) { if !item.c_layout {
field_align := u64(alignment_of(field.type, store, selected)) for field in fields_for(store, value) {
offset = (offset+field_align-1)/field_align*field_align offset += size(field.type, store, selected)
offset += size(field.type, store, selected) max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
max_align = max(max_align, field_align) }
} 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 return (offset+max_align-1)/max_align*max_align
case .Union: case .Union:
+486 -31
View File
@@ -1257,6 +1257,29 @@ main func() void {}
testing.expect_value(t, module.imports[2].path, "dir\"name\\tail") 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) @(test)
parser_accepts_chained_field_access :: proc(t: ^testing.T) { parser_accepts_chained_field_access :: proc(t: ^testing.T) {
text := `main func() void { text := `main func() void {
@@ -1278,7 +1301,7 @@ parser_accepts_chained_field_access :: proc(t: ^testing.T) {
@(test) @(test)
lexer_diagnoses_invalid_import_strings :: proc(t: ^testing.T) { 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} source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file) diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics) 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) stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items) defer delete(stream.items)
testing.expect_value(t, len(diagnostics.items), 2) testing.expect_value(t, len(diagnostics.items), 4)
} }
@(test) @(test)
@@ -1867,6 +1890,92 @@ many_item_pointer_slices_require_end_bound :: proc(t: ^testing.T) {
testing.expect_value(t, end_bound_errors, 2) 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) @(test)
layout_builtins_compile_and_run :: proc(t: ^testing.T) { layout_builtins_compile_and_run :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-layout-builtins" directory := "/tmp/brolang-test-layout-builtins"
@@ -1887,6 +1996,18 @@ Bool_Last :: struct {
flag bool 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 Opaque :: opaque
Color :: enum { Color :: enum {
red red
@@ -1937,12 +2058,19 @@ main func() i32 {
if (sizeof!(bool) != 1) return 15 if (sizeof!(bool) != 1) return 15
if (sizeof!(Bool_First) != 2) return 16 if (sizeof!(Bool_First) != 2) return 16
if (sizeof!(Bool_Last) != 2) return 17 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() first :: make_bool_first()
if (!first.flag) return 18 if (!first.flag) return 24
if (first.byte != 41) return 19 if (first.byte != 41) return 25
last :: make_bool_last() last :: make_bool_last()
if (!last.flag) return 20 if (!last.flag) return 26
if (last.byte != 42) return 21 if (last.byte != 42) return 27
return 0 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()")) 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) @(test)
milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T) { milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T) {
sources := source.init_store() sources := source.init_store()
@@ -3364,11 +3686,11 @@ system func() Io { return Io {value = 0} }
process_source=`io :: import "@std/io" process_source=`io :: import "@std/io"
Init :: struct { io io.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 } 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" process_source=`io :: import "@std/io"
Init :: struct { io io.Io, extra i32 } Init :: struct { io io.Io, extra i32 }
@@ -5894,7 +6216,7 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
} }
cases := [?]Case{ cases := [?]Case{
{"examples/programs/index_signed_error", "/tmp/brolang-test-index-signed-error"}, {"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/scalar_cast_error", "/tmp/brolang-test-scalar-cast-error"},
{"examples/programs/array_const_size_error", "/tmp/brolang-test-array-const-size-error"}, {"examples/programs/array_const_size_error", "/tmp/brolang-test-array-const-size-error"},
} }
@@ -6953,6 +7275,51 @@ main func() i32 {
testing.expect_value(t, state.exit_code, 0) testing.expect_value(t, state.exit_code, 0)
} }
@(test)
struct_error_channels_compile_and_run_at_runtime_and_comptime :: proc(t: ^testing.T) {
text := `ScanError :: struct {
code i32
start usize
end usize
}
scan func(fail bool) i32 ! ScanError {
if fail {
return ScanError { code = 5, start = 7, end = 11 }
}
return 41
}
scan_local func() i32 ! ScanError {
err ScanError := ScanError { code = 3, start = 13, end = 17 }
return err
}
forward func(fail bool) i32 ! ScanError {
return try scan(fail)
}
score_comptime func() i32 {
return scan(true) catch |err| err.code + i32(err.start) + i32(err.end)
}
main func() i32 {
direct :: scan(true) catch |err| err.code + i32(err.start) + i32(err.end)
local :: scan_local() catch |err| err.code + i32(err.start) + i32(err.end)
forwarded :: forward(true) catch |err| err.code + i32(err.start) + i32(err.end)
success :: forward(false) catch 0
comptime_score i32 :: $score_comptime()
return direct + local + forwarded + success + comptime_score - 143
}
`
directory := "/tmp/brolang-test-struct-error-channel"
main_path := "/tmp/brolang-test-struct-error-channel/main.bro"
output := "/tmp/brolang-test-struct-error-channel-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test) @(test)
matched_error_residuals_return_at_runtime_and_comptime :: proc(t: ^testing.T) { matched_error_residuals_return_at_runtime_and_comptime :: proc(t: ^testing.T) {
text := `KeyError :: enum { key_exists } text := `KeyError :: enum { key_exists }
@@ -10106,14 +10473,18 @@ imports_are_file_local :: proc(t: ^testing.T) {
} }
@(test) @(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{ cases := [?]string{
`hide import "../dep"`, `@hide import "../dep"`,
`hide dep :: import "../dep"`, `@hide:file dep :: import "../dep"`,
`hide func() void {}`, `@hide func() void {}`,
`main func(hide value i32) void {}`, `main func(@hide value i32) void {}`,
`Box :: struct { hide i32 }`, `Box :: struct { @hide i32 }`,
`main func() void { hide value i32 := 1 }`, `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 { for text in cases {
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
@@ -10131,17 +10502,17 @@ hide_is_rejected_outside_named_top_level_declarations :: proc(t: ^testing.T) {
} }
@(test) @(test)
hide_declarations_are_package_hidden :: proc(t: ^testing.T) { local_declarations_resolve_at_their_declared_scope :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-hidden-declarations" output := "/tmp/brolang-test-local-declarations"
defer _ = os.remove(output) defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/hidden_valid/app", output) status := compiler_core.compile_package("examples/packages/hidden_valid/app", output)
testing.expect_value(t, status, 0) testing.expect_value(t, status, 0)
state := run_executable(output) state := run_executable(output)
testing.expect_value(t, state.exit_code, 9) testing.expect_value(t, state.exit_code, 15)
} }
@(test) @(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() sources := source.init_store()
defer source.destroy_store(&sources) defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources) diagnostics := source.init_store_diagnostics(&sources)
@@ -10159,18 +10530,27 @@ package_hidden_declarations_resolve_locally_but_not_through_imports :: proc(t: ^
found_sibling_type := false found_sibling_type := false
found_import := false found_import := false
found_collision := false found_collision := false
found_file_sibling := false
found_file_sibling_value := false
found_file_sibling_type := false
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
found_sibling = found_sibling || strings.contains(diagnostic.message, "unknown symbol 'sibling'") 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_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_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_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_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)
testing.expect(t, !found_sibling_value) testing.expect(t, !found_sibling_value)
testing.expect(t, !found_sibling_type) testing.expect(t, !found_sibling_type)
testing.expect(t, found_import) testing.expect(t, found_import)
testing.expect(t, found_collision) 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) @(test)
@@ -10297,7 +10677,7 @@ MaybePoint :: alias ?@dep.Point
Concrete :: alias dep.Box(i32) Concrete :: alias dep.Box(i32)
` `
hidden_text := `dep :: import "../dep" hidden_text := `dep :: import "../dep"
hide local_answer_alias :: alias dep.answer @hide local_answer_alias :: alias dep.answer
` `
top_text := `facade :: import "../facade" top_text := `facade :: import "../facade"
Box :: alias facade.RenamedBox Box :: alias facade.RenamedBox
@@ -10421,7 +10801,7 @@ declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) {
testing.expect(t, os.make_directory(directory) == nil) testing.expect(t, os.make_directory(directory) == nil)
} }
dep_text := `visible func() i32 { return 1 } 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 func() i32 { return 3 }
ambiguous i32 :: 4 ambiguous i32 :: 4
` `
@@ -10465,7 +10845,7 @@ main func() void {}
testing.expect(t, loaded) testing.expect(t, loaded)
wants := []string{ wants := []string{
"has no member 'missing'", "has no member 'missing'",
"is package-hidden", "is not public",
"unknown symbol 'nope'", "unknown symbol 'nope'",
"unavailable imported package 'gone'", "unavailable imported package 'gone'",
"package member 'dep.ambiguous' is ambiguous", "package member 'dep.ambiguous' is ambiguous",
@@ -11243,6 +11623,39 @@ braceless_while_compiles_and_runs :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 42) 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) @(test)
parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) { parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) {
text := `main func() void { text := `main func() void {
@@ -13894,7 +14307,6 @@ main func() i32 {
@(test) @(test)
keywords_are_valid_enum_members_and_tagged_union_variants :: proc(t: ^testing.T) { 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_Func))
testing.expect(t, token.is_keyword(.Keyword_Hide))
testing.expect(t, token.is_keyword(.Keyword_C_Longdouble)) testing.expect(t, token.is_keyword(.Keyword_C_Longdouble))
testing.expect(t, !token.is_keyword(.Identifier)) testing.expect(t, !token.is_keyword(.Identifier))
testing.expect(t, !token.is_keyword(.Underscore)) testing.expect(t, !token.is_keyword(.Underscore))
@@ -13909,6 +14321,9 @@ Token :: union(TokenKind) {
else void else void
return i32 return i32
} }
hide func() i32 { return 0 }
package func() i32 { return 0 }
file func() i32 { return 0 }
kind func(value bool) TokenKind { kind func(value bool) TokenKind {
if value { if value {
return .if return .if
@@ -13921,7 +14336,7 @@ main func() i32 {
a Token := Token{ if = 1 } a Token := Token{ if = 1 }
b Token := Token{ else } b Token := Token{ else }
c Token := .return{2} c Token := .return{2}
total i32 := a.if + c.return total i32 := a.if + c.return + hide() + package() + file()
match first { match first {
.if: total = total + 1 .if: total = total + 1
.else: total = total + 2 .else: total = total + 2
@@ -14771,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")) 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) @(test)
poisoned_global_and_local_types_do_not_create_inference_fallbacks :: proc(t: ^testing.T) { poisoned_global_and_local_types_do_not_create_inference_fallbacks :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-poisoned-declarations" directory := "/tmp/brolang-test-poisoned-declarations"
@@ -14865,7 +15308,19 @@ exercise func(cursor usize, narrow i8, wider i16, count i32) i32 {
return 0 return 0
} }
main func() i32 { return exercise(7, 1, 1000, 3) } scan_open_cursor func() void {
cursor := 0
first Token := Token{start = cursor}
second Token := Token{start = cursor}
_ = first
_ = second
}
main func() i32 {
result := exercise(7, 1, 1000, 3)
scan_open_cursor()
return result
}
` `
source_file := source.Source{path="record_constraints.bro", text=text} source_file := source.Source{path="record_constraints.bro", text=text}
diagnostics := source.init_diagnostics(&source_file) diagnostics := source.init_diagnostics(&source_file)
@@ -15136,12 +15591,12 @@ dependency_passes test {
defer delete(stderr) defer delete(stderr)
output := string(stderr) output := string(stderr)
testing.expect_value(t, state.exit_code, 1) 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_passes...[\x1b[92mok\x1b[0m]"))
testing.expect(t, strings.contains(output, "root.root_fails...[failed]")) 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, "expected 42, found 41"))
testing.expect(t, strings.contains(output, "root.root_continues...[ok]")) testing.expect(t, strings.contains(output, "root.root_continues...[\x1b[92mok\x1b[0m]"))
testing.expect(t, strings.contains(output, "root.root_errors...[failed]")) testing.expect(t, strings.contains(output, "root.root_errors...[\x1b[91mfailure\x1b[0m]"))
testing.expect(t, strings.contains(output, "dependency.dependency_passes...[ok]")) 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, root_path))
testing.expect(t, strings.contains(output, "3 passed, 2 failed")) testing.expect(t, strings.contains(output, "3 passed, 2 failed"))
} }
+13 -3
View File
@@ -1,12 +1,22 @@
hide sibling func() i32 { @hide sibling func() i32 {
return 1 return 1
} }
hide Sibling :: struct { @hide Sibling :: struct {
value i32 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 { collision c_func() i32 {
return 1 return 1
+9 -3
View File
@@ -1,6 +1,6 @@
import "../dep" import "../dep"
hide collision func() i32 { @hide collision func() i32 {
return 2 return 2
} }
@@ -12,6 +12,12 @@ read_sibling_value func() i32 {
return sibling_value return sibling_value
} }
main func() i32 { read_file_sibling func(value File_Sibling) i32 {
return sibling() + dep.secret() + read_sibling(Sibling { value = 1 }) + read_sibling_value() 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 -1
View File
@@ -1,3 +1,3 @@
hide secret c_func() i32 { @hide:package secret c_func() i32 {
return 1 return 1
} }
+26 -12
View File
@@ -1,26 +1,28 @@
hide helper func() i32 { @hide:package
helper func() i32 {
thing Thing := Thing { value = value } thing Thing := Thing { value = value }
return thing.value return thing.value
} }
hide Thing :: struct { @hide
Thing :: struct {
value i32 value i32
} }
hide Local_Union :: union { @hide Local_Union :: union {
value i32 value i32
} }
hide Local_Enum :: enum { @hide Local_Enum :: enum {
value value
} }
hide Local_Opaque :: opaque @hide Local_Opaque :: opaque
hide Local_Distinct :: distinct i32 @hide Local_Distinct :: distinct i32
hide Local_Alias :: alias i32 @hide Local_Alias :: alias i32
hide value :: 1 @hide value :: 1
hide mutable_value i32 := 1 @hide mutable_value i32 := 1
_foreign c_func() i32 { _foreign c_func() i32 {
return 1 return 1
@@ -30,15 +32,27 @@ _C_Record :: c_struct {
value c_int value c_int
} }
hide local_foreign c_func() i32 { @hide local_foreign c_func() i32 {
return 1 return 1
} }
hide Local_C_Record :: c_struct { @hide Local_C_Record :: c_struct {
value c_int 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 { from_a func() i32 {
record Local_C_Record := Local_C_Record { value = 0 } 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)
} }
+13 -1
View File
@@ -5,9 +5,21 @@ Box :: struct {
_value i32 _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 { from_b func(_input i32) i32 {
_local Box := Box { _value = _input } _local Box := Box { _value = _input }
record _C_Record := _C_Record { value = 0 } record _C_Record := _C_Record { value = 0 }
thing Thing := Thing { value = value } 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
} }
+5 -5
View File
@@ -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 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 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, alloc = fail_alloc,
realloc = fail_realloc, realloc = fail_realloc,
free = fail_free, free = fail_free,
} }
hide fail_allocator mem.Allocator :: mem.Allocator { @hide fail_allocator mem.Allocator :: mem.Allocator {
context = null, context = null,
vtable = &fail_vtable, vtable = &fail_vtable,
} }
+2 -2
View File
@@ -82,8 +82,8 @@ main func() i32 {
if bits != 255 { return 10 } if bits != 255 { return 10 }
values [10]u8 := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] values [10]u8 := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
if values[LocalID(4)] != 4 { return 11 } if values[usize(LocalID(4))] != 4 { return 11 }
section []u8 := values[LocalID(2)..LocalID(5)] 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 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 } if u32(id) != 7 or usize(id) != 7 or f64(id) != 7.0 { return 13 }
@@ -1,6 +1,6 @@
main func() i32 { main func() i32 {
items [3]mut i32 := undefined items [3]mut i32 := undefined
i int := 1 i u32 := 1
items[i] = 42 items[i] = 42
return 0 return 0
} }
+6 -6
View File
@@ -1,7 +1,7 @@
io :: import "@std/io" io :: import "@std/io"
process :: import "@std/process" 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 { if buffer.len == 0 {
return 0 return 0
} }
@@ -13,26 +13,26 @@ hide read_ok func(_ ?@mut anyopaque, _ io.Handle, buffer []mut u8) usize ! io.Re
return 2 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 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 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 { if bytes.len > 2 {
return 2 return 2
} }
return bytes.len 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 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 return bytes.len + 1
} }
@@ -1,27 +1,27 @@
mem :: import "@std/mem" mem :: import "@std/mem"
hide probe_count func(context ?@mut anyopaque) void { @hide probe_count func(context ?@mut anyopaque) void {
if context |raw| { if context |raw| {
count @mut usize :: ptrcast!(usize, raw) count @mut usize :: ptrcast!(usize, raw)
count^ += 1 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) probe_count(context)
return null 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) probe_count(context)
return null 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) probe_count(context)
} }
hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable { @hide probe_vtable mem.AllocatorVTable :: mem.AllocatorVTable {
alloc = probe_alloc, alloc = probe_alloc,
realloc = probe_realloc, realloc = probe_realloc,
free = probe_free, free = probe_free,
+1 -1
View File
@@ -52,7 +52,7 @@ main func() i32 {
items[0] = 10 items[0] = 10
items[1] = 20 items[1] = 20
idx u8 := 2 idx usize := 2
items[idx] = items[0] + items[1] items[idx] = items[0] + items[1]
if (items[2] != 30) return 1 if (items[2] != 30) return 1
+1 -1
View File
@@ -10,7 +10,7 @@ print func($format []u8, $Args type, args Args) void {
io.print(writer, format, Args, args) catch |_| {} 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 request usize := bytes.len
maximum usize :: usize(maxval!(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { if request > maximum {
+3 -3
View File
@@ -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 # mapping both 0 and 1 to 1 is safe because equality resolves
# collisions (since hash and key must both be equal). # collisions (since hash and key must both be equal).
if (hash == 0) return 1 if (hash == 0) return 1
@@ -143,7 +143,7 @@ hide normalize func(hash usize) usize {
#! FNV-1a hash implementation. #! FNV-1a hash implementation.
#! note: vulnerable to collision attacks. #! note: vulnerable to collision attacks.
hide str_hash func(key []u8) usize { @hide str_hash func(key []u8) usize {
hash u32 := 2166136261 # offset basis hash u32 := 2166136261 # offset basis
prime u32 := 16777619 prime u32 := 16777619
@@ -155,6 +155,6 @@ hide str_hash func(key []u8) usize {
return usize(hash) return usize(hash)
} }
hide str_eql func(a, b []u8) bool { @hide str_eql func(a, b []u8) bool {
return mem.eql(a, b) return mem.eql(a, b)
} }
+9 -9
View File
@@ -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 request usize := buffer.len
maximum usize :: usize(maxval!(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { 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 request usize := bytes.len
maximum usize :: usize(maxval!(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { 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 flags c_int := c.O_RDONLY
match mode { match mode {
.read_only: flags = c.O_RDONLY .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 { if c.close(handle.file_desc) != 0 {
return .close_failed 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)} 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)} 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)} return Handle {file_desc = c_int(Stream.stderr)}
} }
hide system_vtable IoVTable :: IoVTable { @hide system_vtable IoVTable :: IoVTable {
read = system_read, read = system_read,
write = system_write, write = system_write,
open = system_open, open = system_open,
@@ -136,7 +136,7 @@ hide system_vtable IoVTable :: IoVTable {
stderr = system_stderr, stderr = system_stderr,
} }
hide system func() Io { @hide system func() Io {
return Io { return Io {
context = null, context = null,
vtable = &system_vtable, vtable = &system_vtable,
+13 -13
View File
@@ -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 buffer [65]mut u8 := undefined
end usize := buffer.len end usize := buffer.len
current i64 := value current i64 := value
@@ -161,7 +161,7 @@ hide write_integer_signed func(output Writer, value i64, base u64, uppercase boo
return 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 buffer [65]mut u8 := undefined
end usize := buffer.len end usize := buffer.len
current u64 := value current u64 := value
@@ -184,7 +184,7 @@ hide write_integer_unsigned func(output Writer, value u64, base u64, uppercase b
return return
} }
hide FormatTokenKind :: enum { @hide FormatTokenKind :: enum {
unused unused
literal literal
default default
@@ -198,14 +198,14 @@ hide FormatTokenKind :: enum {
scientific scientific
} }
hide FormatToken :: struct { @hide FormatToken :: struct {
kind FormatTokenKind kind FormatTokenKind
start usize start usize
end usize end usize
field []u8 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 tokens [N]mut FormatToken := undefined
for (usize(0))..format.len |index| { for (usize(0))..format.len |index| {
tokens[index] = FormatToken {kind = .unused, start = 0, end = 0, field = ""} 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 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) { match typeinfo!(T) {
.record |record|: return record.fields[index].name .record |record|: return record.fields[index].name
else: compile_error!("io.print arguments must be a tuple") 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)^ 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) { match typeinfo!(T) {
.bool: return true .bool: return true
.integer: 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) { match typeinfo!(T) {
.integer: if minval!(T) < 0 { .integer: if minval!(T) < 0 {
try write_integer_signed(output, i64(value), base, uppercase) 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. # 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) { match typeinfo!(T) {
.float: { .float: {
buffer [64]mut u8 := undefined buffer [64]mut u8 := undefined
@@ -381,7 +381,7 @@ hide write_float func(output Writer, $T type, value T, scientific bool) void ! W
return 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) { match typeinfo!(T) {
.integer: try write_integer(output, value, 10, false) .integer: try write_integer(output, value, 10, false)
.float: try write_float(output, value, 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 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) { match typeinfo!(T) {
.integer: { .integer: {
if minval!(T) < 0 or maxval!(T) > 255 { 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 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) { match typeinfo!(T) {
.bool: if value { .bool: if value {
try write_all(output, "true") try write_all(output, "true")
+7 -7
View File
@@ -119,11 +119,11 @@ empty func($T type) []mut T {
return empty_slice(T, 0) 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 if (value == 0) return false
current usize := value current usize := value
while current > 1 { while current > 1 {
@@ -134,7 +134,7 @@ hide power_of_two func(value usize) bool {
return true 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 (power_of_two(alignment) == false) return null
if alignment <= malloc_alignment { 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]) 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 (power_of_two(alignment) == false) return null
if new_size == 0 { 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) 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) c.free(memory)
} }
hide c_vtable AllocatorVTable :: AllocatorVTable { @hide c_vtable AllocatorVTable :: AllocatorVTable {
alloc = c_alloc, alloc = c_alloc,
realloc = c_realloc, realloc = c_realloc,
free = c_free, free = c_free,
+2 -2
View File
@@ -13,13 +13,13 @@ TestOuter :: distinct TestInner
TestOuterAlias :: alias TestOuter 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) { match typeinfo!(Array) {
.array |info|: return info.child == Child and info.len == len .array |info|: return info.child == Child and info.len == len
else: return false else: return false
} }
} }
hide distinct_info_matches func($Distinct, $Backing type) bool { @hide distinct_info_matches func($Distinct, $Backing type) bool {
match typeinfo!(Distinct) { match typeinfo!(Distinct) {
.distinct |backing|: return backing == Backing .distinct |backing|: return backing == Backing
else: return false else: return false
+1 -1
View File
@@ -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 } return struct { []u8, V }
} }
+2 -2
View File
@@ -73,10 +73,10 @@ expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) voi
run func(name []u8, callback *func() void ! Error) bool { run func(name []u8, callback *func() void ! Error) bool {
callback() catch |_| { callback() catch |_| {
debug.print("{s}...[failed]\n", {name,}) debug.print("{s}...[\x1b[91mfailure\x1b[0m]\n", {name,})
return false return false
} }
debug.print("{s}...[ok]\n", {name,}) debug.print("{s}...[\x1b[92mok\x1b[0m]\n", {name,})
return true return true
} }
+6 -6
View File
@@ -20,17 +20,17 @@ Token :: struct {
kind Kind kind Kind
} }
hide is_alpha func(value u8) bool { @hide is_alpha func(value u8) bool {
return value == '_' or return value == '_' or
value >= 'a' and value <= 'z' or value >= 'a' and value <= 'z' or
value >= 'A' and value <= 'Z' 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' 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. # 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") { if mem.eql(word, "func") or mem.eql(word, "void") {
return .keyword return .keyword
@@ -38,7 +38,7 @@ hide word_kind func(word []u8) Kind {
return .identifier 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 { try arraylist.append(tokens, Token {
start = start, start = start,
length = end - start, length = end - start,
@@ -95,7 +95,7 @@ lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
return return
} }
hide kind_name func(kind Kind) *c_char { @hide kind_name func(kind Kind) *c_char {
return match kind { return match kind {
.invalid: "invalid" .invalid: "invalid"
.eof: "eof" .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)) _ = c.printf("%-11s", kind_name(token.kind))
if token.length != 0 { if token.length != 0 {
_ = c.printf(" `") _ = c.printf(" `")
+4 -4
View File
@@ -74,7 +74,7 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
return 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 request usize := buffer.len
maximum usize :: usize(maxval!(c_long)) maximum usize :: usize(maxval!(c_long))
if request > maximum { 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) fd c_int :: c_int(stream)
request usize := bytes.len request usize := bytes.len
maximum usize :: usize(maxval!(c_long)) 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, read = system_read,
write = system_write, write = system_write,
} }
hide system func() Io { @hide system func() Io {
return Io { return Io {
context = null, context = null,
vtable = &system_vtable, vtable = &system_vtable,
+8 -8
View File
@@ -41,9 +41,9 @@ eql func($T type, left, right []T) bool {
return true 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) pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
return pointer[..count] 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 { if value == 0 {
return false return false
} }
@@ -135,7 +135,7 @@ hide power_of_two func(value usize) bool {
return true 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 { if power_of_two(alignment) == false {
return null return null
} }
@@ -153,7 +153,7 @@ hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return ptrcast!(u8, memory[0]) 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 { if power_of_two(alignment) == false {
return null 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) 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) c.free(memory)
} }
hide c_vtable AllocatorVTable :: AllocatorVTable { @hide c_vtable AllocatorVTable :: AllocatorVTable {
alloc = c_alloc, alloc = c_alloc,
realloc = c_realloc, realloc = c_realloc,
free = c_free, free = c_free,
+8 -8
View File
@@ -27,9 +27,9 @@ raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize)
allocator.vtable.free(allocator.context, memory, size, alignment) 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) pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
return pointer[..count] 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 { if value == 0 {
return false return false
} }
@@ -80,7 +80,7 @@ hide power_of_two func(value usize) bool {
return true 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 { if power_of_two(alignment) == false {
return null return null
} }
@@ -98,7 +98,7 @@ hide c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
return ptrcast!(u8, memory[0]) 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 { if power_of_two(alignment) == false {
return null 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) 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) c.free(memory)
} }
hide c_vtable AllocatorVTable :: AllocatorVTable { @hide c_vtable AllocatorVTable :: AllocatorVTable {
alloc = c_alloc, alloc = c_alloc,
realloc = c_realloc, realloc = c_realloc,
free = c_free, free = c_free,