fix honey compiler blockers
This commit is contained in:
@@ -48,9 +48,10 @@ Instead of passing these on the command line, a project can describe its build
|
|||||||
in Brolang itself. `brolang new <project>` creates a project with local `std`
|
in Brolang itself. `brolang new <project>` creates a project with local `std`
|
||||||
and `ffi` copies; `brolang init` does the same for the current directory without
|
and `ffi` copies; `brolang init` does the same for the current directory without
|
||||||
overwriting existing files. `brolang build [root]` reads a `config` constant
|
overwriting existing files. `brolang build [root]` reads a `config` constant
|
||||||
from `root/build.bro` and compiles the program package it names. Without
|
from exactly one of `root/build.bro` or `root/build.hon` and compiles the
|
||||||
`root`, it searches the current directory and parents for the nearest
|
program package it names. Without `root`, it searches the current directory
|
||||||
`build.bro`. Build outputs are written to `root/build/<name>`.
|
and parents for the nearest build file. Build outputs are written to
|
||||||
|
`root/build/<name>`.
|
||||||
|
|
||||||
```bro
|
```bro
|
||||||
b :: import "@std/build"
|
b :: import "@std/build"
|
||||||
@@ -67,15 +68,16 @@ config :: b.BuildConfig{
|
|||||||
```
|
```
|
||||||
|
|
||||||
`name` is a plain executable name, and `source` is the program package relative
|
`name` is a plain executable name, and `source` is the program package relative
|
||||||
to `build.bro`. The list fields map to the matching C options (`libraries` →
|
to the build file. The list fields map to the matching C options (`libraries` →
|
||||||
`-l`, `lib_paths` → `-L`, `includes` → `-I`, `defines` → C defines, `links` →
|
`-l`, `lib_paths` → `-L`, `includes` → `-I`, `defines` → C defines, `links` →
|
||||||
linker inputs) and, like those flags, their paths are relative to the invocation
|
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]`.
|
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
|
The command reads the same build file—exactly one of `build.bro` or
|
||||||
its C link inputs, libraries, include paths, and defines.
|
`build.hon`—writes `build/<name>-test`, and reuses its C link inputs,
|
||||||
|
libraries, include paths, and defines.
|
||||||
|
|
||||||
```bro
|
```bro
|
||||||
math :: import "../math"
|
math :: import "../math"
|
||||||
|
|||||||
+25
-23
@@ -66,10 +66,10 @@ load_build_config :: proc(project_root: string) -> (BuildConfig, bool) {
|
|||||||
return {}, false
|
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 a build config. Suppress
|
||||||
// one but surface any real errors in build.bro (and fail on them).
|
// that one but surface any real errors in the build config (and fail on them).
|
||||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
|
||||||
if build_bro_has_errors(&diagnostics) {
|
if build_config_has_errors(&diagnostics) {
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -82,14 +82,14 @@ project_root_for_command :: proc(root, command: string) -> (string, bool, bool)
|
|||||||
}
|
}
|
||||||
project_root, found := find_build_root()
|
project_root, found := find_build_root()
|
||||||
if !found {
|
if !found {
|
||||||
fmt.eprintfln("brolang %s: could not find build.bro in the current directory or any parent", command)
|
fmt.eprintfln("brolang %s: could not find build.bro or build.hon in the current directory or any parent", command)
|
||||||
return "", false, false
|
return "", false, false
|
||||||
}
|
}
|
||||||
return project_root, true, true
|
return project_root, true, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// run_build implements `brolang build [root]`: it reads build.bro and compiles
|
// run_build implements `brolang build [root]`: it reads the build config and
|
||||||
// the configured program package.
|
// compiles the configured program package.
|
||||||
run_build :: proc(root: string) -> int {
|
run_build :: proc(root: string) -> int {
|
||||||
project_root, owned, found := project_root_for_command(root, "build")
|
project_root, owned, found := project_root_for_command(root, "build")
|
||||||
if !found {
|
if !found {
|
||||||
@@ -161,7 +161,7 @@ valid_output_name :: proc(name: string) -> bool {
|
|||||||
|
|
||||||
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
|
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
|
||||||
if !valid_output_name(name) {
|
if !valid_output_name(name) {
|
||||||
fmt.eprintfln("build.bro: config 'name' must be a plain executable name, got '%s'", name)
|
fmt.eprintfln("build config: config 'name' must be a plain executable name, got '%s'", name)
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
build_dir, dir_error := filepath.join({project_root, "build"}, allocator)
|
build_dir, dir_error := filepath.join({project_root, "build"}, allocator)
|
||||||
@@ -202,15 +202,17 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
|
|||||||
current = strings.clone(start, allocator)
|
current = strings.clone(start, allocator)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
build_path, build_error := filepath.join({current, "build.bro"}, allocator)
|
for build_file in ([?]string{"build.bro", "build.hon"}) {
|
||||||
if build_error != nil {
|
build_path, build_error := filepath.join({current, build_file}, allocator)
|
||||||
delete(current, allocator)
|
if build_error != nil {
|
||||||
return "", false
|
delete(current, allocator)
|
||||||
}
|
return "", false
|
||||||
found := os.exists(build_path)
|
}
|
||||||
delete(build_path, allocator)
|
found := os.exists(build_path)
|
||||||
if found {
|
delete(build_path, allocator)
|
||||||
return current, true
|
if found {
|
||||||
|
return current, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if current == "/" {
|
if current == "/" {
|
||||||
delete(current, allocator)
|
delete(current, allocator)
|
||||||
@@ -227,10 +229,10 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build_bro_has_errors reports whether checking build.bro produced any diagnostic
|
// build_config_has_errors reports whether checking a build config produced any
|
||||||
// other than the benign "missing or unusable main function" (build.bro has no
|
// diagnostic other than the benign "missing or unusable main function" (build
|
||||||
// main by design; that one is emitted with an empty span).
|
// configs have no main by design; that one is emitted with an empty span).
|
||||||
build_bro_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
|
build_config_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
|
||||||
for item in diagnostics.items {
|
for item in diagnostics.items {
|
||||||
if item.span == (source.Span{}) && item.message == "missing or unusable main function" {
|
if item.span == (source.Span{}) && item.message == "missing or unusable main function" {
|
||||||
continue
|
continue
|
||||||
@@ -255,12 +257,12 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
fmt.eprintln("build.bro: missing top-level 'config' constant")
|
fmt.eprintln("build config: missing top-level 'config' constant")
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
root := unwrap_coercions(m, config_expr)
|
root := unwrap_coercions(m, config_expr)
|
||||||
if root == hir.INVALID_EXPR || m.exprs[root].kind != .Struct {
|
if root == hir.INVALID_EXPR || m.exprs[root].kind != .Struct {
|
||||||
fmt.eprintln("build.bro: 'config' must be a BuildConfig{...} literal")
|
fmt.eprintln("build config: 'config' must be a BuildConfig{...} literal")
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
args := m.exprs[root].args
|
args := m.exprs[root].args
|
||||||
@@ -322,7 +324,7 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
|
|||||||
cfg.c_options.defines = defines[:]
|
cfg.c_options.defines = defines[:]
|
||||||
|
|
||||||
if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 {
|
if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 {
|
||||||
fmt.eprintln("build.bro: config requires non-empty 'name' and 'source'")
|
fmt.eprintln("build config: config requires non-empty 'name' and 'source'")
|
||||||
destroy_build_config(&cfg)
|
destroy_build_config(&cfg)
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1677,7 +1677,7 @@ is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
|
|||||||
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||||
#partial switch kind(value, store) {
|
#partial switch kind(value, store) {
|
||||||
case .Scalar:
|
case .Scalar:
|
||||||
return u64(bits(value, selected)/8)
|
return u64((bits(value, selected)+7)/8)
|
||||||
case .Pointer:
|
case .Pointer:
|
||||||
return u64(target.pointer_bits(selected)/8)
|
return u64(target.pointer_bits(selected)/8)
|
||||||
case .Slice:
|
case .Slice:
|
||||||
|
|||||||
+100
-3
@@ -1738,6 +1738,16 @@ layout_builtins_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
y u8
|
y u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool_First :: struct {
|
||||||
|
flag bool
|
||||||
|
byte u8
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool_Last :: struct {
|
||||||
|
byte u8
|
||||||
|
flag bool
|
||||||
|
}
|
||||||
|
|
||||||
Opaque :: opaque
|
Opaque :: opaque
|
||||||
Color :: enum {
|
Color :: enum {
|
||||||
red
|
red
|
||||||
@@ -1755,6 +1765,20 @@ buffer func($T type) [sizeof!(T)]u8 {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
make_bool_first func() Bool_First {
|
||||||
|
value Bool_First = undefined
|
||||||
|
value.flag = true
|
||||||
|
value.byte = 41
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
make_bool_last func() Bool_Last {
|
||||||
|
value Bool_Last = undefined
|
||||||
|
value.byte = 42
|
||||||
|
value.flag = true
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
bytes [_]u8 :: buffer(i32)
|
bytes [_]u8 :: buffer(i32)
|
||||||
if (needs_usize(SIZE_GLOBAL) != 4) return 1
|
if (needs_usize(SIZE_GLOBAL) != 4) return 1
|
||||||
@@ -1771,6 +1795,15 @@ main func() i32 {
|
|||||||
if (alignof!(Point) != 4) return 12
|
if (alignof!(Point) != 4) return 12
|
||||||
if (sizeof!(UserID) != 4) return 13
|
if (sizeof!(UserID) != 4) return 13
|
||||||
if (alignof!(UserID) != 4) return 14
|
if (alignof!(UserID) != 4) return 14
|
||||||
|
if (sizeof!(bool) != 1) return 15
|
||||||
|
if (sizeof!(Bool_First) != 2) return 16
|
||||||
|
if (sizeof!(Bool_Last) != 2) return 17
|
||||||
|
first :: make_bool_first()
|
||||||
|
if (!first.flag) return 18
|
||||||
|
if (first.byte != 41) return 19
|
||||||
|
last :: make_bool_last()
|
||||||
|
if (!last.flag) return 20
|
||||||
|
if (last.byte != 42) return 21
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -5560,19 +5593,27 @@ init_project_creates_missing_layout_and_preserves_existing_files :: proc(t: ^tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
build_root_search_finds_nearest_parent_build_file :: proc(t: ^testing.T) {
|
build_hon_is_discovered_from_nested_directory_and_builds :: proc(t: ^testing.T) {
|
||||||
root := "/tmp/brolang-test-build-search"
|
root := "/tmp/brolang-test-build-search"
|
||||||
nested := "/tmp/brolang-test-build-search/source/nested"
|
nested := "/tmp/brolang-test-build-search/source/nested"
|
||||||
|
build_bro := "/tmp/brolang-test-build-search/build.bro"
|
||||||
|
build_hon := "/tmp/brolang-test-build-search/build.hon"
|
||||||
|
output := "/tmp/brolang-test-build-search/build/brolang-test-build-search"
|
||||||
_ = os2.remove_all(root)
|
_ = os2.remove_all(root)
|
||||||
defer _ = os2.remove_all(root)
|
defer _ = os2.remove_all(root)
|
||||||
|
|
||||||
|
testing.expect(t, init_project(root))
|
||||||
|
testing.expect(t, os.rename(build_bro, build_hon))
|
||||||
testing.expect(t, os2.make_directory_all(nested) == nil)
|
testing.expect(t, os2.make_directory_all(nested) == nil)
|
||||||
build_text := "# root\n"
|
|
||||||
testing.expect(t, os.write_entire_file("/tmp/brolang-test-build-search/build.bro", transmute([]byte)build_text))
|
|
||||||
found, ok := compiler_core.find_build_root_from(nested)
|
found, ok := compiler_core.find_build_root_from(nested)
|
||||||
defer delete(found)
|
defer delete(found)
|
||||||
testing.expect(t, ok)
|
testing.expect(t, ok)
|
||||||
testing.expect(t, strings.has_suffix(found, "/brolang-test-build-search"))
|
testing.expect(t, strings.has_suffix(found, "/brolang-test-build-search"))
|
||||||
|
|
||||||
|
status := compiler_core.run_build(found)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -10460,6 +10501,62 @@ cross_package_recursive_specialization_reaches_fixed_point :: proc(t: ^testing.T
|
|||||||
testing.expect(t, os.exists(output))
|
testing.expect(t, os.exists(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
qualified_concrete_call_in_runtime_for_retains_specialization :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-qualified-concrete-call"
|
||||||
|
ast_directory := "/tmp/brolang-test-qualified-concrete-call/ast"
|
||||||
|
main_path := "/tmp/brolang-test-qualified-concrete-call/main.hon"
|
||||||
|
ast_path := "/tmp/brolang-test-qualified-concrete-call/ast/ast.hon"
|
||||||
|
output := "/tmp/brolang-test-qualified-concrete-call-output"
|
||||||
|
ast_text := `Node :: struct { token usize }
|
||||||
|
Token :: struct { start usize }
|
||||||
|
|
||||||
|
render_literal func(node @Node, tokens []Token, program []u8) ?[]u8 {
|
||||||
|
if (node.token >= tokens.len) return null
|
||||||
|
token :: tokens[node.token]
|
||||||
|
if (token.start >= program.len) return null
|
||||||
|
return program[token.start..token.start+1]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
main_text := `ast :: import "./ast"
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
nodes [1]mut ast.Node = undefined
|
||||||
|
nodes[0].token = 0
|
||||||
|
token_storage [1]mut ast.Token = undefined
|
||||||
|
token_storage[0].start = 0
|
||||||
|
mutable_tokens []mut ast.Token = token_storage[..]
|
||||||
|
program :: "x"
|
||||||
|
for nodes |node| {
|
||||||
|
_ = ast.render_literal(&node, mutable_tokens, program)
|
||||||
|
}
|
||||||
|
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.make_directory(ast_directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)main_text))
|
||||||
|
testing.expect(t, os.write_entire_file(ast_path, transmute([]byte)ast_text))
|
||||||
|
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) {
|
imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-package-missing-root-main"
|
output := "/tmp/brolang-test-package-missing-root-main"
|
||||||
|
|||||||
@@ -101,10 +101,10 @@ print_usage :: proc() {
|
|||||||
" brolang translate-c|--translate-c <header.h>... [--output-dir <dir>] [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
" brolang translate-c|--translate-c <header.h>... [--output-dir <dir>] [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
||||||
)
|
)
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang build [root] (reads root/build.bro; writes root/build/name)",
|
" brolang build [root] (reads exactly one of root/build.bro or root/build.hon; writes root/build/name)",
|
||||||
)
|
)
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang test [root] (reads root/build.bro; writes and runs root/build/name-test)",
|
" brolang test [root] (reads exactly one of root/build.bro or root/build.hon; writes and runs root/build/name-test)",
|
||||||
)
|
)
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang new <project-path>",
|
" brolang new <project-path>",
|
||||||
|
|||||||
+5
-4
@@ -1,14 +1,15 @@
|
|||||||
# Build configuration surface for `brolang build` (v0).
|
# Build configuration surface for `brolang build` (v0).
|
||||||
#
|
#
|
||||||
# A project's `build.bro` imports this module and declares a top-level constant
|
# A project's `build.bro` or `build.hon` imports this module and declares a
|
||||||
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
|
# top-level constant named `config` of type `BuildConfig`. `brolang build
|
||||||
# build.bro, reads the config, and writes root/build/name.
|
# [root]` accepts exactly one of those files, reads the config, and writes
|
||||||
|
# root/build/name.
|
||||||
#
|
#
|
||||||
# Declarative and literal-only: one executable per build. List fields take an
|
# Declarative and literal-only: one executable per build. List fields take an
|
||||||
# address-of an array literal (`&["raylib"]`) and default to empty.
|
# address-of an array literal (`&["raylib"]`) and default to empty.
|
||||||
BuildConfig :: struct {
|
BuildConfig :: struct {
|
||||||
name []u8 # output executable name under root/build
|
name []u8 # output executable name under root/build
|
||||||
source []u8 # program package directory, relative to build.bro
|
source []u8 # program package directory, relative to the build file
|
||||||
libraries [][]u8 = &[] # library names to link (-l)
|
libraries [][]u8 = &[] # library names to link (-l)
|
||||||
lib_paths [][]u8 = &[] # library search directories (-L)
|
lib_paths [][]u8 = &[] # library search directories (-L)
|
||||||
includes [][]u8 = &[] # C include directories (-I)
|
includes [][]u8 = &[] # C include directories (-I)
|
||||||
|
|||||||
Reference in New Issue
Block a user