From 6455df52b485cf3a3bd0859d0c9c15fc5761c7df Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 13 Jul 2026 13:46:45 +0200 Subject: [PATCH] add ArrayList alias to std --- LANGUAGE.md | 3 + TODO.md | 9 +- compiler/ast/ast.odin | 25 +++ compiler/checker/checker.odin | 26 ++- compiler/loader/loader.odin | 230 ++++++++++++++++++++++ compiler/parser/parser.odin | 25 +++ compiler_tests.odin | 143 ++++++++++++++ examples/programs/arraylist/main.bro | 3 +- std/std.bro | 3 + testbed/lexer/main.bro | 13 +- testbed/lexer/std/arraylist/arraylist.bro | 2 +- testbed/lexer/std/io/io.bro | 122 ++++++++++++ testbed/lexer/std/mem/mem.bro | 6 +- testbed/lexer/std/std.bro | 3 + 14 files changed, 600 insertions(+), 13 deletions(-) create mode 100644 std/std.bro create mode 100644 testbed/lexer/std/io/io.bro create mode 100644 testbed/lexer/std/std.bro diff --git a/LANGUAGE.md b/LANGUAGE.md index f8ee7ea..1e3ac75 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -13,6 +13,8 @@ roadmap and milestone history. - package-level functions, globals, native type declarations, and `Name :: alias T` - directory packages with merged declarations - file-local relative imports, import aliases, and qualified member access +- transparent declaration aliases with `Name :: alias package.Member`; functions/type factories, + named types, and globals retain their original declaration or storage identity - native top-level declarations beginning with `_` are visible only within their source file; locals, fields, parameters, and C declarations are unaffected - relative `.h` imports as synthetic C header package namespaces - root `main` validation with trap executable recovery for missing or unusable entry points @@ -121,6 +123,7 @@ The six spellings are reserved only as direct unqualified calls. A qualified cal ### standard packages +- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist` - `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation - `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit - `std/io` explicit `Io` capabilities, `Reader`/`Writer` stream values, one-shot `read`/`write`, and allocation-free `write_all` diff --git a/TODO.md b/TODO.md index 641f126..bdc5ca8 100644 --- a/TODO.md +++ b/TODO.md @@ -814,7 +814,14 @@ - `read` and `write` validate provider counts; `write_all` handles partial writes and no progress - the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing -34. re-exports so `std` can re-export e.g. `ArrayList(T)`, so users can do `std.ArrayList(i32)` instead of `std.arraylist.ArrayList(i32)`, while still using `std.arraylist.append(&values, 420)`. thinking this would be nice ergonomically. +34. package declaration aliases and root `std.ArrayList` (implemented) + - bare qualified aliases use `Name :: alias package.Member` without adding a keyword + - functions/type factories, named types, and globals transparently retain the target identity; + mutable global aliases therefore share the original storage + - aliases resolve transitively at load time, consume their file-local import, preserve leading- + underscore visibility, and diagnose missing, hidden, unavailable, ambiguous, cyclic, or + conflicting targets + - root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist` ## A word on unchecked casts diff --git a/compiler/ast/ast.odin b/compiler/ast/ast.odin index 180fcf3..5e32eee 100644 --- a/compiler/ast/ast.odin +++ b/compiler/ast/ast.odin @@ -251,6 +251,28 @@ Import :: struct { diagnostic: source.Diagnostic_Id, } +Declaration_Alias_Kind :: enum u8 { + Invalid, + Function, + Global, + Type, +} + +Declaration_Alias :: struct { + span: source.Span, + name: symbol.Id, + qualifier: symbol.Id, + member: symbol.Id, + pkg: Package_Id, + file: File_Id, + target_pkg: Package_Id, + target: u32, + kind: Declaration_Alias_Kind, + file_hidden: bool, + valid: bool, + diagnostic: source.Diagnostic_Id, +} + File :: struct { source: source.Source_Id, pkg: Package_Id, @@ -290,6 +312,7 @@ Module :: struct { functions: [dynamic]Function, globals: [dynamic]Global, imports: [dynamic]Import, + aliases: [dynamic]Declaration_Alias, files: [dynamic]File, packages: [dynamic]Package, unsupported: [dynamic]Unsupported, @@ -309,6 +332,7 @@ init_module :: proc(allocator := context.allocator) -> Module { module.functions.allocator = allocator module.globals.allocator = allocator module.imports.allocator = allocator + module.aliases.allocator = allocator module.files.allocator = allocator module.packages.allocator = allocator module.unsupported.allocator = allocator @@ -360,6 +384,7 @@ destroy_module :: proc(module: ^Module) { delete(module.functions) delete(module.globals) delete(module.imports) + delete(module.aliases) delete(module.files) delete(module.packages) delete(module.unsupported) diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 6f69e25..03f3ede 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -868,6 +868,11 @@ build_symbol_indexes :: proc(checker: ^Checker) { function_count += 1 } } + for alias in checker.ast_module.aliases { + if alias.valid && alias.kind == .Function { + function_count += 1 + } + } checker.function_index = make([]Function_Index_Entry, function_count, checker.allocator) function_index := 0 for function, id in checker.ast_module.functions { @@ -877,12 +882,31 @@ build_symbol_indexes :: proc(checker: ^Checker) { checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, file=function.file, hidden=function.file_hidden, name=function.name, id=ast.function_id(id)} function_index += 1 } + for alias in checker.ast_module.aliases { + if alias.valid && alias.kind == .Function { + checker.function_index[function_index] = Function_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Function_Id(alias.target)} + function_index += 1 + } + } slice.sort_by(checker.function_index, function_index_less) - checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator) + global_count := len(checker.ast_module.globals) + for alias in checker.ast_module.aliases { + if alias.valid && alias.kind == .Global { + global_count += 1 + } + } + checker.global_index = make([]Global_Index_Entry, global_count, checker.allocator) for global, id in checker.ast_module.globals { checker.global_index[id] = Global_Index_Entry{scope=global.pkg, file=global.file, hidden=global.file_hidden, name=global.name, id=ast.global_id(id)} } + global_index := len(checker.ast_module.globals) + for alias in checker.ast_module.aliases { + if alias.valid && alias.kind == .Global { + checker.global_index[global_index] = Global_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Global_Id(alias.target)} + global_index += 1 + } + } slice.sort_by(checker.global_index, global_index_less) checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator) diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin index 4374233..51b5331 100644 --- a/compiler/loader/loader.odin +++ b/compiler/loader/loader.odin @@ -1293,6 +1293,235 @@ find_type_import :: proc(module: ^ast.Module, file: ast.File_Id, alias: symbol.I return ast.INVALID_IMPORT } +alias_declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool { + return left_file == right_file if left_hidden && right_hidden else true +} + +alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool { + for function in module.functions { + if function.pkg == alias.pkg && function.name == alias.name && + alias_declarations_conflict(alias.file, alias.file_hidden, function.file, function.file_hidden) { + return true + } + } + for global in module.globals { + if global.pkg == alias.pkg && global.name == alias.name && + alias_declarations_conflict(alias.file, alias.file_hidden, global.file, global.file_hidden) { + return true + } + } + for item in module.type_store.nodes { + if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) && + alias_declarations_conflict(alias.file, alias.file_hidden, ast.File_Id(item.file), item.file_hidden) { + return true + } + } + return false +} + +direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Declaration_Alias_Kind, u32, int) { + kind := ast.Declaration_Alias_Kind.Invalid + target: u32 + kinds := 0 + for function, index in module.functions { + if function.pkg == pkg && function.name == name && !function.generated && !function.file_hidden { + kind = .Function + target = u32(ast.function_id(index)) + kinds += 1 + break + } + } + for global, index in module.globals { + if global.pkg == pkg && global.name == name && !global.file_hidden { + kind = .Global + target = u32(ast.global_id(index)) + kinds += 1 + break + } + } + if value := types.find_named(&module.type_store, u32(pkg), u32(name)); types.is_valid(value) { + if item, ok := types.node(&module.type_store, value); ok && item.declared { + kind = .Type + target = u32(value) + kinds += 1 + } + } + return kind, target, kinds +} + +hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool { + for function in module.functions { + if function.pkg == pkg && function.name == name && function.file_hidden { + return true + } + } + for global in module.globals { + if global.pkg == pkg && global.name == name && global.file_hidden { + return true + } + } + for item in module.type_store.nodes { + if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.file_hidden { + return true + } + } + for alias in module.aliases { + if alias.valid && alias.pkg == pkg && alias.name == name && alias.file_hidden { + return true + } + } + return false +} + +find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int { + for alias, index in module.aliases { + if alias.valid && alias.pkg == pkg && alias.name == name && !alias.file_hidden { + return index + } + } + return -1 +} + +resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bool { + alias := &state.module.aliases[index] + if !alias.valid { + return false + } + if states[index] == 2 { + return alias.kind != .Invalid + } + if states[index] == 1 { + alias.diagnostic = source.addf( + state.diagnostics, + alias.span, + "declaration alias cycle involving '%s'", + symbol.resolve(state.symbols, alias.name), + ) + alias.valid = false + return false + } + states[index] = 1 + defer states[index] = 2 + + kind, target, kinds := direct_alias_target(state.module, alias.target_pkg, alias.member) + if kinds > 1 { + alias.diagnostic = source.addf( + state.diagnostics, + alias.span, + "package member '%s.%s' is ambiguous", + symbol.resolve(state.symbols, alias.qualifier), + symbol.resolve(state.symbols, alias.member), + ) + alias.valid = false + return false + } + if kinds == 1 { + alias.kind = kind + alias.target = target + return true + } + + if target_alias := find_public_alias(state.module, alias.target_pkg, alias.member); target_alias >= 0 { + if resolve_declaration_alias(state, target_alias, states) { + resolved := state.module.aliases[target_alias] + alias.kind = resolved.kind + alias.target = resolved.target + return true + } + alias.valid = false + return false + } + + if hidden_alias_target_exists(state.module, alias.target_pkg, alias.member) { + alias.diagnostic = source.addf( + state.diagnostics, + alias.span, + "package member '%s.%s' is file-hidden", + symbol.resolve(state.symbols, alias.qualifier), + symbol.resolve(state.symbols, alias.member), + ) + } else { + alias.diagnostic = source.addf( + state.diagnostics, + alias.span, + "package '%s' has no member '%s'", + symbol.resolve(state.symbols, alias.qualifier), + symbol.resolve(state.symbols, alias.member), + ) + } + alias.valid = false + return false +} + +validate_declaration_aliases :: proc(state: ^State) { + for &alias, index in state.module.aliases { + name := symbol.resolve(state.symbols, alias.name) + if alias_conflicts_with_declaration(state.module, alias) { + alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with a package declaration", name) + alias.valid = false + continue + } + for previous in state.module.aliases[:index] { + if previous.pkg == alias.pkg && previous.name == alias.name && + alias_declarations_conflict(alias.file, alias.file_hidden, previous.file, previous.file_hidden) { + alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name) + alias.valid = false + break + } + } + if !alias.valid { + continue + } + for import_item in state.module.imports { + if import_item.file == alias.file && import_item.alias == alias.name { + alias.diagnostic = source.addf(state.diagnostics, alias.span, "declaration alias '%s' conflicts with an import", name) + alias.valid = false + break + } + } + if !alias.valid { + continue + } + + import_id := find_type_import(state.module, alias.file, alias.qualifier) + if import_id == ast.INVALID_IMPORT { + alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown package alias '%s'", symbol.resolve(state.symbols, alias.qualifier)) + alias.valid = false + continue + } + state.module.imports[import_id].used = true + import_item := state.module.imports[import_id] + alias.target_pkg = import_item.target + if !import_item.valid || import_item.target == ast.INVALID_PACKAGE || + int(import_item.target) >= len(state.module.packages) || !state.module.packages[import_item.target].available { + alias.diagnostic = source.addf(state.diagnostics, alias.span, "unavailable imported package '%s'", symbol.resolve(state.symbols, alias.qualifier)) + alias.valid = false + } + } + + states := make([]u8, len(state.module.aliases), state.allocator) + defer delete(states, state.allocator) + for _, index in state.module.aliases { + _ = resolve_declaration_alias(state, index, states) + } + for &alias in state.module.aliases { + if !alias.valid || alias.kind != .Type { + continue + } + id := types.named( + &state.module.type_store, + u32(alias.pkg), + u32(alias.name), + file=u32(alias.file), + file_hidden=alias.file_hidden, + ) + if !types.define_alias(&state.module.type_store, id, types.Type(alias.target)) { + alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name)) + alias.valid = false + } + } +} + canonical_type :: proc( module: ^ast.Module, value: types.Type, @@ -1456,6 +1685,7 @@ load :: proc( state.root_failed = true } validate_imports(&state) + validate_declaration_aliases(&state) canonicalize_types(&module, allocator) return module, !state.root_failed } diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 2c01f64..de2304d 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -2425,6 +2425,31 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token) { parse_alias :: proc(parser: ^Parser, name: token.Token) { start := advance(parser) + saved := parser.cursor + if current(parser).kind == .Identifier && peek(parser).kind == .Dot { + qualifier := advance(parser) + advance(parser) + if current(parser).kind == .Identifier { + member := advance(parser) + if current(parser).kind == .Newline || current(parser).kind == .Eof { + append(&parser.module.aliases, ast.Declaration_Alias{ + span=span_from(name.span, member.span), + name=name.symbol, + qualifier=qualifier.symbol, + member=member.symbol, + pkg=parser.pkg, + file=parser.file, + target_pkg=ast.INVALID_PACKAGE, + file_hidden=file_hidden_name(parser, name), + valid=true, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + _ = finish_statement(parser) + return + } + } + } + parser.cursor = saved child := parse_type(parser) id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name)) if !types.define_alias(&parser.module.type_store, id, child) { diff --git a/compiler_tests.odin b/compiler_tests.odin index d1ea338..72eca32 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -7125,6 +7125,149 @@ imports_do_not_reexport_members :: proc(t: ^testing.T) { testing.expect(t, !state.success) } +@(test) +declaration_aliases_preserve_identity_and_chain :: proc(t: ^testing.T) { + root :: "/tmp/brolang-test-declaration-aliases" + dep_dir :: root + "/dep" + facade_dir :: root + "/facade" + top_dir :: root + "/top" + app_dir :: root + "/app" + output :: "/tmp/brolang-test-declaration-aliases-output" + _ = os2.remove_all(root) + defer _ = os2.remove_all(root) + defer _ = os.remove(output) + directories := [?]string{root, dep_dir, facade_dir, top_dir, app_dir} + for directory in directories { + testing.expect(t, os.make_directory(directory) == nil) + } + dep_text := `Box func($T type) type { + return struct { value T } +} +Point :: struct { value i32 } +counter i32 = 1 +answer func() i32 { return 40 } +` + facade_text := `dep :: import "../dep" +RenamedBox :: alias dep.Box +RenamedPoint :: alias dep.Point +counter :: alias dep.counter +answer :: alias dep.answer +_local_answer :: alias dep.answer +local_answer func() i32 { return _local_answer() } +Scalar :: alias i32 +MaybePoint :: alias ?@dep.Point +Concrete :: alias dep.Box(i32) +` + top_text := `facade :: import "../facade" +Box :: alias facade.RenamedBox +Point :: alias facade.RenamedPoint +counter :: alias facade.counter +answer :: alias facade.answer +` + app_text := `dep :: import "../dep" +facade :: import "../facade" +top :: import "../top" + +main func() i32 { + box top.Box(i32) :: top.Box(i32) { value = 2 } + point top.Point :: top.Point { value = 3 } + maybe facade.MaybePoint :: none + scalar facade.Scalar :: 5 + top.counter = 7 + if box.value != 2 or point.value != 3 { return 1 } + if scalar != 5 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 } + _ = maybe + return 0 +} +` + testing.expect(t, os.write_entire_file(dep_dir + "/dep.bro", transmute([]byte)dep_text)) + testing.expect(t, os.write_entire_file(facade_dir + "/facade.bro", transmute([]byte)facade_text)) + testing.expect(t, os.write_entire_file(top_dir + "/top.bro", transmute([]byte)top_text)) + testing.expect(t, os.write_entire_file(app_dir + "/main.bro", transmute([]byte)app_text)) + + status := compiler_core.compile_package(app_dir, output) + testing.expect_value(t, status, 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 0) +} + +@(test) +declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) { + root :: "/tmp/brolang-test-declaration-alias-errors" + dep_dir :: root + "/dep" + facade_dir :: root + "/facade" + a_dir :: root + "/a" + b_dir :: root + "/b" + app_dir :: root + "/app" + _ = os2.remove_all(root) + defer _ = os2.remove_all(root) + directories := [?]string{root, dep_dir, facade_dir, a_dir, b_dir, app_dir} + for directory in directories { + testing.expect(t, os.make_directory(directory) == nil) + } + dep_text := `visible func() i32 { return 1 } +_hidden func() i32 { return 2 } +ambiguous func() i32 { return 3 } +ambiguous i32 :: 4 +` + facade_text := `dep :: import "../dep" +gone :: import "../gone" +missing :: alias dep.missing +hidden :: alias dep._hidden +unknown :: alias nope.visible +unavailable :: alias gone.visible +ambiguous :: alias dep.ambiguous +duplicate :: alias dep.visible +duplicate :: alias dep.visible +collision func() i32 { return 0 } +collision :: alias dep.visible +dep :: alias dep.visible +` + a_text := `b :: import "../b" +value :: alias b.value +` + b_text := `a :: import "../a" +value :: alias a.value +` + app_text := `facade :: import "../facade" +a :: import "../a" +main func() void {} +` + testing.expect(t, os.write_entire_file(dep_dir + "/dep.bro", transmute([]byte)dep_text)) + testing.expect(t, os.write_entire_file(facade_dir + "/facade.bro", transmute([]byte)facade_text)) + testing.expect(t, os.write_entire_file(a_dir + "/a.bro", transmute([]byte)a_text)) + testing.expect(t, os.write_entire_file(b_dir + "/b.bro", transmute([]byte)b_text)) + testing.expect(t, os.write_entire_file(app_dir + "/main.bro", transmute([]byte)app_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(app_dir, &sources, &diagnostics, &symbols) + defer ast.destroy_module(&module) + testing.expect(t, loaded) + wants := []string{ + "has no member 'missing'", + "is file-hidden", + "unknown package alias 'nope'", + "unavailable imported package 'gone'", + "package member 'dep.ambiguous' is ambiguous", + "duplicate declaration alias 'duplicate'", + "declaration alias 'collision' conflicts with a package declaration", + "declaration alias 'dep' conflicts with an import", + "declaration alias cycle", + } + for want in wants { + found := false + for diagnostic in diagnostics.items { + found = found || strings.contains(diagnostic.message, want) + } + testing.expect(t, found) + } +} + @(test) self_import_via_dot_is_valid :: proc(t: ^testing.T) { output := "/tmp/brolang-test-package-self" diff --git a/examples/programs/arraylist/main.bro b/examples/programs/arraylist/main.bro index 38887b7..a7df56d 100644 --- a/examples/programs/arraylist/main.bro +++ b/examples/programs/arraylist/main.bro @@ -1,5 +1,6 @@ arraylist :: import "@std/arraylist" mem :: import "@std/mem" +std :: import "@std" _fail_alloc func(_ ?*mut anyopaque, _ usize, _ usize) ?*mut u8 { return none @@ -25,7 +26,7 @@ _fail_allocator mem.Allocator :: mem.Allocator { _noop func() void {} run func() i32 ! mem.AllocError { - values arraylist.ArrayList(i32) = arraylist.init(mem.c_allocator) + values std.ArrayList(i32) = arraylist.init(mem.c_allocator) defer arraylist.deinit(&values) if (values.items.len != 0 or values.capacity != 0) return 1 diff --git a/std/std.bro b/std/std.bro new file mode 100644 index 0000000..2e49663 --- /dev/null +++ b/std/std.bro @@ -0,0 +1,3 @@ +import "arraylist" + +ArrayList :: alias arraylist.ArrayList diff --git a/testbed/lexer/main.bro b/testbed/lexer/main.bro index ff06dfc..c51996d 100644 --- a/testbed/lexer/main.bro +++ b/testbed/lexer/main.bro @@ -1,6 +1,7 @@ -arraylist :: import "@std/arraylist" -mem :: import "@std/mem" -c :: import "@ffi/c" +import "@ffi/c" +import "@std" +import "@std/mem" +import "@std/arraylist" Kind :: enum(u8) { invalid @@ -37,7 +38,7 @@ _word_kind func(word []u8) Kind { return .identifier } -_append func(tokens @mut arraylist.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError { +_append func(tokens @mut std.ArrayList(Token), kind Kind, start, end usize) void ! mem.AllocError { try arraylist.append(tokens, Token { start = start, length = end - start, @@ -46,7 +47,7 @@ _append func(tokens @mut arraylist.ArrayList(Token), kind Kind, start, end usize return _ } -lex func(source []u8, tokens @mut arraylist.ArrayList(Token)) void ! mem.AllocError { +lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError { cursor usize = 0 while cursor < source.len { value u8 :: source[cursor] @@ -131,7 +132,7 @@ main func() i32 { ` hello() `} - tokens arraylist.ArrayList(Token) = arraylist.init(mem.c_allocator) + tokens std.ArrayList(Token) = arraylist.init(mem.c_allocator) defer arraylist.deinit(&tokens) lex(source, &tokens) catch |_| { diff --git a/testbed/lexer/std/arraylist/arraylist.bro b/testbed/lexer/std/arraylist/arraylist.bro index 9ba3513..95cc061 100644 --- a/testbed/lexer/std/arraylist/arraylist.bro +++ b/testbed/lexer/std/arraylist/arraylist.bro @@ -30,7 +30,7 @@ reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem new_capacity usize = 8 if list.capacity >= 8 { - half usize :: list.capacity / 2 + half usize :: div_trunc(list.capacity, 2) if list.capacity > max_value(usize) - half { new_capacity = minimum_capacity } else { diff --git a/testbed/lexer/std/io/io.bro b/testbed/lexer/std/io/io.bro new file mode 100644 index 0000000..bf51ec9 --- /dev/null +++ b/testbed/lexer/std/io/io.bro @@ -0,0 +1,122 @@ +c :: import "@ffi/c" + +ReadError :: enum { + read_failed +} + +WriteError :: enum { + write_failed + no_progress +} + +Io :: struct { + context ?*mut anyopaque + vtable @IoVTable +} + +IoVTable :: struct { + read @func(context ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError + write @func(context ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError +} + +ReadStream :: enum(c_int) { + stdin = 0 +} + +WriteStream :: enum(c_int) { + stdout = 1 + stderr = 2 +} + +Reader :: struct { + impl Io + stream ReadStream +} + +Writer :: struct { + impl Io + stream WriteStream +} + +read func(reader Reader, buffer []mut u8) usize ! ReadError { + if buffer.len == 0 { + return 0 + } + count usize :: try reader.impl.vtable.read(reader.impl.context, reader.stream, buffer) + if count > buffer.len { + return .read_failed + } + return count +} + +write func(writer Writer, bytes []u8) usize ! WriteError { + if bytes.len == 0 { + return 0 + } + count usize :: try writer.impl.vtable.write(writer.impl.context, writer.stream, bytes) + if count > bytes.len { + return .write_failed + } + return count +} + +write_all func(writer Writer, bytes []u8) void ! WriteError { + offset usize = 0 + while offset < bytes.len { + count usize :: write(writer, bytes[offset..]) catch |err| { + return err + } + if count == 0 { + return .no_progress + } + offset += count + } + return _ +} + +_system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError { + request usize = buffer.len + maximum usize :: usize(max_value(c_long)) + if request > maximum { + request = maximum + } + while true { + count c_long :: c.read(c_int(stream), buffer.ptr, c_ulong(request)) + if count >= 0 { + return usize(count) + } + if c.__error()^ != 4 { + return .read_failed + } + } +} + +_system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError { + fd c_int :: c_int(stream) + request usize = bytes.len + maximum usize :: usize(max_value(c_long)) + if request > maximum { + request = maximum + } + while true { + count c_long :: c.write(fd, bytes.ptr, c_ulong(request)) + if count >= 0 { + return usize(count) + } + if c.__error()^ != 4 { + return .write_failed + } + } +} + +_system_vtable IoVTable :: IoVTable { + read = _system_read, + write = _system_write, +} + +_system func() Io { + return Io { + context = none, + vtable = &_system_vtable, + } +} diff --git a/testbed/lexer/std/mem/mem.bro b/testbed/lexer/std/mem/mem.bro index 2f3ad18..49450ed 100644 --- a/testbed/lexer/std/mem/mem.bro +++ b/testbed/lexer/std/mem/mem.bro @@ -61,7 +61,7 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError { if element_size == 0 { return _empty_slice(T, count) } - if count > max_value(usize) / element_size { + if count > div_trunc(max_value(usize), element_size) { return .out_of_memory } @@ -86,7 +86,7 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu if element_size == 0 { return _empty_slice(T, new_count) } - if new_count > max_value(usize) / element_size { + if new_count > div_trunc(max_value(usize), element_size) { return .out_of_memory } @@ -125,7 +125,7 @@ _power_of_two func(value usize) bool { current usize = value while current > 1 { - half usize = current / 2 + half usize = div_trunc(current, 2) if half * 2 != current { return false } diff --git a/testbed/lexer/std/std.bro b/testbed/lexer/std/std.bro new file mode 100644 index 0000000..2e49663 --- /dev/null +++ b/testbed/lexer/std/std.bro @@ -0,0 +1,3 @@ +import "arraylist" + +ArrayList :: alias arraylist.ArrayList