add new and init to toolchain
This commit is contained in:
@@ -31,9 +31,12 @@ invocation in command-line order:
|
|||||||
C preprocessing.
|
C preprocessing.
|
||||||
|
|
||||||
Instead of passing these on the command line, a project can describe its build
|
Instead of passing these on the command line, a project can describe its build
|
||||||
in Brolang itself. `brolang build [root]` (root defaults to the current
|
in Brolang itself. `brolang new <project>` creates a project with local `std`
|
||||||
directory) reads a `config` constant from `root/build.bro` and compiles the
|
and `ffi` copies; `brolang init` does the same for the current directory without
|
||||||
program package it names:
|
overwriting existing files. `brolang build [root]` reads a `config` constant
|
||||||
|
from `root/build.bro` and compiles the program package it names. Without
|
||||||
|
`root`, it searches the current directory and parents for the nearest
|
||||||
|
`build.bro`. Build outputs are written to `root/build/<name>`.
|
||||||
|
|
||||||
```bro
|
```bro
|
||||||
b :: import "@std/build"
|
b :: import "@std/build"
|
||||||
@@ -49,12 +52,12 @@ config :: b.BuildConfig{
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`source` is the program package, relative to `build.bro`. The list fields map to
|
`name` is a plain executable name, and `source` is the program package relative
|
||||||
the matching C options (`libraries` → `-l`, `lib_paths` → `-L`, `includes` →
|
to `build.bro`. The list fields map to the matching C options (`libraries` →
|
||||||
`-I`, `defines` → C defines, `links` → linker inputs) and, like those flags,
|
`-l`, `lib_paths` → `-L`, `includes` → `-I`, `defines` → C defines, `links` →
|
||||||
their paths are relative to the invocation directory. Lists take the address of
|
linker inputs) and, like those flags, their paths are relative to the invocation
|
||||||
an array literal; empty lists are written `&[]`. See `examples/build/` for
|
directory. Lists take the address of an array literal; empty lists are written
|
||||||
runnable projects.
|
`&[]`. See `examples/build/` for runnable projects.
|
||||||
|
|
||||||
Relative `.h` imports create synthetic package namespaces backed by libclang:
|
Relative `.h` imports create synthetic package namespaces backed by libclang:
|
||||||
|
|
||||||
@@ -146,11 +149,12 @@ specialization is demanded.
|
|||||||
|
|
||||||
Every immediate `.bro` file in the input directory belongs to the root
|
Every immediate `.bro` file in the input directory belongs to the root
|
||||||
package. Imports are relative directory paths and are local to the file that
|
package. Imports are relative directory paths and are local to the file that
|
||||||
declares them:
|
declares them. Imports beginning with `@` resolve from the project root:
|
||||||
|
|
||||||
```bro
|
```bro
|
||||||
import "../math"
|
import "../math"
|
||||||
other_math :: import "../math"
|
other_math :: import "../math"
|
||||||
|
heap :: import "@std/mem/heap"
|
||||||
|
|
||||||
value :: math.sum(other_math.value, 1)
|
value :: math.sum(other_math.value, 1)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -626,8 +626,9 @@
|
|||||||
- typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
|
- typed allocation, allocator parameters, arenas/pools, build-mode heap policy, and escaping-allocation diagnostics remain deferred
|
||||||
|
|
||||||
26. import from project "root" (implemented)
|
26. import from project "root" (implemented)
|
||||||
- imports beginning with `@` resolve from the compiler process cwd / project root
|
- imports beginning with `@` resolve from the project root
|
||||||
- `heap :: import "@std/mem/heap"` works from any package depth without `../../../` path math
|
- `brolang build [root]` uses `root` as the project root; without `root`, it searches cwd and parents for `build.bro`
|
||||||
|
- direct `brolang <package-dir> -o <out>` defaults the project root to the package dir; `--root <dir>` overrides it
|
||||||
|
|
||||||
27. comptime integer value parameters (implemented; v1)
|
27. comptime integer value parameters (implemented; v1)
|
||||||
- `$N` marks an integer comptime parameter in a normal `func` signature:
|
- `$N` marks an integer comptime parameter in a normal `func` signature:
|
||||||
@@ -715,6 +716,8 @@
|
|||||||
`root/build.bro` (importing `@std/build`'s `BuildConfig`) and compiles the
|
`root/build.bro` (importing `@std/build`'s `BuildConfig`) and compiles the
|
||||||
program package it names. The config is read from the checked HIR — build.bro
|
program package it names. The config is read from the checked HIR — build.bro
|
||||||
is never lowered — so it is literal-only.
|
is never lowered — so it is literal-only.
|
||||||
|
- `brolang new <project>` and `brolang init` create the default project layout
|
||||||
|
with local `std`, `ffi`, `vendor`, `source`, and `build.bro`
|
||||||
- enabled `&<array literal>` (Zig's `&.{...}`): the literal is promoted to an
|
- enabled `&<array literal>` (Zig's `&.{...}`): the literal is promoted to an
|
||||||
anonymous global whose address decays to a slice, so list fields like
|
anonymous global whose address decays to a slice, so list fields like
|
||||||
`libraries = &["raylib"]` work; empty lists are `&[]`
|
`libraries = &["raylib"]` work; empty lists are `&[]`
|
||||||
|
|||||||
@@ -13,6 +13,32 @@ append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Al
|
|||||||
append(command, strings.clone(value, allocator))
|
append(command, strings.clone(value, allocator))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macos_sdk_root :: proc(allocator := context.allocator) -> (string, bool) {
|
||||||
|
candidates := []string{
|
||||||
|
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
|
||||||
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
|
||||||
|
}
|
||||||
|
for path in candidates {
|
||||||
|
if os.exists(path) {
|
||||||
|
return strings.clone(path, allocator), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
append_macos_sdk_paths :: proc(command: ^[dynamic]string, selected: target.Target, allocator: mem.Allocator) {
|
||||||
|
if selected.kind != .Aarch64_Macos {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sdk, ok := macos_sdk_root(allocator)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer delete(sdk, allocator)
|
||||||
|
append(command, fmt.aprintf("-F%s/System/Library/Frameworks", sdk, allocator=allocator))
|
||||||
|
append(command, fmt.aprintf("-L%s/usr/lib", sdk, allocator=allocator))
|
||||||
|
}
|
||||||
|
|
||||||
build_command :: proc(
|
build_command :: proc(
|
||||||
llvm_path, output_path: string,
|
llvm_path, output_path: string,
|
||||||
link_arguments: []linker.Argument,
|
link_arguments: []linker.Argument,
|
||||||
@@ -29,6 +55,7 @@ build_command :: proc(
|
|||||||
append_owned(&command, target.name(selected), allocator)
|
append_owned(&command, target.name(selected), allocator)
|
||||||
append_owned(&command, "-Wno-override-module", allocator)
|
append_owned(&command, "-Wno-override-module", allocator)
|
||||||
append_owned(&command, "-Wno-unused-command-line-argument", allocator)
|
append_owned(&command, "-Wno-unused-command-line-argument", allocator)
|
||||||
|
append_macos_sdk_paths(&command, selected, allocator)
|
||||||
append_owned(&command, llvm_path, allocator)
|
append_owned(&command, llvm_path, allocator)
|
||||||
for path in c_options.include_paths {
|
for path in c_options.include_paths {
|
||||||
append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
|
append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
|
||||||
|
|||||||
+93
-4
@@ -11,6 +11,8 @@ import "./target"
|
|||||||
import "./types"
|
import "./types"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import vmem "core:mem/virtual"
|
import vmem "core:mem/virtual"
|
||||||
|
import "core:os"
|
||||||
|
import "core:os/os2"
|
||||||
import "core:path/filepath"
|
import "core:path/filepath"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
@@ -46,6 +48,21 @@ destroy_build_config :: proc(cfg: ^BuildConfig) {
|
|||||||
// package the config names. build.bro is only checked (never lowered/emitted),
|
// package the config names. build.bro is only checked (never lowered/emitted),
|
||||||
// so the config is read straight from the HIR.
|
// so the config is read straight from the HIR.
|
||||||
run_build :: proc(root: string) -> int {
|
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)
|
||||||
@@ -61,10 +78,10 @@ run_build :: proc(root: string) -> int {
|
|||||||
defer vmem.arena_destroy(&arena)
|
defer vmem.arena_destroy(&arena)
|
||||||
a := vmem.arena_allocator(&arena)
|
a := vmem.arena_allocator(&arena)
|
||||||
|
|
||||||
ast_module, loaded := loader.load(root, &sources, &diagnostics, &symbols, a, a, cimport.Options{}, target.DEFAULT)
|
ast_module, loaded := loader.load(project_root, &sources, &diagnostics, &symbols, a, a, cimport.Options{}, target.DEFAULT, project_root)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
fmt.eprintln("failed to load build root:", root)
|
fmt.eprintln("failed to load build root:", project_root)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
// 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
|
||||||
@@ -82,9 +99,81 @@ run_build :: proc(root: string) -> int {
|
|||||||
}
|
}
|
||||||
defer destroy_build_config(&cfg)
|
defer destroy_build_config(&cfg)
|
||||||
|
|
||||||
program := filepath.join({root, cfg.source_dir})
|
program := filepath.join({project_root, cfg.source_dir})
|
||||||
defer delete(program)
|
defer delete(program)
|
||||||
return compile_package(program, cfg.output_name, cfg.link_arguments, target.DEFAULT, cfg.c_options)
|
output, output_ok := build_output_path(project_root, cfg.output_name)
|
||||||
|
if !output_ok {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
defer delete(output)
|
||||||
|
return compile_package(program, output, cfg.link_arguments, target.DEFAULT, cfg.c_options, project_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_output_name :: proc(name: string) -> bool {
|
||||||
|
return len(name) > 0 && name != "." && name != ".." &&
|
||||||
|
!strings.contains(name, "/") && !strings.contains(name, "\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
|
||||||
|
if !valid_output_name(name) {
|
||||||
|
fmt.eprintfln("build.bro: config 'name' must be a plain executable name, got '%s'", name)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
build_dir, dir_error := filepath.join({project_root, "build"}, allocator)
|
||||||
|
if dir_error != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if err := os2.make_directory_all(build_dir); err != nil {
|
||||||
|
fmt.eprintfln("failed to create build directory '%s': %v", build_dir, err)
|
||||||
|
delete(build_dir, allocator)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
output, output_error := filepath.join({build_dir, name}, allocator)
|
||||||
|
delete(build_dir, allocator)
|
||||||
|
if output_error != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return output, true
|
||||||
|
}
|
||||||
|
|
||||||
|
find_build_root :: proc(allocator := context.allocator) -> (string, bool) {
|
||||||
|
current := os.get_current_directory(allocator)
|
||||||
|
if len(current) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
defer delete(current, allocator)
|
||||||
|
return find_build_root_from(current, allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
find_build_root_from :: proc(start: string, allocator := context.allocator) -> (string, bool) {
|
||||||
|
current, current_ok := filepath.abs(start, allocator)
|
||||||
|
if !current_ok {
|
||||||
|
current = strings.clone(start, allocator)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
build_path, build_error := filepath.join({current, "build.bro"}, allocator)
|
||||||
|
if build_error != nil {
|
||||||
|
delete(current, allocator)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
found := os.exists(build_path)
|
||||||
|
delete(build_path, allocator)
|
||||||
|
if found {
|
||||||
|
return current, true
|
||||||
|
}
|
||||||
|
if current == "/" {
|
||||||
|
delete(current, allocator)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
parent := filepath.dir(current, allocator)
|
||||||
|
if parent == current {
|
||||||
|
delete(parent, allocator)
|
||||||
|
delete(current, allocator)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
delete(current, allocator)
|
||||||
|
current = parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build_bro_has_errors reports whether checking build.bro produced any diagnostic
|
// build_bro_has_errors reports whether checking build.bro produced any diagnostic
|
||||||
|
|||||||
@@ -616,9 +616,22 @@ add_label_shadow_diagnostic :: proc(ctx: ^Build_Ctx, span: source.Span, label: s
|
|||||||
if !symbol.is_valid(label) {
|
if !symbol.is_valid(label) {
|
||||||
return source.INVALID_DIAGNOSTIC
|
return source.INVALID_DIAGNOSTIC
|
||||||
}
|
}
|
||||||
|
yield_targets := ctx.yield_targets^[:]
|
||||||
|
if len(yield_targets) > 0 && yield_targets[len(yield_targets) - 1].label == label {
|
||||||
|
label_is_active_loop := false
|
||||||
|
for loop_label in ctx.loop_labels^[:] {
|
||||||
|
if loop_label == label {
|
||||||
|
label_is_active_loop = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !label_is_active_loop {
|
||||||
|
yield_targets = yield_targets[:len(yield_targets) - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
return add_shadow_diagnostic(
|
return add_shadow_diagnostic(
|
||||||
ctx.checker, span, label, "label",
|
ctx.checker, span, label, "label",
|
||||||
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], yield_targets,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ compile_package :: proc(
|
|||||||
link_arguments: []linker.Argument = nil,
|
link_arguments: []linker.Argument = nil,
|
||||||
selected := target.DEFAULT,
|
selected := target.DEFAULT,
|
||||||
c_options := cimport.Options{},
|
c_options := cimport.Options{},
|
||||||
|
project_root := "",
|
||||||
) -> int {
|
) -> int {
|
||||||
sources := source.init_store()
|
sources := source.init_store()
|
||||||
defer source.destroy_store(&sources)
|
defer source.destroy_store(&sources)
|
||||||
@@ -75,6 +76,7 @@ compile_package :: proc(
|
|||||||
vmem.arena_allocator(&parser_arena),
|
vmem.arena_allocator(&parser_arena),
|
||||||
c_options,
|
c_options,
|
||||||
selected,
|
selected,
|
||||||
|
project_root if len(project_root) > 0 else input_path,
|
||||||
)
|
)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
|
|||||||
@@ -1416,11 +1416,13 @@ load :: proc(
|
|||||||
allocator := context.allocator,
|
allocator := context.allocator,
|
||||||
c_options := cimport.Options{},
|
c_options := cimport.Options{},
|
||||||
selected := target.DEFAULT,
|
selected := target.DEFAULT,
|
||||||
|
project_root_path := "",
|
||||||
) -> (ast.Module, bool) {
|
) -> (ast.Module, bool) {
|
||||||
module := ast.init_module(allocator)
|
module := ast.init_module(allocator)
|
||||||
project_root, project_root_ok := filepath.abs(".", allocator)
|
project_root_source := project_root_path if len(project_root_path) > 0 else root_path
|
||||||
|
project_root, project_root_ok := filepath.abs(project_root_source, allocator)
|
||||||
if !project_root_ok {
|
if !project_root_ok {
|
||||||
project_root = strings.clone(".", allocator)
|
project_root = strings.clone(project_root_source, allocator)
|
||||||
}
|
}
|
||||||
state := State{
|
state := State{
|
||||||
module=&module,
|
module=&module,
|
||||||
|
|||||||
+234
-10
@@ -736,6 +736,8 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
|
|||||||
"vendor/include",
|
"vendor/include",
|
||||||
"--c-define",
|
"--c-define",
|
||||||
"FEATURE=1",
|
"FEATURE=1",
|
||||||
|
"--root",
|
||||||
|
".",
|
||||||
"--target",
|
"--target",
|
||||||
"aarch64-macos",
|
"aarch64-macos",
|
||||||
})
|
})
|
||||||
@@ -746,6 +748,7 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
|
|||||||
testing.expect(t, valid)
|
testing.expect(t, valid)
|
||||||
testing.expect_value(t, options.input_path, "app")
|
testing.expect_value(t, options.input_path, "app")
|
||||||
testing.expect_value(t, options.output_path, "app.out")
|
testing.expect_value(t, options.output_path, "app.out")
|
||||||
|
testing.expect_value(t, options.project_root, ".")
|
||||||
testing.expect_value(t, len(options.link_arguments), 4)
|
testing.expect_value(t, len(options.link_arguments), 4)
|
||||||
testing.expect_value(t, options.link_arguments[0].kind, linker.Kind.Input)
|
testing.expect_value(t, options.link_arguments[0].kind, linker.Kind.Input)
|
||||||
testing.expect_value(t, options.link_arguments[0].value, "native.c")
|
testing.expect_value(t, options.link_arguments[0].value, "native.c")
|
||||||
@@ -759,6 +762,7 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
|
|||||||
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
|
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
|
||||||
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
|
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
|
||||||
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"})
|
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"})
|
||||||
|
_, duplicate_root_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--root", ".", "--root", "other"})
|
||||||
_, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
|
_, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
|
||||||
_, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"})
|
_, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"})
|
||||||
_, legacy_link := parse_cli_args([]string{"brolang", "app", "-o", "out", "--link", "native.c"})
|
_, legacy_link := parse_cli_args([]string{"brolang", "app", "-o", "out", "--link", "native.c"})
|
||||||
@@ -767,6 +771,7 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
|
|||||||
testing.expect(t, !unknown_valid)
|
testing.expect(t, !unknown_valid)
|
||||||
testing.expect(t, !incomplete_valid)
|
testing.expect(t, !incomplete_valid)
|
||||||
testing.expect(t, !duplicate_output_valid)
|
testing.expect(t, !duplicate_output_valid)
|
||||||
|
testing.expect(t, !duplicate_root_valid)
|
||||||
testing.expect(t, !duplicate_empty_output_valid)
|
testing.expect(t, !duplicate_empty_output_valid)
|
||||||
testing.expect(t, !invalid_target)
|
testing.expect(t, !invalid_target)
|
||||||
testing.expect(t, !legacy_link)
|
testing.expect(t, !legacy_link)
|
||||||
@@ -2560,22 +2565,194 @@ build_command_is_recognized :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
build_subcommand_compiles_and_runs :: proc(t: ^testing.T) {
|
build_subcommand_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
defer _ = os.remove("hello")
|
output := "examples/build/hello/build/hello"
|
||||||
|
defer _ = os2.remove_all("examples/build/hello/build")
|
||||||
status := compiler_core.run_build("examples/build/hello")
|
status := compiler_core.run_build("examples/build/hello")
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable("./hello")
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
build_subcommand_links_c_source_via_list_field :: proc(t: ^testing.T) {
|
build_subcommand_links_c_source_via_list_field :: proc(t: ^testing.T) {
|
||||||
defer _ = os.remove("manual_build")
|
output := "examples/build/manual/build/manual_build"
|
||||||
|
defer _ = os2.remove_all("examples/build/manual/build")
|
||||||
status := compiler_core.run_build("examples/build/manual")
|
status := compiler_core.run_build("examples/build/manual")
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable("./manual_build")
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 42)
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
new_project_creates_layout_and_builds :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-new-project"
|
||||||
|
output := "/tmp/brolang-test-new-project/build/brolang-test-new-project"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
|
||||||
|
testing.expect_value(t, run_new_project(root), 0)
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-new-project/source"))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-new-project/std"))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-new-project/ffi"))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-new-project/vendor"))
|
||||||
|
testing.expect(t, os.exists("/tmp/brolang-test-new-project/build.bro"))
|
||||||
|
testing.expect(t, os.exists("/tmp/brolang-test-new-project/source/main.bro"))
|
||||||
|
|
||||||
|
status := compiler_core.run_build(root)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
init_project_creates_missing_layout_and_preserves_existing_files :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-init-project"
|
||||||
|
output := "/tmp/brolang-test-init-project/build/brolang-test-init-project"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
|
||||||
|
testing.expect(t, os2.make_directory_all(root) == nil)
|
||||||
|
testing.expect(t, init_project(root))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-init-project/source"))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-init-project/std"))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-init-project/ffi"))
|
||||||
|
testing.expect(t, os.is_dir("/tmp/brolang-test-init-project/vendor"))
|
||||||
|
|
||||||
|
status := compiler_core.run_build(root)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
|
||||||
|
custom_build := "custom build"
|
||||||
|
custom_main := "custom main"
|
||||||
|
testing.expect(t, os.write_entire_file("/tmp/brolang-test-init-project/build.bro", transmute([]byte)custom_build))
|
||||||
|
testing.expect(t, os.write_entire_file("/tmp/brolang-test-init-project/source/main.bro", transmute([]byte)custom_main))
|
||||||
|
testing.expect(t, init_project(root))
|
||||||
|
build_data, build_ok := os.read_entire_file("/tmp/brolang-test-init-project/build.bro")
|
||||||
|
defer delete(build_data)
|
||||||
|
main_data, main_ok := os.read_entire_file("/tmp/brolang-test-init-project/source/main.bro")
|
||||||
|
defer delete(main_data)
|
||||||
|
testing.expect(t, build_ok && string(build_data) == custom_build)
|
||||||
|
testing.expect(t, main_ok && string(main_data) == custom_main)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
build_root_search_finds_nearest_parent_build_file :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-build-search"
|
||||||
|
nested := "/tmp/brolang-test-build-search/source/nested"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
|
||||||
|
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)
|
||||||
|
defer delete(found)
|
||||||
|
testing.expect(t, ok)
|
||||||
|
testing.expect(t, strings.has_suffix(found, "/brolang-test-build-search"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
build_subcommand_discovered_root_writes_to_root_build_dir :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-build-nested"
|
||||||
|
nested := "/tmp/brolang-test-build-nested/source/nested"
|
||||||
|
output := "/tmp/brolang-test-build-nested/build/brolang-test-build-nested"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
|
||||||
|
testing.expect(t, init_project(root))
|
||||||
|
testing.expect(t, os2.make_directory_all(nested) == nil)
|
||||||
|
found, ok := compiler_core.find_build_root_from(nested)
|
||||||
|
defer delete(found)
|
||||||
|
testing.expect(t, ok)
|
||||||
|
|
||||||
|
status := compiler_core.run_build(found)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
testing.expect(t, os.exists(output))
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
build_subcommand_rejects_path_like_output_name :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-build-invalid-name"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
|
||||||
|
testing.expect(t, init_project(root))
|
||||||
|
text := `b :: import "@std/build"
|
||||||
|
|
||||||
|
config :: b.BuildConfig{
|
||||||
|
name = "bad/name",
|
||||||
|
source = "source",
|
||||||
|
libraries = &[],
|
||||||
|
lib_paths = &[],
|
||||||
|
includes = &[],
|
||||||
|
defines = &[],
|
||||||
|
links = &[],
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file("/tmp/brolang-test-build-invalid-name/build.bro", transmute([]byte)text))
|
||||||
|
status := compiler_core.run_build(root)
|
||||||
|
testing.expect_value(t, status, 2)
|
||||||
|
testing.expect(t, !os.exists("/tmp/brolang-test-build-invalid-name/build/bad/name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
project_root_imports_resolve_under_passed_root :: proc(t: ^testing.T) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
ast_module, loaded := loader.load(
|
||||||
|
"examples/build/raylib/source",
|
||||||
|
&sources,
|
||||||
|
&diagnostics,
|
||||||
|
&symbols,
|
||||||
|
context.allocator,
|
||||||
|
context.allocator,
|
||||||
|
cimport.Options{},
|
||||||
|
target.DEFAULT,
|
||||||
|
"examples/build/raylib",
|
||||||
|
)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
|
||||||
|
found_vendor := false
|
||||||
|
for pkg in ast_module.packages {
|
||||||
|
found_vendor = found_vendor || strings.has_suffix(pkg.path, "/examples/build/raylib/vendor/raylib")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_vendor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
direct_compile_defaults_project_root_to_input_package_and_allows_override :: proc(t: ^testing.T) {
|
||||||
|
root := "/tmp/brolang-test-direct-root"
|
||||||
|
app := "/tmp/brolang-test-direct-root/app"
|
||||||
|
dep := "/tmp/brolang-test-direct-root/dep"
|
||||||
|
output := "/tmp/brolang-test-direct-root-out"
|
||||||
|
_ = os2.remove_all(root)
|
||||||
|
_ = os.remove(output)
|
||||||
|
defer _ = os2.remove_all(root)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
|
||||||
|
testing.expect(t, os2.make_directory_all(app) == nil)
|
||||||
|
testing.expect(t, os2.make_directory_all(dep) == nil)
|
||||||
|
main_text := "dep :: import \"@dep\"\nmain func() i32 { return dep.value }\n"
|
||||||
|
dep_text := "value i32 :: 7\n"
|
||||||
|
testing.expect(t, os.write_entire_file("/tmp/brolang-test-direct-root/app/main.bro", transmute([]byte)main_text))
|
||||||
|
testing.expect(t, os.write_entire_file("/tmp/brolang-test-direct-root/dep/dep.bro", transmute([]byte)dep_text))
|
||||||
|
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(app, output), 1)
|
||||||
|
status := compiler_core.compile_package(app, output, nil, target.DEFAULT, cimport.Options{}, root)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 7)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
c_printf_accepts_a_string_literal :: proc(t: ^testing.T) {
|
c_printf_accepts_a_string_literal :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-printf"
|
output := "/tmp/brolang-test-printf"
|
||||||
@@ -2629,7 +2806,7 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
|||||||
milestone_25_heap_compiles_and_runs :: proc(t: ^testing.T) {
|
milestone_25_heap_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-heap"
|
output := "/tmp/brolang-test-heap"
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
status := compiler_core.compile_package("examples/programs/heap", output)
|
status := compiler_core.compile_package("examples/programs/heap", output, nil, target.DEFAULT, cimport.Options{}, ".")
|
||||||
testing.expect_value(t, status, 0)
|
testing.expect_value(t, status, 0)
|
||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
@@ -2643,7 +2820,7 @@ milestone_25_heap_emits_libc_alloc_declarations :: proc(t: ^testing.T) {
|
|||||||
defer source.destroy_diagnostics(&diagnostics)
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
symbols := symbol.init_table()
|
symbols := symbol.init_table()
|
||||||
defer symbol.destroy_table(&symbols)
|
defer symbol.destroy_table(&symbols)
|
||||||
ast_module, loaded := loader.load("examples/programs/heap", &sources, &diagnostics, &symbols)
|
ast_module, loaded := loader.load("examples/programs/heap", &sources, &diagnostics, &symbols, context.allocator, context.allocator, cimport.Options{}, target.DEFAULT, ".")
|
||||||
defer ast.destroy_module(&ast_module)
|
defer ast.destroy_module(&ast_module)
|
||||||
testing.expect(t, loaded)
|
testing.expect(t, loaded)
|
||||||
|
|
||||||
@@ -2818,6 +2995,35 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 42)
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
value_loop_label_does_not_shadow_own_yield_target :: proc(t: ^testing.T) {
|
||||||
|
text := `main func() i32 {
|
||||||
|
idx :: for 0..10 |i| hit: {
|
||||||
|
if (i == 3) yield :hit i
|
||||||
|
yield none
|
||||||
|
}
|
||||||
|
if idx |found| {
|
||||||
|
if (found == 3) return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
native_union_compiles_and_runs :: proc(t: ^testing.T) {
|
native_union_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-unions"
|
output := "/tmp/brolang-test-unions"
|
||||||
@@ -4306,7 +4512,7 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
|||||||
command := backend.build_command("module.ll", "program", arguments, target.DEFAULT, c_options)
|
command := backend.build_command("module.ll", "program", arguments, target.DEFAULT, c_options)
|
||||||
defer backend.destroy_command(command)
|
defer backend.destroy_command(command)
|
||||||
|
|
||||||
expected := []string{
|
expected_prefix := []string{
|
||||||
"/usr/bin/env",
|
"/usr/bin/env",
|
||||||
"zig",
|
"zig",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -4314,6 +4520,24 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
|||||||
"aarch64-macos",
|
"aarch64-macos",
|
||||||
"-Wno-override-module",
|
"-Wno-override-module",
|
||||||
"-Wno-unused-command-line-argument",
|
"-Wno-unused-command-line-argument",
|
||||||
|
}
|
||||||
|
testing.expect(t, len(command) >= len(expected_prefix))
|
||||||
|
for value, index in expected_prefix {
|
||||||
|
testing.expect_value(t, command[index], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
tail_index := len(expected_prefix)
|
||||||
|
if sdk, ok := backend.macos_sdk_root(); ok {
|
||||||
|
defer delete(sdk)
|
||||||
|
frameworks := fmt.tprintf("-F%s/System/Library/Frameworks", sdk)
|
||||||
|
lib_dir := fmt.tprintf("-L%s/usr/lib", sdk)
|
||||||
|
testing.expect(t, len(command) >= tail_index + 2)
|
||||||
|
testing.expect_value(t, command[tail_index], frameworks)
|
||||||
|
testing.expect_value(t, command[tail_index + 1], lib_dir)
|
||||||
|
tail_index += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_tail := []string{
|
||||||
"module.ll",
|
"module.ll",
|
||||||
"-Ivendor/include",
|
"-Ivendor/include",
|
||||||
"-DFEATURE=1",
|
"-DFEATURE=1",
|
||||||
@@ -4324,9 +4548,9 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
|||||||
"-o",
|
"-o",
|
||||||
"program",
|
"program",
|
||||||
}
|
}
|
||||||
testing.expect_value(t, len(command), len(expected))
|
testing.expect_value(t, len(command), tail_index + len(expected_tail))
|
||||||
for value, index in expected {
|
for value, index in expected_tail {
|
||||||
testing.expect_value(t, command[index], value)
|
testing.expect_value(t, command[tail_index + index], value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Build configuration surface for `brolang build` (v0).
|
||||||
|
BuildConfig :: struct {
|
||||||
|
name []u8
|
||||||
|
source []u8
|
||||||
|
libraries [][]u8
|
||||||
|
lib_paths [][]u8
|
||||||
|
includes [][]u8
|
||||||
|
defines [][]u8
|
||||||
|
links [][]u8
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Build configuration surface for `brolang build` (v0).
|
||||||
|
BuildConfig :: struct {
|
||||||
|
name []u8
|
||||||
|
source []u8
|
||||||
|
libraries [][]u8
|
||||||
|
lib_paths [][]u8
|
||||||
|
includes [][]u8
|
||||||
|
defines [][]u8
|
||||||
|
links [][]u8
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Illustrative build.bro for a raylib program (not run in CI: needs raylib
|
# Illustrative build.bro for a raylib program (not run in CI: needs raylib
|
||||||
# installed and the game sources under ./game).
|
# installed).
|
||||||
#
|
#
|
||||||
# Generate the raylib bindings once with:
|
# Generate the raylib bindings once with:
|
||||||
# brolang translate-c /opt/homebrew/Cellar/raylib/*/include/raylib.h > game/raylib.bro
|
# brolang translate-c /opt/homebrew/Cellar/raylib/*/include/raylib.h > game/raylib.bro
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Ball :: struct {
|
|||||||
# A frame's worth of player intent, as a tagged union. Each arm carries exactly
|
# A frame's worth of player intent, as a tagged union. Each arm carries exactly
|
||||||
# the data that action needs (or `void` when it needs none).
|
# the data that action needs (or `void` when it needs none).
|
||||||
Command :: union(enum) {
|
Command :: union(enum) {
|
||||||
spawn Vector2 # spawn a shape at this point
|
spawn rl.Vector2 # spawn a shape at this point
|
||||||
push struct { dx f32, dy f32 } # blow every shape this way
|
push struct { dx f32, dy f32 } # blow every shape this way
|
||||||
clear void
|
clear void
|
||||||
idle void
|
idle void
|
||||||
@@ -55,11 +55,11 @@ SpawnError :: union(enum) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# value-match used as an expression source: each arm yields a Color.
|
# value-match used as an expression source: each arm yields a Color.
|
||||||
color_for func(k Kind) Color {
|
color_for func(k Kind) rl.Color {
|
||||||
col :: match k {
|
col :: match k {
|
||||||
.circle: Color{ r = 235, g = 90, b = 90, a = 255 }
|
.circle: rl.Color{ r = 235, g = 90, b = 90, a = 255 }
|
||||||
.square: Color{ r = 90, g = 205, b = 130, a = 255 }
|
.square: rl.Color{ r = 90, g = 205, b = 130, a = 255 }
|
||||||
.triangle: Color{ r = 105, g = 160, b = 245, a = 255 }
|
.triangle: rl.Color{ r = 105, g = 160, b = 245, a = 255 }
|
||||||
}
|
}
|
||||||
return col
|
return col
|
||||||
}
|
}
|
||||||
@@ -76,19 +76,19 @@ next_kind func(k Kind) Kind {
|
|||||||
# Read this frame's input into a single Command (contextual union construction:
|
# Read this frame's input into a single Command (contextual union construction:
|
||||||
# `.clear`, `.spawn{...}`, `.push{...}` are built against the return type).
|
# `.clear`, `.spawn{...}`, `.push{...}` are built against the return type).
|
||||||
read_command func() Command {
|
read_command func() Command {
|
||||||
if rl.IsMouseButtonPressed(MOUSE_BUTTON_LEFT) return .spawn{ GetMousePosition() }
|
if rl.IsMouseButtonPressed(rl.MOUSE_BUTTON_LEFT) return .spawn{ rl.GetMousePosition() }
|
||||||
if rl.IsKeyPressed(KEY_SPACE) return .clear
|
if rl.IsKeyPressed(rl.KEY_SPACE) return .clear
|
||||||
|
|
||||||
fx f32 = 0.0
|
fx f32 = 0.0
|
||||||
fy f32 = 0.0
|
fy f32 = 0.0
|
||||||
if rl.IsKeyDown(KEY_A) fx -= FORCE
|
if rl.IsKeyDown(rl.KEY_A) fx -= FORCE
|
||||||
if rl.IsKeyDown(KEY_D) fx += FORCE
|
if rl.IsKeyDown(rl.KEY_D) fx += FORCE
|
||||||
if rl.IsKeyDown(KEY_W) fy -= FORCE
|
if rl.IsKeyDown(rl.KEY_W) fy -= FORCE
|
||||||
if rl.IsKeyDown(KEY_S) fy += FORCE
|
if rl.IsKeyDown(rl.KEY_S) fy += FORCE
|
||||||
if rl.IsKeyDown(KEY_LEFT) fx -= FORCE
|
if rl.IsKeyDown(rl.KEY_LEFT) fx -= FORCE
|
||||||
if rl.IsKeyDown(KEY_RIGHT) fx += FORCE
|
if rl.IsKeyDown(rl.KEY_RIGHT) fx += FORCE
|
||||||
if rl.IsKeyDown(KEY_UP) fy -= FORCE
|
if rl.IsKeyDown(rl.KEY_UP) fy -= FORCE
|
||||||
if rl.IsKeyDown(KEY_DOWN) fy += FORCE
|
if rl.IsKeyDown(rl.KEY_DOWN) fy += FORCE
|
||||||
|
|
||||||
moved :: fx != 0.0 or fy != 0.0
|
moved :: fx != 0.0 or fy != 0.0
|
||||||
if (moved) return .push{ dx = fx, dy = fy }
|
if (moved) return .push{ dx = fx, dy = fy }
|
||||||
@@ -130,7 +130,7 @@ step func(b @mut Ball) void {
|
|||||||
|
|
||||||
draw_ball func(b @Ball, highlight bool) void {
|
draw_ball func(b @Ball, highlight bool) void {
|
||||||
col :: color_for(b.kind)
|
col :: color_for(b.kind)
|
||||||
center Vector2 = Vector2{ x = b.x, y = b.y }
|
center rl.Vector2 = rl.Vector2{ x = b.x, y = b.y }
|
||||||
match b.kind {
|
match b.kind {
|
||||||
.circle: rl.DrawCircleV(center, b.radius, col)
|
.circle: rl.DrawCircleV(center, b.radius, col)
|
||||||
.square: rl.DrawPoly(center, 4, b.radius, 45.0, col)
|
.square: rl.DrawPoly(center, 4, b.radius, 45.0, col)
|
||||||
@@ -143,7 +143,7 @@ draw_ball func(b @Ball, highlight bool) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
rl.SetConfigFlags(FLAG_MSAA_4X_HINT)
|
rl.SetConfigFlags(rl.FLAG_MSAA_4X_HINT)
|
||||||
rl.InitWindow(W, H, "brolang — bouncing shapes")
|
rl.InitWindow(W, H, "brolang — bouncing shapes")
|
||||||
defer rl.CloseWindow() # runs on every exit path out of main
|
defer rl.CloseWindow() # runs on every exit path out of main
|
||||||
rl.SetTargetFPS(60)
|
rl.SetTargetFPS(60)
|
||||||
@@ -210,9 +210,9 @@ main func() i32 {
|
|||||||
|
|
||||||
# --- which shape is under the cursor? (optional via a value-loop) ---
|
# --- which shape is under the cursor? (optional via a value-loop) ---
|
||||||
mouse :: rl.GetMousePosition()
|
mouse :: rl.GetMousePosition()
|
||||||
sel :: for 0..(count) |i| blk: {
|
sel :: for 0..(count) |i| hover: {
|
||||||
c Vector2 = Vector2{ x = balls[i].x, y = balls[i].y }
|
c rl.Vector2 = rl.Vector2{ x = balls[i].x, y = balls[i].y }
|
||||||
if rl.CheckCollisionPointCircle(mouse, c, balls[i].radius) yield :blk i
|
if rl.CheckCollisionPointCircle(mouse, c, balls[i].radius) yield :hover i
|
||||||
yield none
|
yield none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Build configuration surface for `brolang build` (v0).
|
||||||
|
BuildConfig :: struct {
|
||||||
|
name []u8
|
||||||
|
source []u8
|
||||||
|
libraries [][]u8
|
||||||
|
lib_paths [][]u8
|
||||||
|
includes [][]u8
|
||||||
|
defines [][]u8
|
||||||
|
links [][]u8
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
heap :: import "@std/mem/heap"
|
heap :: import "@std/mem/heap"
|
||||||
|
|
||||||
#printf c_func(fmt *c_char, ...) c_int
|
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
memory ?*mut u8 = heap.alloc(4)
|
memory ?*mut u8 = heap.alloc(4)
|
||||||
defer heap.free(memory)
|
defer heap.free(memory)
|
||||||
@@ -11,11 +9,6 @@ main func() i32 {
|
|||||||
bytes[1] = 20
|
bytes[1] = 20
|
||||||
bytes[2] = bytes[0] + bytes[1]
|
bytes[2] = bytes[0] + bytes[1]
|
||||||
|
|
||||||
_ = printf("bytes[0]: %d\n", bytes[0])
|
|
||||||
_ = printf("bytes[1]: %d\n", bytes[1])
|
|
||||||
_ = printf("bytes[2]: %d\n", bytes[2])
|
|
||||||
_ = printf("bytes[3]: %d\n", bytes[3])
|
|
||||||
|
|
||||||
if (bytes[2] != 30) {
|
if (bytes[2] != 30) {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import "core:strings"
|
|||||||
Cli_Options :: struct {
|
Cli_Options :: struct {
|
||||||
input_path: string,
|
input_path: string,
|
||||||
output_path: string,
|
output_path: string,
|
||||||
|
project_root: string,
|
||||||
link_arguments: []linker.Argument,
|
link_arguments: []linker.Argument,
|
||||||
c_options: cimport.Options,
|
c_options: cimport.Options,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
@@ -71,6 +72,11 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
|||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
options.target = selected
|
options.target = selected
|
||||||
|
case "--root":
|
||||||
|
if len(options.project_root) > 0 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
options.project_root = value
|
||||||
case:
|
case:
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -87,13 +93,19 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
|||||||
|
|
||||||
print_usage :: proc() {
|
print_usage :: proc() {
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
"usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
|
"usage: brolang <package-directory> -o <executable> [--root <project-root>] [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
|
||||||
)
|
)
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang translate-c|--translate-c <header.h> [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
" brolang translate-c|--translate-c <header.h> [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
||||||
)
|
)
|
||||||
fmt.eprintln(
|
fmt.eprintln(
|
||||||
" brolang build [root] (reads root/build.bro; root defaults to the current directory)",
|
" brolang build [root] (reads root/build.bro; writes root/build/name)",
|
||||||
|
)
|
||||||
|
fmt.eprintln(
|
||||||
|
" brolang new <project-path>",
|
||||||
|
)
|
||||||
|
fmt.eprintln(
|
||||||
|
" brolang init",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +117,230 @@ is_build_command :: proc(arg: string) -> bool {
|
|||||||
return arg == "build"
|
return arg == "build"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_new_command :: proc(arg: string) -> bool {
|
||||||
|
return arg == "new"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_init_command :: proc(arg: string) -> bool {
|
||||||
|
return arg == "init"
|
||||||
|
}
|
||||||
|
|
||||||
|
template_root_valid :: proc(root: string) -> bool {
|
||||||
|
std_build, std_error := filepath.join({root, "std", "build", "build.bro"})
|
||||||
|
if std_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(std_build)
|
||||||
|
ffi_stdio, ffi_error := filepath.join({root, "ffi", "c", "stdio.bro"})
|
||||||
|
if ffi_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(ffi_stdio)
|
||||||
|
return os.exists(std_build) && os.exists(ffi_stdio)
|
||||||
|
}
|
||||||
|
|
||||||
|
find_template_root :: proc(allocator := context.allocator) -> (string, bool) {
|
||||||
|
if exe, exe_error := os2.get_executable_path(allocator); exe_error == nil {
|
||||||
|
defer delete(exe, allocator)
|
||||||
|
exe_dir := filepath.dir(exe, allocator)
|
||||||
|
defer delete(exe_dir, allocator)
|
||||||
|
if template_root_valid(exe_dir) {
|
||||||
|
return strings.clone(exe_dir, allocator), true
|
||||||
|
}
|
||||||
|
parent := filepath.dir(exe_dir, allocator)
|
||||||
|
defer delete(parent, allocator)
|
||||||
|
if template_root_valid(parent) {
|
||||||
|
return strings.clone(parent, allocator), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cwd := os.get_current_directory(allocator)
|
||||||
|
defer delete(cwd, allocator)
|
||||||
|
if template_root_valid(cwd) {
|
||||||
|
return strings.clone(cwd, allocator), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_directory :: proc(path: string) -> bool {
|
||||||
|
if os.exists(path) {
|
||||||
|
if os.is_dir(path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
fmt.eprintfln("path exists and is not a directory: %s", path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := os2.make_directory_all(path); err != nil {
|
||||||
|
fmt.eprintfln("failed to create directory '%s': %v", path, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_child_directory :: proc(root, name: string) -> bool {
|
||||||
|
path, err := filepath.join({root, name})
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(path)
|
||||||
|
return ensure_directory(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
write_file_if_missing :: proc(path, text: string) -> bool {
|
||||||
|
if os.exists(path) {
|
||||||
|
if os.is_dir(path) {
|
||||||
|
fmt.eprintfln("path exists and is not a file: %s", path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !os.write_entire_file(path, transmute([]byte)text) {
|
||||||
|
fmt.eprintfln("failed to write file '%s'", path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
write_escaped_brolang_string :: proc(builder: ^strings.Builder, value: string) {
|
||||||
|
for b in transmute([]byte)value {
|
||||||
|
if b == '\\' || b == '"' {
|
||||||
|
strings.write_byte(builder, '\\')
|
||||||
|
}
|
||||||
|
strings.write_byte(builder, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default_build_bro :: proc(project_name: string, allocator := context.allocator) -> string {
|
||||||
|
name := project_name
|
||||||
|
if len(name) == 0 || name == "." || name == "/" {
|
||||||
|
name = "app"
|
||||||
|
}
|
||||||
|
builder := strings.builder_make()
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
strings.write_string(&builder, "b :: import \"@std/build\"\n\nconfig :: b.BuildConfig{\n\tname = \"")
|
||||||
|
write_escaped_brolang_string(&builder, name)
|
||||||
|
strings.write_string(&builder, "\",\n\tsource = \"source\",\n\tlibraries = &[],\n\tlib_paths = &[],\n\tincludes = &[],\n\tdefines = &[],\n\tlinks = &[],\n}\n")
|
||||||
|
return strings.clone(strings.to_string(builder), allocator)
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_default_sources :: proc(root: string) -> bool {
|
||||||
|
source_dir, source_error := filepath.join({root, "source"})
|
||||||
|
if source_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(source_dir)
|
||||||
|
if !ensure_directory(source_dir) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
main_path, main_error := filepath.join({source_dir, "main.bro"})
|
||||||
|
if main_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(main_path)
|
||||||
|
return write_file_if_missing(main_path, "main func() i32 {\n\treturn 0\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_tree_if_missing :: proc(root, template_root, name: string) -> bool {
|
||||||
|
dst, dst_error := filepath.join({root, name})
|
||||||
|
if dst_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(dst)
|
||||||
|
if os.exists(dst) {
|
||||||
|
if os.is_dir(dst) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
fmt.eprintfln("path exists and is not a directory: %s", dst)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
src, src_error := filepath.join({template_root, name})
|
||||||
|
if src_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(src)
|
||||||
|
if err := os2.copy_directory_all(dst, src); err != nil {
|
||||||
|
fmt.eprintfln("failed to copy '%s' to '%s': %v", src, dst, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
init_project :: proc(root: string) -> bool {
|
||||||
|
if !ensure_directory(root) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !ensure_default_sources(root) || !ensure_child_directory(root, "vendor") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
needs_template := true
|
||||||
|
std_path, std_error := filepath.join({root, "std"})
|
||||||
|
ffi_path, ffi_error := filepath.join({root, "ffi"})
|
||||||
|
if std_error == nil && ffi_error == nil {
|
||||||
|
needs_template = !os.exists(std_path) || !os.exists(ffi_path)
|
||||||
|
}
|
||||||
|
if std_error == nil {
|
||||||
|
delete(std_path)
|
||||||
|
}
|
||||||
|
if ffi_error == nil {
|
||||||
|
delete(ffi_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
template_root := ""
|
||||||
|
if needs_template {
|
||||||
|
ok: bool
|
||||||
|
template_root, ok = find_template_root()
|
||||||
|
if !ok {
|
||||||
|
fmt.eprintln("could not find bundled std/ffi sources")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(template_root)
|
||||||
|
if !copy_tree_if_missing(root, template_root, "std") ||
|
||||||
|
!copy_tree_if_missing(root, template_root, "ffi") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build_path, build_error := filepath.join({root, "build.bro"})
|
||||||
|
if build_error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(build_path)
|
||||||
|
project_name := filepath.base(root)
|
||||||
|
build_text := default_build_bro(project_name)
|
||||||
|
defer delete(build_text)
|
||||||
|
return write_file_if_missing(build_path, build_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
new_project :: proc(path: string) -> bool {
|
||||||
|
if os.exists(path) {
|
||||||
|
fmt.eprintfln("project path already exists: %s", path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
template_root, ok := find_template_root()
|
||||||
|
if !ok {
|
||||||
|
fmt.eprintln("could not find bundled std/ffi sources")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer delete(template_root)
|
||||||
|
if !ensure_directory(path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !init_project(path) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
run_init_project :: proc() -> int {
|
||||||
|
cwd := os.get_current_directory()
|
||||||
|
defer delete(cwd)
|
||||||
|
return 0 if init_project(cwd) else 2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_new_project :: proc(path: string) -> int {
|
||||||
|
return 0 if new_project(path) else 2
|
||||||
|
}
|
||||||
|
|
||||||
zig_lib_dir_from_env_output :: proc(text: string, allocator := context.allocator) -> (string, bool) {
|
zig_lib_dir_from_env_output :: proc(text: string, allocator := context.allocator) -> (string, bool) {
|
||||||
rest := text
|
rest := text
|
||||||
prefix := ".lib_dir = \""
|
prefix := ".lib_dir = \""
|
||||||
@@ -241,9 +477,27 @@ run_translate_c :: proc(args: []string) -> int {
|
|||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
if len(os2.args) >= 2 && is_build_command(os2.args[1]) {
|
if len(os2.args) >= 2 && is_build_command(os2.args[1]) {
|
||||||
root := os2.args[2] if len(os2.args) >= 3 else "."
|
if len(os2.args) > 3 {
|
||||||
|
print_usage()
|
||||||
|
os2.exit(2)
|
||||||
|
}
|
||||||
|
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_new_command(os2.args[1]) {
|
||||||
|
if len(os2.args) != 3 {
|
||||||
|
print_usage()
|
||||||
|
os2.exit(2)
|
||||||
|
}
|
||||||
|
os2.exit(run_new_project(os2.args[2]))
|
||||||
|
}
|
||||||
|
if len(os2.args) >= 2 && is_init_command(os2.args[1]) {
|
||||||
|
if len(os2.args) != 2 {
|
||||||
|
print_usage()
|
||||||
|
os2.exit(2)
|
||||||
|
}
|
||||||
|
os2.exit(run_init_project())
|
||||||
|
}
|
||||||
if len(os2.args) >= 2 && is_translate_c_command(os2.args[1]) {
|
if len(os2.args) >= 2 && is_translate_c_command(os2.args[1]) {
|
||||||
os2.exit(run_translate_c(os2.args))
|
os2.exit(run_translate_c(os2.args))
|
||||||
}
|
}
|
||||||
@@ -255,7 +509,14 @@ main :: proc() {
|
|||||||
defer delete(options.link_arguments)
|
defer delete(options.link_arguments)
|
||||||
defer delete(options.c_options.include_paths)
|
defer delete(options.c_options.include_paths)
|
||||||
defer delete(options.c_options.defines)
|
defer delete(options.c_options.defines)
|
||||||
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target, options.c_options)
|
status := compiler.compile_package(
|
||||||
|
options.input_path,
|
||||||
|
options.output_path,
|
||||||
|
options.link_arguments,
|
||||||
|
options.target,
|
||||||
|
options.c_options,
|
||||||
|
options.project_root,
|
||||||
|
)
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
os2.exit(status)
|
os2.exit(status)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,12 +2,12 @@
|
|||||||
#
|
#
|
||||||
# A project's `build.bro` imports this module and declares a top-level constant
|
# A project's `build.bro` imports this module and declares a top-level constant
|
||||||
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
|
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
|
||||||
# build.bro, reads the config, and compiles the program package it names.
|
# build.bro, 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"]`); empty lists are written `&[]`.
|
# address-of an array literal (`&["raylib"]`); empty lists are written `&[]`.
|
||||||
BuildConfig :: struct {
|
BuildConfig :: struct {
|
||||||
name []u8 # output executable name
|
name []u8 # output executable name under root/build
|
||||||
source []u8 # program package directory, relative to build.bro
|
source []u8 # program package directory, relative to build.bro
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user