Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef91f13e7b | |||
| b09787029d |
+11
-4
@@ -18,11 +18,13 @@ roadmap and milestone history.
|
|||||||
- `hide` makes any named top-level declaration file-local; declarations are public by default,
|
- `hide` makes any named top-level declaration file-local; declarations are public by default,
|
||||||
leading underscores are ordinary identifier characters, and imports are always file-local
|
leading underscores are ordinary identifier characters, 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 implicit fallible-void results, plus anonymous
|
||||||
|
transitive `test import "..."` discovery used only by test builds
|
||||||
- root `main` validation with trap executable recovery for missing or unusable entry points
|
- root `main` validation with trap executable recovery for missing or unusable entry points
|
||||||
|
|
||||||
### scalar, aggregate, and pointer types
|
### scalar, aggregate, and pointer types
|
||||||
|
|
||||||
- exact-width integers, `isize`, `usize`, `f32`, `f64`, `bool`, `void`, `anyopaque`, and contextual `int`, `float`, and `range` constraints
|
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, and `anyopaque`; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
|
||||||
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
|
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
|
||||||
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
|
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
|
||||||
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
|
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
|
||||||
@@ -42,16 +44,17 @@ roadmap and milestone history.
|
|||||||
|
|
||||||
#### native record constraint fields
|
#### native record constraint fields
|
||||||
|
|
||||||
A direct `int`, `float`, or `range` field in a named native struct or union is a
|
A direct `int`, `uint`, `float`, or `range` field in a named native struct or union is a
|
||||||
program-wide constraint, not per-value polymorphism. Before record layout, all reachable keyed
|
program-wide constraint, not per-value polymorphism. Before record layout, all reachable keyed
|
||||||
constructors, field assignments, and concrete uses of field reads contribute demands and the field
|
constructors, field assignments, and concrete uses of field reads contribute demands and the field
|
||||||
resolves once to one concrete runtime type. Compatible scalar demands widen normally. Integer
|
resolves once to one concrete runtime type. Compatible scalar demands widen normally. Integer
|
||||||
literals remain provisional until inference settles, so a later `usize` use can resolve an `int`
|
literals remain provisional until inference settles, so a later `usize` use can resolve an `int`
|
||||||
field to `usize`; otherwise literal-only `int` fields use the widest smallest-signed type required,
|
field to `usize`; otherwise literal-only `int` fields use the widest smallest-signed type required,
|
||||||
and literal-only `float` fields use `f64`.
|
literal-only `uint` fields use the widest smallest-unsigned type required, and literal-only `float`
|
||||||
|
fields use `f64`.
|
||||||
|
|
||||||
An undemanded field or incompatible demands are errors. This inference applies only to direct
|
An undemanded field or incompatible demands are errors. This inference applies only to direct
|
||||||
fields of named native records. `c_struct` fields, nested constraints such as `[]int`, and fields in
|
fields of named native records. `c_struct` fields, nested constraints such as `[]int` / `[]uint`, and fields in
|
||||||
anonymous generated records still require concrete runtime types.
|
anonymous generated records still require concrete runtime types.
|
||||||
|
|
||||||
#### keyword member names
|
#### keyword member names
|
||||||
@@ -180,6 +183,8 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
|||||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
||||||
- `std/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` and expected-first `expect_equal`; direct calls through
|
||||||
|
an alias of exactly `@std/testing` receive compiler-injected source locations
|
||||||
|
|
||||||
### compiler behavior
|
### compiler behavior
|
||||||
|
|
||||||
@@ -188,6 +193,8 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
|||||||
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
|
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
|
||||||
- demand-driven LLVM declarations for referenced foreign functions
|
- demand-driven LLVM declarations for referenced foreign functions
|
||||||
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point obtains the hidden system I/O provider and constructs the init value
|
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point obtains the hidden system I/O provider and constructs the init value
|
||||||
|
- `brolang test [root]` reuses `build.bro`, discovers only explicit test-import edges, skips the
|
||||||
|
application entry point, and runs tests sequentially while continuing after assertion failures
|
||||||
- replaceable dynamically loaded libclang C-import backend
|
- replaceable dynamically loaded libclang C-import backend
|
||||||
- C-header import caching by canonical path, target, include paths, and defines
|
- C-header import caching by canonical path, target, include paths, and defines
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,27 @@ linker inputs) and, like those flags, their paths are relative to the invocation
|
|||||||
directory. Lists take the address of an array literal; empty lists are written
|
directory. Lists take the address of an array literal; empty lists are written
|
||||||
`&[]`. See `examples/build/` for runnable projects.
|
`&[]`. See `examples/build/` for runnable projects.
|
||||||
|
|
||||||
|
Projects can declare tests directly and run them with `brolang test [root]`.
|
||||||
|
The command reads the same `build.bro`, writes `build/<name>-test`, and reuses
|
||||||
|
its C link inputs, libraries, include paths, and defines.
|
||||||
|
|
||||||
|
```bro
|
||||||
|
math :: import "../math"
|
||||||
|
testing :: import "@std/testing"
|
||||||
|
|
||||||
|
test import "../math"
|
||||||
|
|
||||||
|
addition test {
|
||||||
|
try testing.expect(math.add(20, 22) == 42)
|
||||||
|
try testing.expect_equal(42, math.add(20, 22))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`test import` discovers tests transitively without creating a namespace;
|
||||||
|
calling package code still requires an ordinary import. Ordinary imports do
|
||||||
|
not discover dependency tests. Assertions report their source location, a
|
||||||
|
failure ends only the current test, and the runner continues with the suite.
|
||||||
|
|
||||||
Relative `.h` imports create synthetic package namespaces backed by libclang:
|
Relative `.h` imports create synthetic package namespaces backed by libclang:
|
||||||
|
|
||||||
```bro
|
```bro
|
||||||
|
|||||||
@@ -897,6 +897,22 @@
|
|||||||
- `tag!` reads or folds a tagged union discriminant, while `tagname!` turns a comptime-known enum
|
- `tag!` reads or folds a tagged union discriminant, while `tagname!` turns a comptime-known enum
|
||||||
value into its immutable field-name string
|
value into its immutable field-name string
|
||||||
|
|
||||||
|
41. native test framework (implemented; v1)
|
||||||
|
- `name test { ... }` declares an implicit `void ! testing.Error` test omitted from executable builds
|
||||||
|
- anonymous `test import` edges discover dependency tests transitively; ordinary imports remain
|
||||||
|
separate namespaces and never discover tests
|
||||||
|
- direct `@std/testing` `expect` and expected-first `expect_equal` calls inject their source locations
|
||||||
|
- `brolang test` reuses `build.bro`, runs tests sequentially, continues after assertion failures,
|
||||||
|
prints a summary, and returns failure when a test fails or traps
|
||||||
|
- filtering, skipping, fixtures, snapshots, parallelism, isolation, allocators, and more assertion
|
||||||
|
families remain deferred
|
||||||
|
|
||||||
|
42. unsigned integer constraint (implemented)
|
||||||
|
- `uint` is the unsigned subset constraint while `int` remains the whole integer family
|
||||||
|
- literal-only values choose the smallest fitting `u8`, `u16`, `u32`, or `u64`
|
||||||
|
- native unsigned scalars and target-classified unsigned C scalars satisfy the constraint
|
||||||
|
- `isize` and `usize` remain concrete pointer-sized types
|
||||||
|
|
||||||
## A word on unchecked casts
|
## A word on unchecked casts
|
||||||
|
|
||||||
For casts that bypass safety checks, Honey provides builtin functions:
|
For casts that bypass safety checks, Honey provides builtin functions:
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ Function :: struct {
|
|||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
generated: bool,
|
generated: bool,
|
||||||
|
test: bool,
|
||||||
file_hidden: bool,
|
file_hidden: bool,
|
||||||
has_body: bool,
|
has_body: bool,
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
@@ -276,6 +277,7 @@ Import :: struct {
|
|||||||
target: Package_Id,
|
target: Package_Id,
|
||||||
valid: bool,
|
valid: bool,
|
||||||
used: bool,
|
used: bool,
|
||||||
|
test_only: bool,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,9 +312,15 @@ Package :: struct {
|
|||||||
path: string,
|
path: string,
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
available: bool,
|
available: bool,
|
||||||
|
test: bool,
|
||||||
kind: Package_Kind,
|
kind: Package_Kind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Compile_Mode :: enum u8 {
|
||||||
|
Executable,
|
||||||
|
Test,
|
||||||
|
}
|
||||||
|
|
||||||
Package_Kind :: enum u8 {
|
Package_Kind :: enum u8 {
|
||||||
Native,
|
Native,
|
||||||
C_Header,
|
C_Header,
|
||||||
|
|||||||
+69
-24
@@ -43,26 +43,7 @@ destroy_build_config :: proc(cfg: ^BuildConfig) {
|
|||||||
delete(cfg.c_options.defines)
|
delete(cfg.c_options.defines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run_build implements `brolang build [root]`: it loads and type-checks
|
load_build_config :: proc(project_root: string) -> (BuildConfig, bool) {
|
||||||
// `root/build.bro`, reads its `config` constant, and compiles the program
|
|
||||||
// package the config names. build.bro is only checked (never lowered/emitted),
|
|
||||||
// so the config is read straight from the HIR.
|
|
||||||
run_build :: proc(root: string) -> int {
|
|
||||||
project_root := root
|
|
||||||
owns_project_root := false
|
|
||||||
if len(project_root) == 0 {
|
|
||||||
found_root, found := find_build_root()
|
|
||||||
if !found {
|
|
||||||
fmt.eprintln("brolang build: could not find build.bro in the current directory or any parent")
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
project_root = found_root
|
|
||||||
owns_project_root = true
|
|
||||||
}
|
|
||||||
defer if owns_project_root {
|
|
||||||
delete(project_root)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -73,7 +54,7 @@ run_build :: proc(root: string) -> int {
|
|||||||
arena: vmem.Arena
|
arena: vmem.Arena
|
||||||
if err := vmem.arena_init_growing(&arena); err != nil {
|
if err := vmem.arena_init_growing(&arena); err != nil {
|
||||||
fmt.eprintln("failed to initialize build arena:", err)
|
fmt.eprintln("failed to initialize build arena:", err)
|
||||||
return 2
|
return {}, false
|
||||||
}
|
}
|
||||||
defer vmem.arena_destroy(&arena)
|
defer vmem.arena_destroy(&arena)
|
||||||
a := vmem.arena_allocator(&arena)
|
a := vmem.arena_allocator(&arena)
|
||||||
@@ -82,7 +63,7 @@ run_build :: proc(root: string) -> int {
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
fmt.eprintln("failed to load build root:", project_root)
|
fmt.eprintln("failed to load build root:", project_root)
|
||||||
return 2
|
return {}, false
|
||||||
}
|
}
|
||||||
// check needs no `main`: it synthesizes a trap main and emits one benign
|
// check needs no `main`: it synthesizes a trap main and emits one benign
|
||||||
// "missing main" diagnostic, which is expected for build.bro. Suppress that
|
// "missing main" diagnostic, which is expected for build.bro. Suppress that
|
||||||
@@ -90,10 +71,32 @@ run_build :: proc(root: string) -> int {
|
|||||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
|
||||||
if build_bro_has_errors(&diagnostics) {
|
if build_bro_has_errors(&diagnostics) {
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
return extract_build_config(&hir_module, &symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
project_root_for_command :: proc(root, command: string) -> (string, bool, bool) {
|
||||||
|
if len(root) > 0 {
|
||||||
|
return root, false, true
|
||||||
|
}
|
||||||
|
project_root, found := find_build_root()
|
||||||
|
if !found {
|
||||||
|
fmt.eprintfln("brolang %s: could not find build.bro in the current directory or any parent", command)
|
||||||
|
return "", false, false
|
||||||
|
}
|
||||||
|
return project_root, true, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// run_build implements `brolang build [root]`: it reads build.bro and compiles
|
||||||
|
// the configured program package.
|
||||||
|
run_build :: proc(root: string) -> int {
|
||||||
|
project_root, owned, found := project_root_for_command(root, "build")
|
||||||
|
if !found {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
defer if owned {delete(project_root)}
|
||||||
cfg, ok := extract_build_config(&hir_module, &symbols)
|
cfg, ok := load_build_config(project_root)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
@@ -109,6 +112,48 @@ run_build :: proc(root: string) -> int {
|
|||||||
return compile_package(program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root)
|
return compile_package(program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_tests :: proc(root: string) -> int {
|
||||||
|
project_root, owned, found := project_root_for_command(root, "test")
|
||||||
|
if !found {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer if owned {delete(project_root)}
|
||||||
|
cfg, ok := load_build_config(project_root)
|
||||||
|
if !ok {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer destroy_build_config(&cfg)
|
||||||
|
|
||||||
|
program := filepath.join({project_root, cfg.source_dir})
|
||||||
|
defer delete(program)
|
||||||
|
test_name := fmt.aprintf("%s-test", cfg.output_name)
|
||||||
|
defer delete(test_name)
|
||||||
|
output, output_ok := build_output_path(project_root, test_name)
|
||||||
|
if !output_ok {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer delete(output)
|
||||||
|
status := compile_package(
|
||||||
|
program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root, .Test,
|
||||||
|
)
|
||||||
|
if status != 0 {
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
state, stdout, stderr, err := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output}},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
if len(stdout) > 0 {fmt.print(string(stdout))}
|
||||||
|
if len(stderr) > 0 {fmt.eprint(string(stderr))}
|
||||||
|
if err != nil {
|
||||||
|
fmt.eprintln("failed to run test executable:", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return 0 if state.exit_code == 0 else 1
|
||||||
|
}
|
||||||
|
|
||||||
valid_output_name :: proc(name: string) -> bool {
|
valid_output_name :: proc(name: string) -> bool {
|
||||||
return len(name) > 0 && name != "." && name != ".." &&
|
return len(name) > 0 && name != "." && name != ".." &&
|
||||||
!strings.contains(name, "/") && !strings.contains(name, "\\")
|
!strings.contains(name, "/") && !strings.contains(name, "\\")
|
||||||
|
|||||||
+113
-38
@@ -501,7 +501,7 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
|
|||||||
strings.write_string(builder, "enum")
|
strings.write_string(builder, "enum")
|
||||||
case .Alias, .Distinct, .Named:
|
case .Alias, .Distinct, .Named:
|
||||||
write_type_label(checker, builder, item.child)
|
write_type_label(checker, builder, item.child)
|
||||||
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
||||||
strings.write_string(builder, types.name(value))
|
strings.write_string(builder, types.name(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1052,6 +1052,26 @@ fits_i64 :: proc(value: i128) -> bool {
|
|||||||
return fits_signed_type(value, types.I64)
|
return fits_signed_type(value, types.I64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fits_u64 :: proc(value: i128) -> bool {
|
||||||
|
return value >= 0 && value <= i128(0xffff_ffff_ffff_ffff)
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint_integer_literal_type :: proc(constraint: types.Type, value: i128) -> types.Type {
|
||||||
|
if constraint == types.UINT {
|
||||||
|
return types.smallest_unsigned_for_literal(u64(value)) if fits_u64(value) else types.INVALID
|
||||||
|
}
|
||||||
|
return types.smallest_signed_for_literal(i64(value)) if fits_i64(value) else types.INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint_recovery_type :: proc(checker: ^Checker, constraint: types.Type) -> types.Type {
|
||||||
|
switch constraint {
|
||||||
|
case types.UINT: return types.U64
|
||||||
|
case types.FLOAT: return types.F64
|
||||||
|
case types.RANGE: return types.range(&checker.module.types, types.I64)
|
||||||
|
case: return types.I64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type_from_syntax :: proc(
|
type_from_syntax :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
value: ast.Type_Syntax,
|
value: ast.Type_Syntax,
|
||||||
@@ -1386,9 +1406,20 @@ build_symbol_indexes :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
slice.sort_by(checker.global_index, global_index_less)
|
slice.sort_by(checker.global_index, global_index_less)
|
||||||
|
|
||||||
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
|
import_count := 0
|
||||||
|
for import_item in checker.ast_module.imports {
|
||||||
|
if !import_item.test_only {
|
||||||
|
import_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checker.import_index = make([]Import_Index_Entry, import_count, checker.allocator)
|
||||||
|
import_index := 0
|
||||||
for import_item, id in checker.ast_module.imports {
|
for import_item, id in checker.ast_module.imports {
|
||||||
checker.import_index[id] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
|
if import_item.test_only {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checker.import_index[import_index] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
|
||||||
|
import_index += 1
|
||||||
}
|
}
|
||||||
slice.sort_by(checker.import_index, import_index_less)
|
slice.sort_by(checker.import_index, import_index_less)
|
||||||
}
|
}
|
||||||
@@ -2636,7 +2667,7 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
|
|||||||
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
||||||
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
||||||
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
||||||
// `int`/`range` constraints have no single default and keep building naturally.
|
// Integer/range constraints keep building naturally from their literals.
|
||||||
if declared == types.FLOAT {
|
if declared == types.FLOAT {
|
||||||
return types.F64
|
return types.F64
|
||||||
}
|
}
|
||||||
@@ -3613,13 +3644,21 @@ record_field_expr_candidate :: proc(
|
|||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[expr_id]
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
constraint := checker.record_field_constraints[slot]
|
constraint := checker.record_field_constraints[slot]
|
||||||
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
|
if !numeric_operand_is_open(checker, expr_id, locals, pkg, file) {
|
||||||
constant.kind == .Value && fits_i64(constant.value) {
|
concrete := types.constraint_target(constraint, inferred, &checker.module.types)
|
||||||
candidate := types.smallest_signed_for_literal(i64(constant.value))
|
if is_runtime_type(checker, concrete) {
|
||||||
if constraint == types.FLOAT {
|
return merge_record_field_demand(checker, slot, concrete, expr.span)
|
||||||
candidate = types.F64
|
}
|
||||||
|
}
|
||||||
|
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
|
||||||
|
constant.kind == .Value {
|
||||||
|
candidate := constraint_integer_literal_type(constraint, constant.value)
|
||||||
|
if constraint == types.FLOAT {
|
||||||
|
candidate = types.F64 if fits_i64(constant.value) else types.INVALID
|
||||||
|
}
|
||||||
|
if types.is_valid(candidate) {
|
||||||
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
||||||
}
|
}
|
||||||
return merge_record_field_default(checker, slot, candidate, expr.span)
|
|
||||||
}
|
}
|
||||||
if is_float_constant_expr(checker, expr_id) {
|
if is_float_constant_expr(checker, expr_id) {
|
||||||
return merge_record_field_default(checker, slot, types.F64, expr.span)
|
return merge_record_field_default(checker, slot, types.F64, expr.span)
|
||||||
@@ -3682,14 +3721,7 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if types.is_constraint(current) {
|
if types.is_constraint(current) {
|
||||||
switch constraint {
|
checker.module.types.fields[slot].type = constraint_recovery_type(checker, constraint)
|
||||||
case types.INT:
|
|
||||||
checker.module.types.fields[slot].type = types.I64
|
|
||||||
case types.FLOAT:
|
|
||||||
checker.module.types.fields[slot].type = types.F64
|
|
||||||
case types.RANGE:
|
|
||||||
checker.module.types.fields[slot].type = types.range(&checker.module.types, types.I64)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3988,7 +4020,7 @@ find_spec :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// specialized_param_type maps a parameter's declared type to its monomorphized
|
// specialized_param_type maps a parameter's declared type to its monomorphized
|
||||||
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
|
// type for a given actual argument. A constraint param (`int`/`uint`/`float`/`range`)
|
||||||
// resolves to the actual's family member (INVALID if out of family), so a call
|
// resolves to the actual's family member (INVALID if out of family), so a call
|
||||||
// passing an out-of-family argument fails to specialize and is rejected.
|
// passing an out-of-family argument fails to specialize and is rejected.
|
||||||
specialized_param_type :: proc(
|
specialized_param_type :: proc(
|
||||||
@@ -4415,14 +4447,16 @@ infer_expr :: proc(
|
|||||||
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||||
constant = eval_constant(checker, frame.expr)
|
constant = eval_constant(checker, frame.expr)
|
||||||
}
|
}
|
||||||
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
||||||
(constant.kind == .Value && !fits_i64(constant.value)) {
|
|
||||||
last = types.I64
|
last = types.I64
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if constant.kind == .Value {
|
if constant.kind == .Value {
|
||||||
last = types.smallest_signed_for_literal(i64(constant.value))
|
last = constraint_integer_literal_type(frame.expected, constant.value)
|
||||||
|
if !types.is_valid(last) {
|
||||||
|
last = types.I64
|
||||||
|
}
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -5055,7 +5089,7 @@ infer_statements :: proc(
|
|||||||
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
||||||
value_type := types.INVALID
|
value_type := types.INVALID
|
||||||
if !is_undefined_expr(checker, statement.expr) {
|
if !is_undefined_expr(checker, statement.expr) {
|
||||||
expected := declared_local if is_runtime_type(checker, declared_local) else types.INVALID
|
expected := declared_local if is_runtime_type(checker, declared_local) || declared_local == types.UINT else types.INVALID
|
||||||
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
|
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
|
||||||
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
|
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
|
||||||
}
|
}
|
||||||
@@ -5071,7 +5105,8 @@ infer_statements :: proc(
|
|||||||
const_val := i128(0)
|
const_val := i128(0)
|
||||||
if !is_runtime_type(checker, declared_local) && !is_undefined_expr(checker, statement.expr) {
|
if !is_runtime_type(checker, declared_local) && !is_undefined_expr(checker, statement.expr) {
|
||||||
constant := eval_integer_constant_in_context(checker, statement.expr, pkg, file)
|
constant := eval_integer_constant_in_context(checker, statement.expr, pkg, file)
|
||||||
if constant.kind == .Value && fits_i64(constant.value) {
|
if constant.kind == .Value &&
|
||||||
|
(fits_i64(constant.value) || declared_local == types.UINT && fits_u64(constant.value)) {
|
||||||
open = true
|
open = true
|
||||||
const_val = constant.value
|
const_val = constant.value
|
||||||
} else if is_float_constant_expr(checker, statement.expr) {
|
} else if is_float_constant_expr(checker, statement.expr) {
|
||||||
@@ -5112,11 +5147,25 @@ infer_statements :: proc(
|
|||||||
expected_assignment := types.INVALID
|
expected_assignment := types.INVALID
|
||||||
if statement.target != ast.INVALID_EXPR {
|
if statement.target != ast.INVALID_EXPR {
|
||||||
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
|
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
|
||||||
|
target_expr := checker.ast_module.exprs[statement.target]
|
||||||
|
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
||||||
|
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
||||||
|
locals^[local_index].declared == types.UINT {
|
||||||
|
expected_assignment = types.UINT
|
||||||
|
} else if global := find_global(checker, target_expr.name, pkg, file); global != ast.INVALID_GLOBAL {
|
||||||
|
ast_global := checker.ast_module.globals[global]
|
||||||
|
if type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file) == types.UINT {
|
||||||
|
expected_assignment = types.UINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if statement.name != checker.sink_symbol {
|
} else if statement.name != checker.sink_symbol {
|
||||||
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
|
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
|
||||||
expected_assignment = locals^[local_index].type
|
expected_assignment = types.UINT if locals^[local_index].declared == types.UINT else locals^[local_index].type
|
||||||
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
||||||
expected_assignment = checker.global_types[global]
|
ast_global := checker.ast_module.globals[global]
|
||||||
|
declared_global := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
||||||
|
expected_assignment = types.UINT if declared_global == types.UINT else checker.global_types[global]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
|
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
|
||||||
@@ -5419,7 +5468,7 @@ infer_spec_locals_and_result :: proc(
|
|||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
||||||
declared = types.I32
|
declared = types.I32
|
||||||
}
|
}
|
||||||
result_hint := declared if is_runtime_type(checker, declared) else types.INVALID
|
result_hint := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
|
||||||
|
|
||||||
locals: [dynamic]Infer_Local
|
locals: [dynamic]Infer_Local
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
@@ -5604,7 +5653,7 @@ open_const_default_type :: proc(
|
|||||||
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
||||||
local := locals[index]
|
local := locals[index]
|
||||||
if local.open_const {
|
if local.open_const {
|
||||||
return types.smallest_signed_for_literal(i64(local.const_value))
|
return constraint_integer_literal_type(local.declared, local.const_value)
|
||||||
}
|
}
|
||||||
if local.open_float {
|
if local.open_float {
|
||||||
return types.F64
|
return types.F64
|
||||||
@@ -5622,7 +5671,9 @@ open_const_default_type :: proc(
|
|||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
if checker.global_open_const[index] {
|
if checker.global_open_const[index] {
|
||||||
return types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
|
ast_global := checker.ast_module.globals[global]
|
||||||
|
declared := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
||||||
|
return constraint_integer_literal_type(declared, checker.global_const_value[index])
|
||||||
}
|
}
|
||||||
if checker.global_open_float[index] {
|
if checker.global_open_float[index] {
|
||||||
return types.F64
|
return types.F64
|
||||||
@@ -5888,7 +5939,8 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
|
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
|
||||||
if constant.kind == .Value && fits_i64(constant.value) {
|
if constant.kind == .Value &&
|
||||||
|
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) {
|
||||||
checker.global_open_const[index] = true
|
checker.global_open_const[index] = true
|
||||||
checker.global_const_value[index] = constant.value
|
checker.global_const_value[index] = constant.value
|
||||||
} else if is_float_constant_expr(checker, global.expr) {
|
} else if is_float_constant_expr(checker, global.expr) {
|
||||||
@@ -5936,7 +5988,7 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
type_from_syntax(checker, global.type, global.pkg, global.file),
|
type_from_syntax(checker, global.type, global.pkg, global.file),
|
||||||
global.expr,
|
global.expr,
|
||||||
)
|
)
|
||||||
expected := declared if is_runtime_type(checker, declared) else types.INVALID
|
expected := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
|
||||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
|
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
|
||||||
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
|
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
|
||||||
resolved != declared {
|
resolved != declared {
|
||||||
@@ -6009,7 +6061,8 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if checker.global_open_const[index] {
|
if checker.global_open_const[index] {
|
||||||
checker.global_types[index] = types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
|
declared := type_from_syntax(checker, global.type, global.pkg, global.file)
|
||||||
|
checker.global_types[index] = constraint_integer_literal_type(declared, checker.global_const_value[index])
|
||||||
defaulted = true
|
defaulted = true
|
||||||
} else if checker.global_open_float[index] {
|
} else if checker.global_open_float[index] {
|
||||||
checker.global_types[index] = types.F64
|
checker.global_types[index] = types.F64
|
||||||
@@ -6383,7 +6436,7 @@ build_constant_expr :: proc(
|
|||||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||||
}
|
}
|
||||||
if constant.kind == .Overflow ||
|
if constant.kind == .Overflow ||
|
||||||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
|
(!types.is_concrete_integer(expected) && expected != types.UINT && !fits_i64(constant.value)) {
|
||||||
id := source.add(
|
id := source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
expr.span,
|
expr.span,
|
||||||
@@ -6396,7 +6449,16 @@ build_constant_expr :: proc(
|
|||||||
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
|
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
|
||||||
value = transmute(i64)u64(constant.value)
|
value = transmute(i64)u64(constant.value)
|
||||||
}
|
}
|
||||||
result_type := types.smallest_signed_for_literal(value)
|
result_type := constraint_integer_literal_type(expected, constant.value)
|
||||||
|
if expected == types.UINT && !types.is_valid(result_type) {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
expr.span,
|
||||||
|
"integer constant %d does not satisfy uint",
|
||||||
|
constant.value,
|
||||||
|
)
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, types.U64)
|
||||||
|
}
|
||||||
if types.is_concrete_integer(expected) {
|
if types.is_concrete_integer(expected) {
|
||||||
if !fits_integer_type(constant.value, expected, checker.target) {
|
if !fits_integer_type(constant.value, expected, checker.target) {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
@@ -12364,11 +12426,12 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
if is_runtime_type(checker, declared) {
|
if is_runtime_type(checker, declared) {
|
||||||
expected = declared
|
expected = declared
|
||||||
} else if constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file);
|
} else if constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file);
|
||||||
constant.kind == .Value && fits_i64(constant.value) &&
|
constant.kind == .Value &&
|
||||||
|
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) &&
|
||||||
is_runtime_type(checker, checker.global_types[global_index]) {
|
is_runtime_type(checker, checker.global_types[global_index]) {
|
||||||
// Open integer constant: build against its demanded/defaulted type. Gated
|
// Open integer constant: build against its demanded/defaulted type. Gated
|
||||||
// to the infer-side open-constant condition so out-of-range constants keep
|
// to the infer-side open-constant condition so out-of-range constants keep
|
||||||
// their original "exceeds signed i64 range" diagnostic.
|
// their original range diagnostic.
|
||||||
expected = checker.global_types[global_index]
|
expected = checker.global_types[global_index]
|
||||||
} else if (is_float_constant_expr(checker, global.expr) ||
|
} else if (is_float_constant_expr(checker, global.expr) ||
|
||||||
is_numeric_arithmetic_expr(checker, global.expr)) &&
|
is_numeric_arithmetic_expr(checker, global.expr)) &&
|
||||||
@@ -12390,6 +12453,18 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
global_type = types.I64
|
global_type = types.I64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if diagnostic == source.INVALID_DIAGNOSTIC && types.is_constraint(declared) &&
|
||||||
|
!types.constraint_accepts(declared, global_type, &checker.module.types) {
|
||||||
|
diagnostic = source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"could not resolve the '%s' constraint for global '%s'",
|
||||||
|
types.name(declared),
|
||||||
|
symbol_text(checker, global.name),
|
||||||
|
)
|
||||||
|
global_type = constraint_recovery_type(checker, declared)
|
||||||
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
||||||
|
}
|
||||||
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
||||||
diagnostic = source.add(
|
diagnostic = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -12874,7 +12949,7 @@ check :: proc(
|
|||||||
main_template := find_template(&checker, checker.main_symbol, 0)
|
main_template := find_template(&checker, checker.main_symbol, 0)
|
||||||
main_declarations := 0
|
main_declarations := 0
|
||||||
for function in ast_module.functions {
|
for function in ast_module.functions {
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol {
|
if !function.generated && function.pkg == 0 && function.name == checker.main_symbol {
|
||||||
main_declarations += 1
|
main_declarations += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12917,7 +12992,7 @@ check :: proc(
|
|||||||
delete(states, allocator)
|
delete(states, allocator)
|
||||||
propagate_problems(&checker)
|
propagate_problems(&checker)
|
||||||
for import_item in ast_module.imports {
|
for import_item in ast_module.imports {
|
||||||
if import_item.valid && !import_item.used {
|
if !import_item.test_only && import_item.valid && !import_item.used {
|
||||||
source.addf_warning(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias))
|
source.addf_warning(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
|
import "./ast"
|
||||||
import "./backend"
|
import "./backend"
|
||||||
import "./cimport"
|
import "./cimport"
|
||||||
import "./checker"
|
import "./checker"
|
||||||
@@ -34,6 +35,7 @@ compile_package :: proc(
|
|||||||
selected := target.DEFAULT,
|
selected := target.DEFAULT,
|
||||||
c_options := cimport.Options{},
|
c_options := cimport.Options{},
|
||||||
project_root := "",
|
project_root := "",
|
||||||
|
mode := ast.Compile_Mode.Executable,
|
||||||
) -> int {
|
) -> int {
|
||||||
sources := source.init_store()
|
sources := source.init_store()
|
||||||
defer source.destroy_store(&sources)
|
defer source.destroy_store(&sources)
|
||||||
@@ -77,12 +79,18 @@ compile_package :: proc(
|
|||||||
c_options,
|
c_options,
|
||||||
selected,
|
selected,
|
||||||
project_root if len(project_root) > 0 else input_path,
|
project_root if len(project_root) > 0 else input_path,
|
||||||
|
mode,
|
||||||
)
|
)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
fmt.eprintln("failed to load root package directory:", input_path)
|
fmt.eprintln("failed to load root package directory:", input_path)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
effective_root := project_root if len(project_root) > 0 else input_path
|
||||||
|
if !prepare_tests(&ast_module, &sources, &diagnostics, &symbols, mode, effective_root) {
|
||||||
|
source.print_all(&diagnostics)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
// Generated C trampolines (for `static inline` imports) must be compiled and
|
// Generated C trampolines (for `static inline` imports) must be compiled and
|
||||||
// linked with the program. Write them out and add the source as a link input
|
// linked with the program. Write them out and add the source as a link input
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ is_identifier_continue :: proc(value: byte) -> bool {
|
|||||||
|
|
||||||
keyword_kind :: proc(text: string) -> token.Kind {
|
keyword_kind :: proc(text: string) -> token.Kind {
|
||||||
switch text {
|
switch text {
|
||||||
|
case "test": return .Keyword_Test
|
||||||
case "func": return .Keyword_Func
|
case "func": return .Keyword_Func
|
||||||
case "c_func": return .Keyword_C_Func
|
case "c_func": return .Keyword_C_Func
|
||||||
case "struct": return .Keyword_Struct
|
case "struct": return .Keyword_Struct
|
||||||
@@ -51,6 +52,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "anyopaque": return .Keyword_Anyopaque
|
case "anyopaque": return .Keyword_Anyopaque
|
||||||
case "bool": return .Keyword_Bool
|
case "bool": return .Keyword_Bool
|
||||||
case "int": return .Keyword_Int
|
case "int": return .Keyword_Int
|
||||||
|
case "uint": return .Keyword_Uint
|
||||||
case "float": return .Keyword_Float
|
case "float": return .Keyword_Float
|
||||||
case "range": return .Keyword_Range
|
case "range": return .Keyword_Range
|
||||||
case "i8": return .Keyword_I8
|
case "i8": return .Keyword_I8
|
||||||
|
|||||||
+56
-22
@@ -26,6 +26,7 @@ State :: struct {
|
|||||||
c_options: cimport.Options,
|
c_options: cimport.Options,
|
||||||
selected: target.Target,
|
selected: target.Target,
|
||||||
project_root: string,
|
project_root: string,
|
||||||
|
mode: ast.Compile_Mode,
|
||||||
record_identities: [dynamic]string,
|
record_identities: [dynamic]string,
|
||||||
record_types: [dynamic]types.Type,
|
record_types: [dynamic]types.Type,
|
||||||
root_failed: bool,
|
root_failed: bool,
|
||||||
@@ -1130,7 +1131,42 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
|
|||||||
return pkg_id
|
return pkg_id
|
||||||
}
|
}
|
||||||
|
|
||||||
load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id {
|
resolve_package_imports :: proc(state: ^State, pkg_id: ast.Package_Id) {
|
||||||
|
if int(pkg_id) >= len(state.module.packages) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canonical := state.module.packages[pkg_id].path
|
||||||
|
import_count := len(state.module.imports)
|
||||||
|
for import_id in 0..<import_count {
|
||||||
|
import_item := state.module.imports[import_id]
|
||||||
|
if import_item.pkg != pkg_id || import_item.target != ast.INVALID_PACKAGE ||
|
||||||
|
import_item.test_only && (state.mode != .Test || !state.module.packages[pkg_id].test) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filepath.is_abs(import_item.path) {
|
||||||
|
state.module.imports[import_id].diagnostic = source.add(state.diagnostics, import_item.span, "absolute import paths are invalid")
|
||||||
|
state.module.imports[import_id].valid = false
|
||||||
|
state.module.imports[import_id].target = add_placeholder(state, import_item.path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
|
||||||
|
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else
|
||||||
|
load_package(state, target_path, import_item.span, include_tests=import_item.test_only)
|
||||||
|
state.module.imports[import_id].target = target
|
||||||
|
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
|
||||||
|
state.module.imports[import_id].valid = false
|
||||||
|
}
|
||||||
|
delete(target_path, state.allocator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load_package :: proc(
|
||||||
|
state: ^State,
|
||||||
|
path: string,
|
||||||
|
import_span: source.Span,
|
||||||
|
is_root := false,
|
||||||
|
include_tests := false,
|
||||||
|
) -> ast.Package_Id {
|
||||||
canonical, ok := filepath.abs(path, state.allocator)
|
canonical, ok := filepath.abs(path, state.allocator)
|
||||||
if !ok || !os.is_dir(path) {
|
if !ok || !os.is_dir(path) {
|
||||||
if is_root {
|
if is_root {
|
||||||
@@ -1152,6 +1188,10 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
|
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
|
||||||
|
if include_tests && !state.module.packages[existing].test {
|
||||||
|
state.module.packages[existing].test = true
|
||||||
|
resolve_package_imports(state, existing)
|
||||||
|
}
|
||||||
delete(canonical, state.allocator)
|
delete(canonical, state.allocator)
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
@@ -1161,6 +1201,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
|
|||||||
path=canonical,
|
path=canonical,
|
||||||
name=symbol.intern(state.symbols, filepath.base(canonical)),
|
name=symbol.intern(state.symbols, filepath.base(canonical)),
|
||||||
available=true,
|
available=true,
|
||||||
|
test=state.mode == .Test && is_root || include_tests,
|
||||||
})
|
})
|
||||||
files, files_ok := read_package_files(state, canonical)
|
files, files_ok := read_package_files(state, canonical)
|
||||||
if !files_ok {
|
if !files_ok {
|
||||||
@@ -1204,26 +1245,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
|
|||||||
}
|
}
|
||||||
os.file_info_slice_delete(files, state.allocator)
|
os.file_info_slice_delete(files, state.allocator)
|
||||||
|
|
||||||
import_count := len(state.module.imports)
|
resolve_package_imports(state, pkg_id)
|
||||||
for import_id in 0..<import_count {
|
|
||||||
import_item := state.module.imports[import_id]
|
|
||||||
if import_item.pkg != pkg_id || import_item.target != ast.INVALID_PACKAGE {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if filepath.is_abs(import_item.path) {
|
|
||||||
state.module.imports[import_id].diagnostic = source.add(state.diagnostics, import_item.span, "absolute import paths are invalid")
|
|
||||||
state.module.imports[import_id].valid = false
|
|
||||||
state.module.imports[import_id].target = add_placeholder(state, import_item.path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
|
|
||||||
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else load_package(state, target_path, import_item.span)
|
|
||||||
state.module.imports[import_id].target = target
|
|
||||||
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
|
|
||||||
state.module.imports[import_id].valid = false
|
|
||||||
}
|
|
||||||
delete(target_path, state.allocator)
|
|
||||||
}
|
|
||||||
return pkg_id
|
return pkg_id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,6 +1270,9 @@ declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, file: as
|
|||||||
|
|
||||||
validate_imports :: proc(state: ^State) {
|
validate_imports :: proc(state: ^State) {
|
||||||
for import_item, import_id in state.module.imports {
|
for import_item, import_id in state.module.imports {
|
||||||
|
if import_item.test_only {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !symbol.is_valid(import_item.alias) && import_item.target != ast.INVALID_PACKAGE {
|
if !symbol.is_valid(import_item.alias) && import_item.target != ast.INVALID_PACKAGE {
|
||||||
state.module.imports[import_id].alias = state.module.packages[import_item.target].name
|
state.module.imports[import_id].alias = state.module.packages[import_item.target].name
|
||||||
}
|
}
|
||||||
@@ -1287,7 +1312,7 @@ validate_imports :: proc(state: ^State) {
|
|||||||
|
|
||||||
find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id {
|
find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.Id) -> ast.Import_Id {
|
||||||
for import_item, index in module.imports {
|
for import_item, index in module.imports {
|
||||||
if import_item.file == file && import_item.alias == alias {
|
if !import_item.test_only && import_item.file == file && import_item.alias == alias {
|
||||||
return ast.import_id(index)
|
return ast.import_id(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1879,6 +1904,7 @@ load :: proc(
|
|||||||
c_options := cimport.Options{},
|
c_options := cimport.Options{},
|
||||||
selected := target.DEFAULT,
|
selected := target.DEFAULT,
|
||||||
project_root_path := "",
|
project_root_path := "",
|
||||||
|
mode := ast.Compile_Mode.Executable,
|
||||||
) -> (ast.Module, bool) {
|
) -> (ast.Module, bool) {
|
||||||
module := ast.init_module(allocator)
|
module := ast.init_module(allocator)
|
||||||
project_root_source := project_root_path if len(project_root_path) > 0 else root_path
|
project_root_source := project_root_path if len(project_root_path) > 0 else root_path
|
||||||
@@ -1896,6 +1922,7 @@ load :: proc(
|
|||||||
c_options=c_options,
|
c_options=c_options,
|
||||||
selected=selected,
|
selected=selected,
|
||||||
project_root=project_root,
|
project_root=project_root,
|
||||||
|
mode=mode,
|
||||||
}
|
}
|
||||||
state.record_identities.allocator = allocator
|
state.record_identities.allocator = allocator
|
||||||
state.record_types.allocator = allocator
|
state.record_types.allocator = allocator
|
||||||
@@ -1911,6 +1938,13 @@ load :: proc(
|
|||||||
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
|
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
|
||||||
state.root_failed = true
|
state.root_failed = true
|
||||||
}
|
}
|
||||||
|
if mode == .Test {
|
||||||
|
testing_path, testing_error := filepath.join({project_root, "std", "testing"}, allocator)
|
||||||
|
if testing_error == nil {
|
||||||
|
_ = load_package(&state, testing_path, source.Span{})
|
||||||
|
delete(testing_path, allocator)
|
||||||
|
}
|
||||||
|
}
|
||||||
validate_imports(&state)
|
validate_imports(&state)
|
||||||
validate_declaration_aliases(&state)
|
validate_declaration_aliases(&state)
|
||||||
resolve_enum_values(&state)
|
resolve_enum_values(&state)
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast
|
|||||||
|
|
||||||
is_type_token :: proc(kind: token.Kind) -> bool {
|
is_type_token :: proc(kind: token.Kind) -> bool {
|
||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
|
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
|
||||||
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
||||||
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
||||||
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
||||||
@@ -280,6 +280,9 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
case .Keyword_Int:
|
case .Keyword_Int:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.INT
|
return types.INT
|
||||||
|
case .Keyword_Uint:
|
||||||
|
advance(parser)
|
||||||
|
return types.UINT
|
||||||
case .Keyword_Float:
|
case .Keyword_Float:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.FLOAT
|
return types.FLOAT
|
||||||
@@ -774,7 +777,7 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
|||||||
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||||
tok := current(parser)
|
tok := current(parser)
|
||||||
#partial switch tok.kind {
|
#partial switch tok.kind {
|
||||||
case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool:
|
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool:
|
||||||
start := tok
|
start := tok
|
||||||
target := parse_type_atom(parser)
|
target := parse_type_atom(parser)
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
@@ -3002,7 +3005,7 @@ decode_multiline_string :: proc(parser: ^Parser, tok: token.Token) -> string {
|
|||||||
return fmt.aprintf("%s", strings.to_string(builder), allocator=parser.module.allocator)
|
return fmt.aprintf("%s", strings.to_string(builder), allocator=parser.module.allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) {
|
parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token, test_only := false) {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
path_token := current(parser)
|
path_token := current(parser)
|
||||||
if path_token.kind != .String {
|
if path_token.kind != .String {
|
||||||
@@ -3018,6 +3021,7 @@ parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) {
|
|||||||
file=parser.file,
|
file=parser.file,
|
||||||
target=ast.INVALID_PACKAGE,
|
target=ast.INVALID_PACKAGE,
|
||||||
valid=false,
|
valid=false,
|
||||||
|
test_only=test_only,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
@@ -3033,13 +3037,45 @@ parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) {
|
|||||||
file=parser.file,
|
file=parser.file,
|
||||||
target=ast.INVALID_PACKAGE,
|
target=ast.INVALID_PACKAGE,
|
||||||
valid=true,
|
valid=true,
|
||||||
|
test_only=test_only,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_test :: proc(parser: ^Parser, name: token.Token) {
|
||||||
|
advance(parser) // consume 'test'
|
||||||
|
skip_newlines(parser)
|
||||||
|
if current(parser).kind != .Left_Brace {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '{' after test name")
|
||||||
|
_ = finish_statement(parser)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body := parse_block(parser)
|
||||||
|
append(&parser.module.functions, ast.Function{
|
||||||
|
span=span_from(name.span, previous(parser).span),
|
||||||
|
name=name.symbol,
|
||||||
|
pkg=parser.pkg,
|
||||||
|
file=parser.file,
|
||||||
|
test=true,
|
||||||
|
has_body=true,
|
||||||
|
result=types.VOID,
|
||||||
|
body=body,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
parse_top_level :: proc(parser: ^Parser) {
|
parse_top_level :: proc(parser: ^Parser) {
|
||||||
hide_token, file_hidden := allow(parser, .Keyword_Hide)
|
hide_token, file_hidden := allow(parser, .Keyword_Hide)
|
||||||
|
if current(parser).kind == .Keyword_Test && peek(parser).kind == .Keyword_Import {
|
||||||
|
if file_hidden {
|
||||||
|
source.add(parser.diagnostics, hide_token.span, "test imports cannot use 'hide'")
|
||||||
|
}
|
||||||
|
start := advance(parser)
|
||||||
|
advance(parser) // consume 'import'
|
||||||
|
parse_import(parser, token.Token{}, start, test_only=true)
|
||||||
|
return
|
||||||
|
}
|
||||||
if current(parser).kind == .Keyword_Import {
|
if current(parser).kind == .Keyword_Import {
|
||||||
if file_hidden {
|
if file_hidden {
|
||||||
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'")
|
source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'")
|
||||||
@@ -3058,6 +3094,13 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
name := advance(parser)
|
name := advance(parser)
|
||||||
|
if current(parser).kind == .Keyword_Test {
|
||||||
|
if file_hidden {
|
||||||
|
source.add(parser.diagnostics, hide_token.span, "test declarations cannot use 'hide'")
|
||||||
|
}
|
||||||
|
parse_test(parser, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
if current(parser).kind == .Colon_Colon {
|
if current(parser).kind == .Colon_Colon {
|
||||||
saved := parser.cursor
|
saved := parser.cursor
|
||||||
advance(parser)
|
advance(parser)
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import "./ast"
|
||||||
|
import "./lexer"
|
||||||
|
import "./parser"
|
||||||
|
import "./source"
|
||||||
|
import "./symbol"
|
||||||
|
import "./types"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:path/filepath"
|
||||||
|
import "core:slice"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
Test_Entry :: struct {
|
||||||
|
function: ast.Function_Id,
|
||||||
|
package_path: string,
|
||||||
|
source_path: string,
|
||||||
|
offset: source.Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
test_entry_less :: proc(a, b: Test_Entry) -> bool {
|
||||||
|
if a.package_path != b.package_path {
|
||||||
|
return a.package_path < b.package_path
|
||||||
|
}
|
||||||
|
if a.source_path != b.source_path {
|
||||||
|
return a.source_path < b.source_path
|
||||||
|
}
|
||||||
|
return a.offset < b.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
testing_package :: proc(module: ^ast.Module, project_root: string) -> ast.Package_Id {
|
||||||
|
path, err := filepath.join({project_root, "std", "testing"}, module.allocator)
|
||||||
|
if err != nil {
|
||||||
|
return ast.INVALID_PACKAGE
|
||||||
|
}
|
||||||
|
defer delete(path, module.allocator)
|
||||||
|
canonical, ok := filepath.abs(path, module.allocator)
|
||||||
|
if !ok {
|
||||||
|
return ast.INVALID_PACKAGE
|
||||||
|
}
|
||||||
|
defer delete(canonical, module.allocator)
|
||||||
|
for pkg, index in module.packages {
|
||||||
|
if pkg.path == canonical {
|
||||||
|
return ast.package_id(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.INVALID_PACKAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
source_file_id :: proc(module: ^ast.Module, id: source.Source_Id) -> ast.File_Id {
|
||||||
|
for file, index in module.files {
|
||||||
|
if file.source == id {
|
||||||
|
return ast.file_id(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.INVALID_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
append_ast_expr :: proc(module: ^ast.Module, expr: ast.Expr) -> ast.Expr_Id {
|
||||||
|
id := ast.expr_id(len(module.exprs))
|
||||||
|
append(&module.exprs, expr)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
location_expr :: proc(
|
||||||
|
module: ^ast.Module,
|
||||||
|
sources: ^source.Store,
|
||||||
|
symbols: ^symbol.Table,
|
||||||
|
span: source.Span,
|
||||||
|
qualifier: symbol.Id,
|
||||||
|
) -> ast.Expr_Id {
|
||||||
|
line, column := 1, 1
|
||||||
|
path := "<unknown>"
|
||||||
|
if int(span.file) < len(sources.items) {
|
||||||
|
file := &sources.items[span.file]
|
||||||
|
line, column = source.line_and_column(file, span.start)
|
||||||
|
path = file.path
|
||||||
|
}
|
||||||
|
string_index := u64(len(module.strings))
|
||||||
|
append(&module.strings, strings.clone(path, module.allocator))
|
||||||
|
file_expr := append_ast_expr(module, ast.Expr{
|
||||||
|
kind=.String, span=span, integer=string_index,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
line_expr := append_ast_expr(module, ast.Expr{
|
||||||
|
kind=.Integer, span=span, integer=u64(line),
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
column_expr := append_ast_expr(module, ast.Expr{
|
||||||
|
kind=.Integer, span=span, integer=u64(column),
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
values := []ast.Expr_Id{file_expr, line_expr, column_expr}
|
||||||
|
names := []string{"file", "line", "column"}
|
||||||
|
fields := make([]ast.Expr_Id, 3, module.allocator)
|
||||||
|
for index in 0..<3 {
|
||||||
|
fields[index] = append_ast_expr(module, ast.Expr{
|
||||||
|
kind=.Keyed, span=span,
|
||||||
|
name=symbol.intern(symbols, names[index]),
|
||||||
|
left=values[index], right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return append_ast_expr(module, ast.Expr{
|
||||||
|
kind=.Struct_Literal, span=span,
|
||||||
|
qualifier=qualifier,
|
||||||
|
name=symbol.intern(symbols, "SourceLocation"),
|
||||||
|
args=fields,
|
||||||
|
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
inject_assertion_locations :: proc(
|
||||||
|
module: ^ast.Module,
|
||||||
|
sources: ^source.Store,
|
||||||
|
symbols: ^symbol.Table,
|
||||||
|
testing_pkg: ast.Package_Id,
|
||||||
|
) {
|
||||||
|
expect := symbol.intern(symbols, "expect")
|
||||||
|
expect_equal := symbol.intern(symbols, "expect_equal")
|
||||||
|
original_count := len(module.exprs)
|
||||||
|
for index in 0..<original_count {
|
||||||
|
expr := &module.exprs[index]
|
||||||
|
if expr.kind != .Call || expr.intrinsic || !symbol.is_valid(expr.qualifier) ||
|
||||||
|
(expr.name != expect && expr.name != expect_equal) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file := source_file_id(module, expr.span.file)
|
||||||
|
matched := false
|
||||||
|
for import_item in module.imports {
|
||||||
|
if !import_item.test_only && import_item.file == file &&
|
||||||
|
import_item.alias == expr.qualifier && import_item.target == testing_pkg {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
location := location_expr(module, sources, symbols, expr.span, expr.qualifier)
|
||||||
|
args := make([]ast.Expr_Id, len(expr.args)+1, module.allocator)
|
||||||
|
copy(args, expr.args)
|
||||||
|
args[len(expr.args)] = location
|
||||||
|
delete(expr.args, module.allocator)
|
||||||
|
expr.args = args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_brolang_string :: proc(builder: ^strings.Builder, value: string) {
|
||||||
|
for byte_value in transmute([]byte)value {
|
||||||
|
if byte_value == '\\' || byte_value == '"' {
|
||||||
|
strings.write_byte(builder, '\\')
|
||||||
|
}
|
||||||
|
strings.write_byte(builder, byte_value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append_runner :: proc(
|
||||||
|
module: ^ast.Module,
|
||||||
|
sources: ^source.Store,
|
||||||
|
diagnostics: ^source.Diagnostics,
|
||||||
|
symbols: ^symbol.Table,
|
||||||
|
testing_pkg: ast.Package_Id,
|
||||||
|
tests: []Test_Entry,
|
||||||
|
) {
|
||||||
|
builder := strings.builder_make(module.allocator)
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
strings.write_string(&builder, "main func() i32 {\n\tfailed i32 = 0\n")
|
||||||
|
for entry, index in tests {
|
||||||
|
test_id := entry.function
|
||||||
|
test := module.functions[test_id]
|
||||||
|
pkg := module.packages[test.pkg]
|
||||||
|
alias := fmt.tprintf("__brolang_test_%d", index)
|
||||||
|
name := fmt.tprintf("%s.%s", filepath.base(pkg.path), symbol.resolve(symbols, test.name))
|
||||||
|
strings.write_string(&builder, "\tif (!__brolang_testing.run(\"")
|
||||||
|
write_brolang_string(&builder, name)
|
||||||
|
fmt.sbprintf(&builder, "\", %s.%s)) ", alias, symbol.resolve(symbols, test.name))
|
||||||
|
strings.write_string(&builder, "{\n\t\tfailed += 1\n\t}\n")
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&builder, "\t__brolang_testing.summary(%d - failed, failed)\n", len(tests))
|
||||||
|
strings.write_string(&builder, "\tif (failed != 0) return 1\n\treturn 0\n}\n")
|
||||||
|
|
||||||
|
runner_text := strings.to_string(builder)
|
||||||
|
source_id := source.add_source(sources, "<brolang-test-runner>", runner_text)
|
||||||
|
file_id := ast.file_id(len(module.files))
|
||||||
|
append(&module.files, ast.File{source=source_id, pkg=0})
|
||||||
|
stream := lexer.lex(&sources.items[source_id], diagnostics, symbols, module.allocator)
|
||||||
|
defer delete(stream.items)
|
||||||
|
parser.parse_into(&stream, &sources.items[source_id], diagnostics, module, 0, file_id)
|
||||||
|
|
||||||
|
append(&module.imports, ast.Import{
|
||||||
|
alias=symbol.intern(symbols, "__brolang_testing"),
|
||||||
|
path=strings.clone("@std/testing", module.allocator),
|
||||||
|
pkg=0, file=file_id, target=testing_pkg,
|
||||||
|
valid=true, used=true,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
for entry, index in tests {
|
||||||
|
test_id := entry.function
|
||||||
|
test := module.functions[test_id]
|
||||||
|
append(&module.imports, ast.Import{
|
||||||
|
alias=symbol.intern(symbols, fmt.tprintf("__brolang_test_%d", index)),
|
||||||
|
path=strings.clone(module.packages[test.pkg].path, module.allocator),
|
||||||
|
pkg=0, file=file_id, target=test.pkg,
|
||||||
|
valid=true, used=true,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_tests :: proc(
|
||||||
|
module: ^ast.Module,
|
||||||
|
sources: ^source.Store,
|
||||||
|
diagnostics: ^source.Diagnostics,
|
||||||
|
symbols: ^symbol.Table,
|
||||||
|
mode: ast.Compile_Mode,
|
||||||
|
project_root: string,
|
||||||
|
) -> bool {
|
||||||
|
if mode == .Executable {
|
||||||
|
for &function in module.functions {
|
||||||
|
if !function.test {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
function.generated = true
|
||||||
|
for &import_item in module.imports {
|
||||||
|
if !import_item.test_only && import_item.file == function.file {
|
||||||
|
import_item.used = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
testing_pkg := testing_package(module, project_root)
|
||||||
|
if testing_pkg == ast.INVALID_PACKAGE {
|
||||||
|
source.add(diagnostics, source.Span{}, "test builds require @std/testing")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
error_type := types.find_named(
|
||||||
|
&module.type_store,
|
||||||
|
u32(testing_pkg),
|
||||||
|
u32(symbol.intern(symbols, "Error")),
|
||||||
|
)
|
||||||
|
if !types.is_enum(error_type, &module.type_store) {
|
||||||
|
source.add(diagnostics, source.Span{}, "@std/testing must declare Error as an enum")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tests: [dynamic]Test_Entry
|
||||||
|
tests.allocator = module.allocator
|
||||||
|
defer delete(tests)
|
||||||
|
main_name := symbol.intern(symbols, "main")
|
||||||
|
for &function, index in module.functions {
|
||||||
|
if function.test {
|
||||||
|
if int(function.pkg) < len(module.packages) && module.packages[function.pkg].test {
|
||||||
|
function.result = types.VOID
|
||||||
|
function.error = error_type
|
||||||
|
file := module.files[function.file]
|
||||||
|
append(&tests, Test_Entry{
|
||||||
|
function=ast.function_id(index),
|
||||||
|
package_path=module.packages[function.pkg].path,
|
||||||
|
source_path=sources.items[file.source].path,
|
||||||
|
offset=function.span.start,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
function.generated = true
|
||||||
|
}
|
||||||
|
} else if function.pkg == 0 && function.name == main_name {
|
||||||
|
function.generated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slice.sort_by(tests[:], test_entry_less)
|
||||||
|
inject_assertion_locations(module, sources, symbols, testing_pkg)
|
||||||
|
append_runner(module, sources, diagnostics, symbols, testing_pkg, tests[:])
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ Kind :: enum u8 {
|
|||||||
Right_Brace,
|
Right_Brace,
|
||||||
Comma,
|
Comma,
|
||||||
Pipe,
|
Pipe,
|
||||||
|
Keyword_Test,
|
||||||
Keyword_Func,
|
Keyword_Func,
|
||||||
Keyword_C_Func,
|
Keyword_C_Func,
|
||||||
Keyword_Struct,
|
Keyword_Struct,
|
||||||
@@ -87,6 +88,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Anyopaque,
|
Keyword_Anyopaque,
|
||||||
Keyword_Bool,
|
Keyword_Bool,
|
||||||
Keyword_Int,
|
Keyword_Int,
|
||||||
|
Keyword_Uint,
|
||||||
Keyword_Float,
|
Keyword_Float,
|
||||||
Keyword_Range,
|
Keyword_Range,
|
||||||
Keyword_I8,
|
Keyword_I8,
|
||||||
@@ -118,7 +120,7 @@ Kind :: enum u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is_keyword :: proc(kind: Kind) -> bool {
|
is_keyword :: proc(kind: Kind) -> bool {
|
||||||
return kind >= .Keyword_Func && kind <= .Keyword_C_Longdouble
|
return kind >= .Keyword_Test && kind <= .Keyword_C_Longdouble
|
||||||
}
|
}
|
||||||
|
|
||||||
Token :: struct {
|
Token :: struct {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ BOOL :: Type(29)
|
|||||||
FLOAT :: Type(30)
|
FLOAT :: Type(30)
|
||||||
RANGE :: Type(31)
|
RANGE :: Type(31)
|
||||||
ANYOPAQUE :: Type(32)
|
ANYOPAQUE :: Type(32)
|
||||||
|
UINT :: Type(33)
|
||||||
|
|
||||||
DYNAMIC_START :: Type(64)
|
DYNAMIC_START :: Type(64)
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ Kind :: enum u8 {
|
|||||||
Void,
|
Void,
|
||||||
Anyopaque,
|
Anyopaque,
|
||||||
Int_Constraint,
|
Int_Constraint,
|
||||||
|
Uint_Constraint,
|
||||||
Float_Constraint,
|
Float_Constraint,
|
||||||
Range_Constraint,
|
Range_Constraint,
|
||||||
Scalar,
|
Scalar,
|
||||||
@@ -595,6 +597,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
|||||||
return .Anyopaque
|
return .Anyopaque
|
||||||
case INT:
|
case INT:
|
||||||
return .Int_Constraint
|
return .Int_Constraint
|
||||||
|
case UINT:
|
||||||
|
return .Uint_Constraint
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return .Float_Constraint
|
return .Float_Constraint
|
||||||
case RANGE:
|
case RANGE:
|
||||||
@@ -642,7 +646,7 @@ is_bool :: proc(value: Type) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is_constraint :: proc(value: Type) -> bool {
|
is_constraint :: proc(value: Type) -> bool {
|
||||||
return value == INT || value == FLOAT || value == RANGE
|
return value == INT || value == UINT || value == FLOAT || value == RANGE
|
||||||
}
|
}
|
||||||
|
|
||||||
// constraint_target reports the concrete type a constraint binding (local, or a
|
// constraint_target reports the concrete type a constraint binding (local, or a
|
||||||
@@ -656,6 +660,8 @@ constraint_target :: proc(constraint, inferred: Type, store: ^Store = nil) -> Ty
|
|||||||
switch constraint {
|
switch constraint {
|
||||||
case INT:
|
case INT:
|
||||||
return inferred if is_concrete_integer(inferred) else INVALID
|
return inferred if is_concrete_integer(inferred) else INVALID
|
||||||
|
case UINT:
|
||||||
|
return inferred if is_unsigned(inferred) else INVALID
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
if is_float(inferred) {
|
if is_float(inferred) {
|
||||||
return inferred
|
return inferred
|
||||||
@@ -673,6 +679,8 @@ constraint_accepts :: proc(constraint, concrete: Type, store: ^Store = nil) -> b
|
|||||||
switch constraint {
|
switch constraint {
|
||||||
case INT:
|
case INT:
|
||||||
return is_concrete_integer(concrete)
|
return is_concrete_integer(concrete)
|
||||||
|
case UINT:
|
||||||
|
return is_unsigned(concrete)
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return is_float(concrete)
|
return is_float(concrete)
|
||||||
case RANGE:
|
case RANGE:
|
||||||
@@ -1686,6 +1694,19 @@ smallest_signed_for_literal :: proc(value: i64) -> Type {
|
|||||||
return I64
|
return I64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smallest_unsigned_for_literal :: proc(value: u64) -> Type {
|
||||||
|
if value <= 255 {
|
||||||
|
return U8
|
||||||
|
}
|
||||||
|
if value <= 65535 {
|
||||||
|
return U16
|
||||||
|
}
|
||||||
|
if value <= 4294967295 {
|
||||||
|
return U32
|
||||||
|
}
|
||||||
|
return U64
|
||||||
|
}
|
||||||
|
|
||||||
name :: proc(value: Type) -> string {
|
name :: proc(value: Type) -> string {
|
||||||
switch value {
|
switch value {
|
||||||
case INVALID: return "<invalid>"
|
case INVALID: return "<invalid>"
|
||||||
@@ -1693,6 +1714,7 @@ name :: proc(value: Type) -> string {
|
|||||||
case ANYOPAQUE: return "anyopaque"
|
case ANYOPAQUE: return "anyopaque"
|
||||||
case BOOL: return "bool"
|
case BOOL: return "bool"
|
||||||
case INT: return "int"
|
case INT: return "int"
|
||||||
|
case UINT: return "uint"
|
||||||
case FLOAT: return "float"
|
case FLOAT: return "float"
|
||||||
case RANGE: return "range"
|
case RANGE: return "range"
|
||||||
case I8: return "i8"
|
case I8: return "i8"
|
||||||
|
|||||||
+286
-1
@@ -1485,6 +1485,7 @@ main func() void {
|
|||||||
_ = maxval!(u8, u16)
|
_ = maxval!(u8, u16)
|
||||||
_ = minval!(1)
|
_ = minval!(1)
|
||||||
_ = maxval!(int)
|
_ = maxval!(int)
|
||||||
|
_ = maxval!(uint)
|
||||||
_ = maxval!(f32)
|
_ = maxval!(f32)
|
||||||
_ = maxval!(bool)
|
_ = maxval!(bool)
|
||||||
_ = maxval!(Named)
|
_ = maxval!(Named)
|
||||||
@@ -1513,7 +1514,7 @@ main func() void {
|
|||||||
}
|
}
|
||||||
testing.expect_value(t, bad_arity, 2)
|
testing.expect_value(t, bad_arity, 2)
|
||||||
testing.expect(t, bad_type)
|
testing.expect(t, bad_type)
|
||||||
testing.expect_value(t, bad_target, 5)
|
testing.expect_value(t, bad_target, 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -12778,3 +12779,287 @@ main func() void {
|
|||||||
testing.expect(t, types.is_slice(nested_field, &hir_module.types))
|
testing.expect(t, types.is_slice(nested_field, &hir_module.types))
|
||||||
testing.expect_value(t, types.child_type(nested_field, &hir_module.types), types.INT)
|
testing.expect_value(t, types.child_type(nested_field, &hir_module.types), types.INT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_records_native_tests_and_test_imports :: proc(t: ^testing.T) {
|
||||||
|
text := `test import "../math"
|
||||||
|
|
||||||
|
addition test {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="tests.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(module.imports), 1)
|
||||||
|
testing.expect(t, module.imports[0].test_only)
|
||||||
|
testing.expect_value(t, module.imports[0].alias, symbol.INVALID)
|
||||||
|
testing.expect_value(t, len(module.functions), 1)
|
||||||
|
testing.expect(t, module.functions[0].test)
|
||||||
|
testing.expect_value(t, symbol.resolve(&symbols, module.functions[0].name), "addition")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
native_test_framework_discovers_reports_and_preserves_main :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-native-framework"
|
||||||
|
root := "/tmp/brolang-test-native-framework/root"
|
||||||
|
dependency := "/tmp/brolang-test-native-framework/dependency"
|
||||||
|
root_path := "/tmp/brolang-test-native-framework/root/main.bro"
|
||||||
|
dependency_path := "/tmp/brolang-test-native-framework/dependency/math.bro"
|
||||||
|
test_output := "/tmp/brolang-test-native-framework-tests"
|
||||||
|
app_output := "/tmp/brolang-test-native-framework-app"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(test_output)
|
||||||
|
defer _ = os.remove(app_output)
|
||||||
|
testing.expect(t, os2.make_directory_all(root) == nil)
|
||||||
|
testing.expect(t, os2.make_directory_all(dependency) == nil)
|
||||||
|
|
||||||
|
root_text := `testing :: import "@std/testing"
|
||||||
|
|
||||||
|
test import "../dependency"
|
||||||
|
|
||||||
|
main func() i32 { return 77 }
|
||||||
|
|
||||||
|
root_passes test {
|
||||||
|
try testing.expect(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
root_fails test {
|
||||||
|
try testing.expect_equal(42, 41)
|
||||||
|
}
|
||||||
|
|
||||||
|
root_continues test {
|
||||||
|
try testing.expect(true)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
dependency_text := `testing :: import "@std/testing"
|
||||||
|
|
||||||
|
dependency_passes test {
|
||||||
|
try testing.expect(true)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(root_path, transmute([]byte)root_text))
|
||||||
|
testing.expect(t, os.write_entire_file(dependency_path, transmute([]byte)dependency_text))
|
||||||
|
|
||||||
|
app_status := compiler_core.compile_package(
|
||||||
|
root, app_output, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||||
|
)
|
||||||
|
testing.expect_value(t, app_status, 0)
|
||||||
|
app_state := run_executable(app_output)
|
||||||
|
testing.expect_value(t, app_state.exit_code, 77)
|
||||||
|
|
||||||
|
test_status := compiler_core.compile_package(
|
||||||
|
root, test_output, nil, target.DEFAULT, cimport.Options{}, ".", .Test,
|
||||||
|
)
|
||||||
|
testing.expect_value(t, test_status, 0)
|
||||||
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{test_output}},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
output := string(stderr)
|
||||||
|
testing.expect_value(t, state.exit_code, 1)
|
||||||
|
testing.expect(t, strings.contains(output, "PASS root.root_passes"))
|
||||||
|
testing.expect(t, strings.contains(output, "FAIL root.root_fails"))
|
||||||
|
testing.expect(t, strings.contains(output, "expected 42, found 41"))
|
||||||
|
testing.expect(t, strings.contains(output, "PASS root.root_continues"))
|
||||||
|
testing.expect(t, strings.contains(output, "PASS dependency.dependency_passes"))
|
||||||
|
testing.expect(t, strings.contains(output, root_path))
|
||||||
|
testing.expect(t, strings.contains(output, "3 passed, 1 failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
uint_constraint_parses_as_type_and_comptime_value :: proc(t: ^testing.T) {
|
||||||
|
text := `value uint :: 1
|
||||||
|
Constraint :: uint
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="uint.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(module.globals), 2)
|
||||||
|
testing.expect_value(t, module.globals[0].type, types.UINT)
|
||||||
|
testing.expect_value(t, module.exprs[module.globals[1].expr].kind, ast.Expr_Kind.Type)
|
||||||
|
testing.expect_value(t, module.exprs[module.globals[1].expr].type, types.UINT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
uint_constraint_uses_smallest_unsigned_types_and_widens :: proc(t: ^testing.T) {
|
||||||
|
text := `Zero uint :: 0
|
||||||
|
Byte uint :: 255
|
||||||
|
Word uint :: 256
|
||||||
|
Dword uint :: 65536
|
||||||
|
Maximum uint :: 18446744073709551615
|
||||||
|
Counter uint = 1
|
||||||
|
|
||||||
|
widen func() uint {
|
||||||
|
value uint = 1
|
||||||
|
value = 1000
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
widen_global func() void { Counter = 1000 }
|
||||||
|
|
||||||
|
main func() void {
|
||||||
|
_ = Zero
|
||||||
|
_ = Byte
|
||||||
|
_ = Word
|
||||||
|
_ = Dword
|
||||||
|
_ = Maximum
|
||||||
|
_ = widen()
|
||||||
|
widen_global()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="uint_types.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)
|
||||||
|
|
||||||
|
expected := []types.Type{types.U8, types.U8, types.U16, types.U32, types.U64, types.U16}
|
||||||
|
for value_type, index in expected {
|
||||||
|
testing.expect_value(t, hir_module.globals[index].type, value_type)
|
||||||
|
}
|
||||||
|
widen_symbol := symbol.intern(&symbols, "widen")
|
||||||
|
found_widen := false
|
||||||
|
for function in hir_module.functions {
|
||||||
|
if function.name != widen_symbol {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
found_widen = true
|
||||||
|
testing.expect_value(t, function.result, types.U16)
|
||||||
|
testing.expect_value(t, function.locals[0].type, types.U16)
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found_widen)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
uint_constraint_infers_boundaries_calls_and_records :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-uint-constraint"
|
||||||
|
main_path := "/tmp/brolang-test-uint-constraint/main.bro"
|
||||||
|
output := "/tmp/brolang-test-uint-constraint-output"
|
||||||
|
text := `Box :: struct { value uint }
|
||||||
|
|
||||||
|
identity func(value uint) uint { return value }
|
||||||
|
whole func(value int) int { return value }
|
||||||
|
max_value func() uint { return 18446744073709551615 }
|
||||||
|
|
||||||
|
make_box func(value usize) Box { return Box{value = value} }
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
zero uint :: 0
|
||||||
|
byte uint :: 255
|
||||||
|
word uint :: 256
|
||||||
|
word_max uint :: 65535
|
||||||
|
dword uint :: 65536
|
||||||
|
maximum uint :: 18446744073709551615
|
||||||
|
platform usize = 7
|
||||||
|
c_value c_uint = c_uint(9)
|
||||||
|
box Box = make_box(7)
|
||||||
|
signed isize = -1
|
||||||
|
if (identity(zero) != 0) return 1
|
||||||
|
if (identity(byte) != 255) return 2
|
||||||
|
if (identity(word) != 256) return 3
|
||||||
|
if (identity(word_max) != 65535) return 4
|
||||||
|
if (identity(dword) != 65536) return 5
|
||||||
|
if (identity(maximum) != 18446744073709551615) return 6
|
||||||
|
if (box.value != 7) return 7
|
||||||
|
if (signed != -1) return 8
|
||||||
|
if (identity(platform) != 7) return 9
|
||||||
|
if (identity(c_value) != c_value) return 10
|
||||||
|
if (whole(word) != 256) return 11
|
||||||
|
if (max_value() != 18446744073709551615) return 12
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = 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)
|
||||||
|
uint_constraint_rejects_other_families_and_recovers_records_as_u64 :: proc(t: ^testing.T) {
|
||||||
|
text := `Unresolved :: struct { value uint }
|
||||||
|
Conflict :: struct { value uint }
|
||||||
|
|
||||||
|
take func(value uint) uint { return value }
|
||||||
|
bad_result func() uint { return -1 }
|
||||||
|
|
||||||
|
bad func(signed i32, decimal f64) void {
|
||||||
|
_ = take(signed)
|
||||||
|
_ = take(decimal)
|
||||||
|
negative uint :: -1
|
||||||
|
a Conflict = Conflict{value = signed}
|
||||||
|
_ = negative
|
||||||
|
_ = a
|
||||||
|
}
|
||||||
|
|
||||||
|
Overflow uint :: 18446744073709551616
|
||||||
|
NegativeGlobal uint :: -1
|
||||||
|
main func() void {
|
||||||
|
bad(1, 1.0)
|
||||||
|
_ = bad_result()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="bad_uint.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)
|
||||||
|
|
||||||
|
signed_error := false
|
||||||
|
float_error := false
|
||||||
|
negative_error := false
|
||||||
|
field_error := false
|
||||||
|
overflow_error := false
|
||||||
|
global_error := false
|
||||||
|
result_error := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
signed_error = signed_error || strings.contains(diagnostic.message, "cannot pass i32 to 'uint' parameter 'value'")
|
||||||
|
float_error = float_error || strings.contains(diagnostic.message, "cannot pass f64 to 'uint' parameter 'value'")
|
||||||
|
negative_error = negative_error || strings.contains(diagnostic.message, "could not resolve the 'uint' constraint for local 'negative'")
|
||||||
|
field_error = field_error || strings.contains(diagnostic.message, "type i32 does not satisfy the 'uint' constraint for field 'Conflict.value'")
|
||||||
|
overflow_error = overflow_error || strings.contains(diagnostic.message, "magnitude does not fit in u64")
|
||||||
|
global_error = global_error || strings.contains(diagnostic.message, "could not resolve the 'uint' constraint for global 'NegativeGlobal'")
|
||||||
|
result_error = result_error || strings.contains(diagnostic.message, "could not resolve a concrete result type for 'bad_result'")
|
||||||
|
}
|
||||||
|
unresolved, unresolved_ok := named_record_field_type(&hir_module, &symbols, "Unresolved", "value")
|
||||||
|
testing.expect(t, signed_error && float_error && negative_error && field_error && overflow_error && global_error && result_error)
|
||||||
|
testing.expect(t, unresolved_ok)
|
||||||
|
testing.expect_value(t, unresolved, types.U64)
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -9,5 +9,5 @@ languages = ["languages/brolang"]
|
|||||||
|
|
||||||
[grammars.brolang]
|
[grammars.brolang]
|
||||||
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
||||||
rev = "03f5445509575fb72226e8b4bbf230d7905b7b8e"
|
rev = "71357b2866d2a558f80b6d5cc65761fb853e74d3"
|
||||||
path = "tree-sitter-brolang"
|
path = "tree-sitter-brolang"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
(type_declaration name: (identifier) @type)
|
(type_declaration name: (identifier) @type)
|
||||||
(function_declaration name: (identifier) @function)
|
(function_declaration name: (identifier) @function)
|
||||||
|
(test_declaration name: (identifier) @function)
|
||||||
(parameter name: (identifier) @variable.parameter)
|
(parameter name: (identifier) @variable.parameter)
|
||||||
|
|
||||||
(intrinsic_call_expression function: (identifier) @function.builtin)
|
(intrinsic_call_expression function: (identifier) @function.builtin)
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
|
|
||||||
[
|
[
|
||||||
"func"
|
"func"
|
||||||
|
"test"
|
||||||
"c_func"
|
"c_func"
|
||||||
"struct"
|
"struct"
|
||||||
"c_struct"
|
"c_struct"
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ print_usage :: proc() {
|
|||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang build [root] (reads root/build.bro; writes root/build/name)",
|
" brolang build [root] (reads root/build.bro; writes root/build/name)",
|
||||||
)
|
)
|
||||||
|
fmt.eprintln(
|
||||||
|
" brolang test [root] (reads root/build.bro; writes and runs root/build/name-test)",
|
||||||
|
)
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang new <project-path>",
|
" brolang new <project-path>",
|
||||||
)
|
)
|
||||||
@@ -122,6 +125,10 @@ is_build_command :: proc(arg: string) -> bool {
|
|||||||
return arg == "build"
|
return arg == "build"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_test_command :: proc(arg: string) -> bool {
|
||||||
|
return arg == "test"
|
||||||
|
}
|
||||||
|
|
||||||
is_new_command :: proc(arg: string) -> bool {
|
is_new_command :: proc(arg: string) -> bool {
|
||||||
return arg == "new"
|
return arg == "new"
|
||||||
}
|
}
|
||||||
@@ -609,6 +616,14 @@ main :: proc() {
|
|||||||
root := os2.args[2] if len(os2.args) == 3 else ""
|
root := os2.args[2] if len(os2.args) == 3 else ""
|
||||||
os2.exit(compiler.run_build(root))
|
os2.exit(compiler.run_build(root))
|
||||||
}
|
}
|
||||||
|
if len(os2.args) >= 2 && is_test_command(os2.args[1]) {
|
||||||
|
if len(os2.args) > 3 {
|
||||||
|
print_usage()
|
||||||
|
os2.exit(2)
|
||||||
|
}
|
||||||
|
root := os2.args[2] if len(os2.args) == 3 else ""
|
||||||
|
os2.exit(compiler.run_tests(root))
|
||||||
|
}
|
||||||
if len(os2.args) >= 2 && is_new_command(os2.args[1]) {
|
if len(os2.args) >= 2 && is_new_command(os2.args[1]) {
|
||||||
if len(os2.args) != 3 {
|
if len(os2.args) != 3 {
|
||||||
print_usage()
|
print_usage()
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
debug :: import "@std/debug"
|
||||||
|
|
||||||
|
Error :: enum {
|
||||||
|
expectation_failed
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceLocation :: struct {
|
||||||
|
file []u8
|
||||||
|
line usize
|
||||||
|
column usize
|
||||||
|
}
|
||||||
|
|
||||||
|
expect func(condition bool, location SourceLocation) void ! Error {
|
||||||
|
if (!condition) {
|
||||||
|
debug.print("{s}:{d}:{d}: expectation failed\n", {location.file, location.line, location.column})
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
|
||||||
|
if (expected != actual) {
|
||||||
|
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run func(name []u8, callback *func() void ! Error) bool {
|
||||||
|
debug.print("RUN {s}\n", {name,})
|
||||||
|
callback() catch |_| {
|
||||||
|
debug.print("FAIL {s}\n", {name,})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
debug.print("PASS {s}\n", {name,})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
summary func(passed, failed i32) void {
|
||||||
|
debug.print("{d} passed, {d} failed\n", {passed, failed})
|
||||||
|
}
|
||||||
@@ -40,7 +40,9 @@ module.exports = grammar({
|
|||||||
|
|
||||||
_top_level_declaration: $ => choice(
|
_top_level_declaration: $ => choice(
|
||||||
$.import_declaration,
|
$.import_declaration,
|
||||||
|
$.test_import_declaration,
|
||||||
$.function_declaration,
|
$.function_declaration,
|
||||||
|
$.test_declaration,
|
||||||
$.type_declaration,
|
$.type_declaration,
|
||||||
$.global_constant_declaration,
|
$.global_constant_declaration,
|
||||||
$.global_variable_declaration,
|
$.global_variable_declaration,
|
||||||
@@ -53,6 +55,20 @@ module.exports = grammar({
|
|||||||
field('path', $.string),
|
field('path', $.string),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
test_import_declaration: $ => seq(
|
||||||
|
'test',
|
||||||
|
'import',
|
||||||
|
repeat($._newline),
|
||||||
|
field('path', $.string),
|
||||||
|
),
|
||||||
|
|
||||||
|
test_declaration: $ => seq(
|
||||||
|
field('name', $.identifier),
|
||||||
|
'test',
|
||||||
|
repeat($._newline),
|
||||||
|
field('body', $.block),
|
||||||
|
),
|
||||||
|
|
||||||
function_declaration: $ => seq(
|
function_declaration: $ => seq(
|
||||||
optional('hide'),
|
optional('hide'),
|
||||||
field('name', $.identifier),
|
field('name', $.identifier),
|
||||||
@@ -212,7 +228,7 @@ module.exports = grammar({
|
|||||||
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
|
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
|
||||||
|
|
||||||
builtin_type: _ => choice(
|
builtin_type: _ => choice(
|
||||||
'void', 'anyopaque', 'bool', 'int', 'float', 'range',
|
'void', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
|
||||||
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
|
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
|
||||||
'isize', 'usize', 'f32', 'f64',
|
'isize', 'usize', 'f32', 'f64',
|
||||||
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
|
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
(type_declaration name: (identifier) @type)
|
(type_declaration name: (identifier) @type)
|
||||||
(function_declaration name: (identifier) @function)
|
(function_declaration name: (identifier) @function)
|
||||||
|
(test_declaration name: (identifier) @function)
|
||||||
(parameter name: (identifier) @variable.parameter)
|
(parameter name: (identifier) @variable.parameter)
|
||||||
|
|
||||||
(intrinsic_call_expression function: (identifier) @function.builtin)
|
(intrinsic_call_expression function: (identifier) @function.builtin)
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
|
|
||||||
[
|
[
|
||||||
"func"
|
"func"
|
||||||
|
"test"
|
||||||
"c_func"
|
"c_func"
|
||||||
"struct"
|
"struct"
|
||||||
"c_struct"
|
"c_struct"
|
||||||
|
|||||||
@@ -26,10 +26,18 @@
|
|||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "import_declaration"
|
"name": "import_declaration"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "test_import_declaration"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "function_declaration"
|
"name": "function_declaration"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "test_declaration"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "type_declaration"
|
"name": "type_declaration"
|
||||||
@@ -100,6 +108,66 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"test_import_declaration": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "import"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "path",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"test_declaration": {
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "name",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "body",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "block"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"function_declaration": {
|
"function_declaration": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -1408,6 +1476,10 @@
|
|||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "int"
|
"value": "int"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "uint"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "float"
|
"value": "float"
|
||||||
|
|||||||
@@ -1852,6 +1852,14 @@
|
|||||||
"type": "import_declaration",
|
"type": "import_declaration",
|
||||||
"named": true
|
"named": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "test_declaration",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test_import_declaration",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "type_declaration",
|
"type": "type_declaration",
|
||||||
"named": true
|
"named": true
|
||||||
@@ -2000,6 +2008,48 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "test_declaration",
|
||||||
|
"named": true,
|
||||||
|
"fields": {
|
||||||
|
"body": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test_import_declaration",
|
||||||
|
"named": true,
|
||||||
|
"fields": {
|
||||||
|
"path": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "tuple_literal",
|
"type": "tuple_literal",
|
||||||
"named": true,
|
"named": true,
|
||||||
@@ -2737,6 +2787,10 @@
|
|||||||
"type": "struct",
|
"type": "struct",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "true",
|
"type": "true",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -2761,6 +2815,10 @@
|
|||||||
"type": "u8",
|
"type": "u8",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "uint",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "undefined",
|
"type": "undefined",
|
||||||
"named": true
|
"named": true
|
||||||
|
|||||||
+141735
-139552
File diff suppressed because it is too large
Load Diff
@@ -364,7 +364,30 @@ main func() void {
|
|||||||
(expression
|
(expression
|
||||||
(sink))
|
(sink))
|
||||||
(expression
|
(expression
|
||||||
(identifier))))))))))
|
(identifier))))))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Native tests
|
||||||
|
==================
|
||||||
|
|
||||||
|
math :: import "../math"
|
||||||
|
test import "../math"
|
||||||
|
|
||||||
|
addition test {}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(import_declaration
|
||||||
|
(identifier)
|
||||||
|
(string
|
||||||
|
(string_content)))
|
||||||
|
(test_import_declaration
|
||||||
|
(string
|
||||||
|
(string_content)))
|
||||||
|
(test_declaration
|
||||||
|
(identifier)
|
||||||
|
(block)))
|
||||||
|
|
||||||
==================
|
==================
|
||||||
Expanded match arms
|
Expanded match arms
|
||||||
@@ -373,7 +396,7 @@ Expanded match arms
|
|||||||
Kind :: enum { one, two }
|
Kind :: enum { one, two }
|
||||||
Value :: union(enum) { number i32, empty void }
|
Value :: union(enum) { number i32, empty void }
|
||||||
|
|
||||||
test func(kind Kind, value Value) void {
|
visit func(kind Kind, value Value) void {
|
||||||
match kind {
|
match kind {
|
||||||
.one: {}
|
.one: {}
|
||||||
expand |tag|: _ = tag
|
expand |tag|: _ = tag
|
||||||
@@ -470,3 +493,19 @@ test func(kind Kind, value Value) void {
|
|||||||
(sink))
|
(sink))
|
||||||
(expression
|
(expression
|
||||||
(identifier))))))))))))
|
(identifier))))))))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Unsigned constraint
|
||||||
|
==================
|
||||||
|
|
||||||
|
value uint :: 255
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(global_constant_declaration
|
||||||
|
(identifier)
|
||||||
|
(type
|
||||||
|
(builtin_type))
|
||||||
|
(expression
|
||||||
|
(integer))))
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
hide helper func() void {}
|
hide helper func() void {}
|
||||||
# <- keyword
|
# <- keyword
|
||||||
|
|
||||||
|
value uint :: 1
|
||||||
|
# ^^^^ type.builtin
|
||||||
|
|
||||||
main func() void {
|
main func() void {
|
||||||
_ = sizeof!(i32)
|
_ = sizeof!(i32)
|
||||||
# ^^^^^^ function.builtin
|
# ^^^^^^ function.builtin
|
||||||
|
|||||||
Reference in New Issue
Block a user