Compare commits

...

7 Commits

Author SHA1 Message Date
hl-valdemar 9e75549d02 tree-sitter description 2026-07-13 18:04:07 +02:00
hl-valdemar 5a958d9bfd allow keywords as values in enums (and tagged unions) 2026-07-13 18:04:07 +02:00
hl-valdemar de56dc7315 makefile 2026-07-13 18:04:07 +02:00
hl-valdemar 6455df52b4 add ArrayList alias to std 2026-07-13 18:04:07 +02:00
hl-valdemar 288df082e2 io interface (first pass) 2026-07-13 18:04:07 +02:00
hl-valdemar 2ed333c70d enforce integer division via explicit builtins 2026-07-13 18:04:07 +02:00
hl-valdemar a4d0fb1e26 tiny lexer test (testbed) 2026-07-13 18:04:07 +02:00
49 changed files with 153558 additions and 142 deletions
+1
View File
@@ -1,2 +1,3 @@
/build/
/grammars/
.DS_Store
+80 -3
View File
@@ -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
@@ -31,16 +33,44 @@ roadmap and milestone history.
- UTF-8 string literals as immutable pointers to static zero-terminated byte arrays, plus raw backtick multiline strings
- narrow immutable zero-terminated byte pointer/slice conversion to `*c_char` / `?*c_char` without general `u8`/`c_char` interchange
- optionals with `none`, `orelse`, postfix `?`, conditional unwraps, guarded unwraps, and left-to-right short-circuiting multi-unwraps
- nominal distinct types with exact backing construction, native enums with optional explicit integer backing, contextual enum literals, and imported C enums as target-backed integer aliases
- nominal distinct types with exact backing construction, native enums with optional explicit integer backing and explicit backing-to-scalar casts, contextual enum literals, and imported C enums as target-backed integer aliases
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
#### keyword member names
Reserved keywords are valid native enum members and tagged-union variants when used in an
unambiguous member context:
```bro
TokenKind :: enum {
if
else
return
}
Token :: union(TokenKind) {
if i32
else void
return i32
}
conditional func() TokenKind { return TokenKind.if }
fallback func() TokenKind { return .else }
token func() Token { return Token{ if = 1 } }
```
Keyword variants also work with field access and `.variant` match patterns; `.else:` remains
distinct from the `else:` catch-all arm. No escaping syntax is required. Keywords remain reserved
for ordinary declarations, struct fields, untagged-union fields, and anonymous payload-struct
fields. `_` is not a keyword member name.
### expressions and control flow
- checked integer `+ - * /`, unary `-`, divide-by-zero traps, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
- assignments and compound assignments `+= -= *= /=` with single evaluation of complex lvalues
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
- assignments and compound assignments `+= -= *= /=` with single evaluation of complex lvalues; `/=` is float-only
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
- boolean `if` / `else if` / `else`, braceless single-statement branches, and optional parenthesized conditions
- `while` loops with optional post-iteration update clauses
@@ -52,6 +82,50 @@ roadmap and milestone history.
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
- direct `return match ...` and `yield match ...` value-control-flow operands
#### division
`/` and `/=` accept only floating-point operands. Integer division must state its rounding and
remainder convention with one of these unqualified builtins:
| Builtin | Result |
| --- | --- |
| `div_trunc(a, b)` | quotient rounded toward zero |
| `div_floor(a, b)` | quotient rounded toward negative infinity |
| `div_exact(a, b)` | truncated quotient; traps unless it divides exactly |
| `div_ceil(a, b)` | quotient rounded toward positive infinity |
| `rem(a, b)` | remainder paired with `div_trunc`; sign follows `a` |
| `mod(a, b)` | modulus paired with `div_floor`; sign follows `b` |
The operands may be compatible concrete integer or float scalars. Existing literal coercion and
numeric widening rules apply, the result has the common operand type, and float quotients are
integral-valued floats. These identities hold when representable:
```bro
div_trunc(a, b) * b + rem(a, b) == a
div_floor(a, b) * b + mod(a, b) == a
```
Negative operands distinguish the operations:
```bro
div_trunc(-5, 3) == -1
div_floor(-5, 3) == -2
div_ceil(-5, 3) == -1
rem(-5, 3) == -2
mod(-5, 3) == 1
mod(5, -3) == -1
```
All six builtins diagnose a zero denominator at comptime and trap at runtime, including float
zero. Quotient operations also trap for signed `min_value(T), -1`; `rem` and `mod` return zero for
that pair. `div_exact` traps when `div_trunc(a, b) * b == a` is false in the operand type, so float
exactness follows floating-point equality. Other float NaN and infinity behavior follows the
underlying IEEE operations. Ordinary float `/` remains unchecked and therefore preserves IEEE
infinity/NaN behavior.
The six spellings are reserved only as direct unqualified calls. A qualified call such as
`math.div_floor(a, b)` resolves to an ordinary package function.
### functions, C interop, and linking
- demand-monomorphized Brolang and C-ABI functions
@@ -77,8 +151,10 @@ roadmap and milestone history.
### 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`
### compiler behavior
@@ -86,6 +162,7 @@ roadmap and milestone history.
- lazy semantic checking of demanded function specializations
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
- demand-driven LLVM declarations for referenced foreign functions
- root `main` may be parameterless or accept the canonical `@std/io Io`; the injected form is called through a synthesized no-argument C entry point
- replaceable dynamically loaded libclang C-import backend
- C-header import caching by canonical path, target, include paths, and defines
+9
View File
@@ -0,0 +1,9 @@
.PHONY: build install
build:
mkdir -p build
odin build . -out:build/brolang
install: build
install -d "$(HOME)/.brolang/bin"
install -m 755 build/brolang "$(HOME)/.brolang/bin/bro"
+18 -1
View File
@@ -9,6 +9,22 @@ odin build . -out:build/brolang
./build/prototype
```
Programs may receive the system I/O capability explicitly. Readers and writers
pair that implementation with a stream; `main func() ...` remains valid.
```bro
io :: import "@std/io"
main func(system io.Io) void {
io.write_all(io.Writer {
impl = system,
stream = .stdout,
}, "hello\n") catch |_| {
return
}
}
```
Bodyless `c_func` declarations bind exact external symbols and require concrete
types. C primitives use atomic target-dependent names and remain semantically
distinct from exact-width Brolang primitives:
@@ -149,7 +165,7 @@ backend failures return status `2`.
Top-level function bodies are semantically checked lazily when a concrete
specialization is demanded.
Every immediate `.bro` file in the input directory belongs to the root
Every immediate `.bro` or `.hon` file in the input directory belongs to the root
package. Imports are relative directory paths and are local to the file that
declares them. Imports beginning with `@` resolve from the project root:
@@ -187,6 +203,7 @@ Current prototype features:
- Bodyless manual and imported C variadic declarations with default argument promotions
- Ordered linking of additional C sources, objects, archives, and libraries
- Checked signed addition and unary negation
- Float-only `/` plus explicit `div_trunc`, `div_floor`, `div_exact`, `div_ceil`, `rem`, and `mod` scalar builtins
- Static, eager runtime, mutable runtime, and deferred problematic globals
- Runtime diagnostics followed by `llvm.trap`
+36 -9
View File
@@ -116,7 +116,7 @@
- `for 0..(len) |i| { ... }` or equivalently `for 0..=(len - 1) |i| { ... }` - calculating range bounds, expressions must be parenthesized
- for all conditionals/guards, parentheses are optional but allowed for visual clarity
6. compound assignment: `+=`, `-=`, `*=`, `/=` (implemented)
6. compound assignment: `+=`, `-=`, `*=`, `/=` (implemented; division semantics superseded by milestone 32)
- added the binary arithmetic operators `-`, `*`, `/` (previously only `+` existed); `*`/`/`
bind tighter than `+`/`-`, and prefix `-` (negation) is unchanged
- compound assignments preserve the target, operator, and right-hand side explicitly through
@@ -124,10 +124,9 @@
operation, and stores through that address
- side-effecting index, field-base, and dereference expressions are evaluated once in
left-to-right order
- integer arithmetic traps on overflow (`Sub_Checked`/`Mul_Checked` via the LLVM
`.with.overflow` intrinsics) and integer `/` traps on divide-by-zero and `INT_MIN / -1`;
floats follow IEEE (`fadd`/`fsub`/`fmul`/`fdiv`, no trap)
- constant folding (global initializers) covers `-`, `*`, `/` alongside `+`
- integer `+`, `-`, and `*` trap on overflow; milestone 32 later restricted `/` and `/=` to
floats and introduced the explicit integer/float division family
- constant folding (global initializers) covers the arithmetic family
7. enums (native and c interop) (implemented; see below)
- native enums are nominal value types with integer runtime representations
@@ -791,10 +790,38 @@
- the existing specialization/HIR/LLVM ABI is unchanged; `std/mem` and `std/arraylist` now use the
inferred form where their arguments or result provide enough information
32. disallow arbitrary integer division
- take inspiration from zig
- see also below for a word on unchecked casts
- the user should be explicit about what they mean with integer division (e.g. `div`, `rem`)
32. explicit division family (implemented)
- `/` and `/=` are float-only; every integer use is rejected with guidance toward explicit
division, including literals, comptime execution, array counts, and compound assignment
- direct unqualified calls reserve `div_trunc`, `div_floor`, `div_exact`, `div_ceil`, `rem`, and
`mod`; qualified names remain ordinary package functions
- the builtins accept compatible concrete integer or float scalars, reuse existing literal and
widening rules, and return the common operand type (integral-valued floats for quotients)
- all builtins diagnose zero denominators at comptime and trap at runtime; quotient operations
also trap on signed `min_value(T) / -1`, while `rem` and `mod` return zero for that pair
- `div_exact` checks the reconstructed dividend in the operand type; `rem` pairs with truncation
and follows the numerator sign, while `mod` pairs with floor and follows the denominator sign
- HIR/IR use compact semantic enum tags; integer floor, ceil, and exact lowering reconstructs the
remainder from one quotient so each produces only one hardware-division candidate
- float lowering uses the typed LLVM trunc/floor/ceil intrinsics, `frem`, and ordered equality;
ordinary float `/` remains the unchecked IEEE infinity/NaN escape hatch
- migrated `std/mem`, `std/arraylist`, and the compound-assignment example to `div_trunc`
33. explicit I/O provider (implemented)
- `main` may take one canonical `@std/io Io`; parameterless entry points remain valid
- the compiler supplies a file-hidden macOS provider through an external no-argument C wrapper
- readers and writers pair an explicit provider with `stdin`, `stdout`, or `stderr`
- `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. 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
+25
View File
@@ -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)
+321 -11
View File
@@ -183,6 +183,8 @@ Checker :: struct {
// build_globals, so the 1:1 module.globals <-> ast.globals index identity holds.
anon_globals: [dynamic]hir.Global,
main_symbol: symbol.Id,
io_main: bool,
io_provider_template: ast.Function_Id,
sink_symbol: symbol.Id,
type_symbol: symbol.Id,
current_result: types.Type,
@@ -399,6 +401,31 @@ Type_Builtin :: enum u8 {
Max_Value,
}
Division_Builtin :: enum u8 {
None,
Trunc,
Floor,
Exact,
Ceil,
Rem,
Mod,
}
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None
}
switch symbol_text(checker, expr.name) {
case "div_trunc": return .Trunc
case "div_floor": return .Floor
case "div_exact": return .Exact
case "div_ceil": return .Ceil
case "rem": return .Rem
case "mod": return .Mod
}
return .None
}
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None
@@ -598,6 +625,10 @@ type_from_syntax :: proc(
changed = true
}
} else {
if constant.kind == .Integer_Division {
source.add(checker.diagnostics, span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
return types.INVALID
}
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
return types.INVALID
}
@@ -837,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 {
@@ -846,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)
@@ -865,6 +920,66 @@ find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(
return find_function_symbol(checker.function_index, pkg, name, file)
}
configure_io_main :: proc(checker: ^Checker) {
main_template := find_template(checker, checker.main_symbol, 0)
if main_template == ast.INVALID_FUNCTION {
return
}
main := checker.ast_module.functions[main_template]
if len(main.params) != 1 || main.params[0].comptime_value {
return
}
parameter_type := types.resolve_alias(
type_from_syntax(checker, main.params[0].type, main.pkg, main.file),
&checker.module.types,
)
io_name := symbol.intern(checker.symbols, "Io")
io_package := ast.INVALID_PACKAGE
io_type := types.INVALID
for import_item in checker.ast_module.imports {
if import_item.valid && import_item.path == "@std/io" {
candidate := types.find_named(&checker.module.types, u32(import_item.target), u32(io_name))
if types.equal(parameter_type, candidate) {
io_package = import_item.target
io_type = candidate
break
}
}
}
if io_package == ast.INVALID_PACKAGE {
return
}
provider_name := symbol.intern(checker.symbols, "_system")
provider := ast.INVALID_FUNCTION
for function, function_id in checker.ast_module.functions {
if function.pkg != io_package || function.name != provider_name {
continue
}
result := types.resolve_alias(
type_from_syntax(checker, function.result, function.pkg, function.file),
&checker.module.types,
)
if function.has_body && !function.c_abi && len(function.params) == 0 &&
!types.is_valid(function.error) && types.equal(result, io_type) {
provider = ast.function_id(function_id)
break
}
}
if provider == ast.INVALID_FUNCTION {
checker.template_diagnostics[main_template] = source.add(
checker.diagnostics,
main.span,
"@std/io does not provide the required '_system func() Io' startup implementation",
)
return
}
checker.io_main = true
checker.io_provider_template = provider
}
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Global_Id {
return find_global_symbol(checker.global_index, pkg, name, file)
}
@@ -2066,6 +2181,18 @@ validate_external_globals :: proc(checker: ^Checker) {
}
}
runtime_write_declaration_matches :: proc(checker: ^Checker, function: ast.Function) -> bool {
if function.variadic || len(function.params) != 3 || types.is_valid(function.error) {
return false
}
store := &checker.module.types
buffer := types.optional(store, types.pointer(store, types.ANYOPAQUE, false, true))
return type_from_syntax(checker, function.params[0].type, function.pkg, function.file) == types.C_INT &&
type_from_syntax(checker, function.params[1].type, function.pkg, function.file) == buffer &&
type_from_syntax(checker, function.params[2].type, function.pkg, function.file) == types.C_ULONG &&
type_from_syntax(checker, function.result, function.pkg, function.file) == types.C_LONG
}
validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions {
if len(function.unsupported_reason) > 0 {
@@ -2232,6 +2359,14 @@ validate_declarations :: proc(checker: ^Checker) {
"main must have a body",
)
}
external_name := function.link_name if len(function.link_name) > 0 else symbol_text(checker, function.name)
if external_name == "write" && !runtime_write_declaration_matches(checker, function) {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"external C function 'write' conflicts with the compiler runtime declaration",
)
}
}
mark_block_imports_used(checker, function.body, function.file)
delete(locals)
@@ -2568,6 +2703,35 @@ infer_nested_expr :: proc(
return result
}
infer_division_builtin :: proc(
checker: ^Checker,
expr: ast.Expr,
locals: []Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
local_types: []types.Type,
expected: types.Type,
) -> types.Type {
if len(expr.args) != 2 {
return types.INVALID
}
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.args[0])
right_const := is_numeric_constant_expr(checker, expr.args[1])
left, right := types.INVALID, types.INVALID
if left_const && !right_const && !types.is_valid(hint) {
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, right)
} else {
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, hint)
right_hint := hint if types.is_valid(hint) else left
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, right_hint)
}
result := types.widest(left, right)
return result if types.is_concrete_scalar(result) && !types.is_bool(result) else types.INVALID
}
infer_compound_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
@@ -2938,6 +3102,11 @@ infer_expr :: proc(
_ = pop(&stack)
continue
}
if division_builtin_call(checker, expr) != .None {
last = infer_division_builtin(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
_ = pop(&stack)
continue
}
if is_ptr_cast_call(checker, expr) {
if len(expr.args) != 2 {
last = types.INVALID
@@ -3952,6 +4121,12 @@ record_demand :: proc(
right := record_demand(checker, expr.right, demand, locals, local_types, pkg, file)
return left || right
}
case .Call:
if division_builtin_call(checker, expr) != .None && len(expr.args) == 2 && is_numeric_demand(demand, checker.target) {
left := record_demand(checker, expr.args[0], demand, locals, local_types, pkg, file)
right := record_demand(checker, expr.args[1], demand, locals, local_types, pkg, file)
return left || right
}
}
return false
}
@@ -4001,6 +4176,9 @@ infer_all :: proc(checker: ^Checker) {
if main_template != ast.INVALID_FUNCTION {
ensure_spec(checker, main_template, nil)
}
if checker.io_main {
ensure_spec(checker, checker.io_provider_template, nil)
}
defaults_applied := false
for {
@@ -4127,6 +4305,9 @@ prune_specs :: proc(checker: ^Checker) {
if main_template != ast.INVALID_FUNCTION {
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
}
if checker.io_main {
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
}
for global in checker.ast_module.globals {
if global.external {
continue
@@ -4435,6 +4616,14 @@ build_constant_expr :: proc(
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Non_Exact {
id := source.add(checker.diagnostics, expr.span, "exact division has a remainder")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Integer_Division {
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Overflow ||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
id := source.add(
@@ -4868,6 +5057,89 @@ build_nested_expr :: proc(
return result
}
try_build_comptime_division :: proc(
checker: ^Checker,
expr: ast.Expr,
kind: Division_Builtin,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> (hir.Expr_Id, bool) {
state := ct_state_make(checker, pkg, file, diagnose=false)
defer ct_state_destroy(&state)
value, flow, ok := ct_eval_division_call(&state, expr, kind, expected, 0)
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE {
return ct_materialize_value(&state, value, expr.span, expected), true
}
message := ""
#partial switch state.error {
case .Div_By_Zero: message = "division builtin denominator is zero"
case .Overflow: message = "signed integer division overflow"
case .Non_Exact: message = "exact division has a remainder"
}
if len(message) == 0 {
return hir.INVALID_EXPR, false
}
id := source.add(checker.diagnostics, expr.span, message)
recovery := expected if types.is_concrete_scalar(expected) else types.I64
return invalid_hir_expr(checker, expr.span, id, recovery), true
}
build_division_builtin :: proc(
checker: ^Checker,
expr: ast.Expr,
kind: Division_Builtin,
locals: []Build_Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> hir.Expr_Id {
if len(expr.args) != 2 {
id := source.addf(
checker.diagnostics, expr.span, "%s expects 2 arguments, got %d",
symbol_text(checker, expr.name), len(expr.args),
)
return invalid_hir_expr(checker, expr.span, id)
}
if value, handled := try_build_comptime_division(checker, expr, kind, expected, pkg, file); handled {
return value
}
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.args[0])
right_const := is_numeric_constant_expr(checker, expr.args[1])
left, right := hir.INVALID_EXPR, hir.INVALID_EXPR
if left_const && !right_const && !types.is_valid(hint) {
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
} else {
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, hint, pkg, file)
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file)
}
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_scalar(result) || types.is_bool(result) {
id := source.add(checker.diagnostics, expr.span, "division builtins require compatible numeric operands")
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
result_kind := hir.Expr_Kind.Div_Trunc
#partial switch kind {
case .Floor: result_kind = .Div_Floor
case .Exact: result_kind = .Div_Exact
case .Ceil: result_kind = .Div_Ceil
case .Rem: result_kind = .Rem
case .Mod: result_kind = .Mod
case:
}
return add_hir_expr(checker, hir.Expr{
kind=result_kind, span=expr.span, type=result, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
fallible_aggregate :: proc(
checker: ^Checker,
span: source.Span,
@@ -5032,7 +5304,11 @@ build_compound_expr :: proc(
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
actual := checker.module.exprs[value].type
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
valid_actual := types.is_concrete_scalar(actual) && !types.is_bool(actual)
actual_repr := types.runtime_representation(actual, store)
actual_item, actual_item_ok := types.node(store, actual)
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
if !valid_target || !valid_actual {
id := source.addf(
checker.diagnostics,
@@ -5578,6 +5854,13 @@ build_binary_arith :: proc(
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
return invalid_hir_expr(checker, span, id)
}
if op == .Div && !types.is_float(result, checker.target) {
id := source.add(
checker.diagnostics, span,
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
)
return invalid_hir_expr(checker, span, id, result)
}
result_kind := hir.Expr_Kind.Add
#partial switch op {
case .Sub: result_kind = .Sub
@@ -5627,7 +5910,7 @@ build_expr :: proc(
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
last = build_constant_expr(checker, expr, constant, frame.expected)
_ = pop(&stack)
continue
@@ -5807,6 +6090,13 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
if builtin := division_builtin_call(checker, expr); builtin != .None {
last = build_division_builtin(
checker, expr, builtin, locals, global_reads, calls, frame.expected, pkg, file,
)
_ = pop(&stack)
continue
}
if is_ptr_cast_call(checker, expr) {
if len(expr.args) != 2 {
id := source.addf(checker.diagnostics, expr.span, "ptr_cast expects 2 arguments, got %d", len(expr.args))
@@ -6362,7 +6652,7 @@ build_expr :: proc(
make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
if function.pkg == 0 && function.name == checker.main_symbol {
if function.pkg == 0 && function.name == checker.main_symbol && !checker.io_main {
return fmt.aprintf("main", allocator = checker.allocator)
}
if function.generated {
@@ -6653,7 +6943,14 @@ build_block :: proc(
}
rhs_type := checker.module.exprs[value].type
result_type := types.widest(target_type, rhs_type)
if !types.is_concrete_scalar(result_type) ||
if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
id := source.add(
checker.diagnostics,
statement.span,
"integer '/=' is not allowed; assign through an explicit division builtin",
)
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else if !types.is_concrete_scalar(result_type) ||
types.is_bool(result_type) {
id := source.add(
checker.diagnostics,
@@ -8970,6 +9267,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
native_main := function.pkg == 0 && function.name == checker.main_symbol && !checker.io_main
if !function.has_body {
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
@@ -8980,7 +9278,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
calling_convention = .C if function.c_abi else .Brolang,
implementation = .Declaration,
linkage = .External if function.c_abi else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
is_main = native_main,
variadic = function.variadic,
params = params[:],
result = spec.result,
@@ -9078,10 +9376,10 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
hir.Function {
name = function.name,
link_name = make_link_name(checker, id),
calling_convention = .C if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang,
calling_convention = .C if function.c_abi || native_main else .Brolang,
implementation = .Definition,
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
linkage = .External if function.c_abi || native_main else .Internal,
is_main = native_main,
variadic = function.variadic,
params = params[:],
result = spec.result,
@@ -9529,6 +9827,7 @@ check :: proc(
symbols = symbols,
module = hir.init_module(selected, allocator),
main_symbol = symbol.intern(symbols, "main"),
io_provider_template = ast.INVALID_FUNCTION,
sink_symbol = symbol.intern(symbols, "_"),
type_symbol = symbol.intern(symbols, "type"),
target = selected,
@@ -9642,6 +9941,7 @@ check :: proc(
validate_type_nodes(&checker)
validate_declarations(&checker)
configure_io_main(&checker)
infer_all(&checker)
validate_external_globals(&checker)
prune_specs(&checker)
@@ -9668,19 +9968,29 @@ check :: proc(
synthesize_trap_main(&checker)
} else {
template := ast_module.functions[main_template]
valid_params := len(template.params) == 0 || checker.io_main && len(template.params) == 1
if main_declarations != 1 ||
!template.has_body ||
len(template.params) != 0 ||
!valid_params ||
!(template.result == types.VOID || template.result == types.I32 || template.result == types.INT) {
id := checker.template_diagnostics[main_template]
if id == source.INVALID_DIAGNOSTIC {
id = source.add(
diagnostics,
template.span,
"main must be unique, have a body, take no parameters, and return void, i32, or int",
"main must be unique, have a body, take no parameters or one @std/io Io, and return void, i32, or int",
)
}
checker.module.injected_main = hir.INVALID_FUNCTION
checker.module.io_provider = hir.INVALID_FUNCTION
replace_main_with_trap(&checker, id)
} else if checker.io_main {
main_spec := find_spec(&checker, main_template, nil)
provider_spec := find_spec(&checker, checker.io_provider_template, nil)
if main_spec != INVALID_SPEC && provider_spec != INVALID_SPEC {
checker.module.injected_main = checker.specs[main_spec].hir_id
checker.module.io_provider = checker.specs[provider_spec].hir_id
}
}
}
+172 -26
View File
@@ -6,6 +6,8 @@ import "../source"
import "../symbol"
import "../types"
import "base:intrinsics"
import "core:math"
import "core:mem"
COMPTIME_EVAL_QUOTA :: 100_000
@@ -28,6 +30,8 @@ Constant_Kind :: enum {
Value,
Overflow,
Div_By_Zero,
Non_Exact,
Integer_Division,
}
Constant :: struct {
@@ -94,8 +98,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul &&
expr.kind != .Div && expr.kind != .Negate {
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul && expr.kind != .Negate {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
@@ -118,9 +121,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
operand = checker.constants[expr.left]
}
result := Constant{kind = .Not_Constant}
if operand.kind == .Div_By_Zero {
result = Constant{kind = .Div_By_Zero}
} else if operand.kind == .Overflow {
if operand.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if operand.kind == .Value {
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
@@ -147,29 +148,19 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
right = checker.constants[expr.right]
}
result := Constant{kind = .Not_Constant}
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
result = Constant{kind = .Div_By_Zero}
} else if left.kind == .Overflow || right.kind == .Overflow {
if left.kind == .Overflow || right.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if left.kind == .Value && right.kind == .Value {
value: i128
overflow: bool
div_by_zero: bool
#partial switch expr.kind {
case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value)
case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value)
case .Div:
if right.value == 0 {
div_by_zero = true
} else {
value = left.value / right.value
}
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
}
switch {
case div_by_zero: result = Constant{kind = .Div_By_Zero}
case overflow: result = Constant{kind = .Overflow}
case: result = Constant{kind = .Value, value = value}
case overflow: result = Constant{kind = .Overflow}
case: result = Constant{kind = .Value, value = value}
}
}
checker.constants[frame.expr] = result
@@ -226,6 +217,8 @@ Ct_Error_Kind :: enum u8 {
Not_Comptime,
Overflow,
Div_By_Zero,
Non_Exact,
Integer_Division,
Quota,
}
@@ -1105,7 +1098,8 @@ ct_eval_expr :: proc(
}
return ct_eval_unary(state, expr.kind, value, expr.span)
case .Add, .Sub, .Mul, .Div, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
@@ -1858,6 +1852,12 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
}
if op == .Div {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Integer_Division, span,
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
)
}
value: i128
overflow := false
#partial switch op {
@@ -1865,12 +1865,6 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
value, overflow = intrinsics.overflow_sub(left.integer, right.integer)
case .Mul:
value, overflow = intrinsics.overflow_mul(left.integer, right.integer)
case .Div:
if right.integer == 0 {
state.error = .Div_By_Zero
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
value = left.integer / right.integer
case:
value, overflow = intrinsics.overflow_add(left.integer, right.integer)
}
@@ -1887,6 +1881,137 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
ct_eval_division_builtin :: proc(
state: ^Ct_State,
kind: Division_Builtin,
left_id, right_id: Ct_Value_Id,
span: source.Span,
) -> (Ct_Value_Id, Ct_Flow, bool) {
if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE ||
int(left_id) >= len(state.values) || int(right_id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
left := state.values[left_id]
right := state.values[right_id]
result_type := types.widest(left.type, right.type)
if !types.is_concrete_scalar(result_type) || types.is_bool(result_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "division builtins require compatible numeric operands",
)
}
left_id, left_ok := ct_coerce_value(state, left_id, result_type, span)
right_id, right_ok := ct_coerce_value(state, right_id, result_type, span)
if !left_ok || !right_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
left = state.values[left_id]
right = state.values[right_id]
if left.kind == .Float && right.kind == .Float {
if right.float == 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
}
quotient := left.float / right.float
result := quotient
#partial switch kind {
case .Trunc: result = math.trunc(quotient)
case .Floor: result = math.floor(quotient)
case .Ceil: result = math.ceil(quotient)
case .Exact:
result = math.trunc(quotient)
if result * right.float != left.float {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
}
case .Rem, .Mod:
result = left.float - math.trunc(quotient) * right.float
if kind == .Mod && result != 0 && (result < 0) != (right.float < 0) {
result += right.float
}
}
if types.bits(result_type, state.checker.target) == 32 {
result = f64(f32(result))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
}
if left.kind != .Integer || right.kind != .Integer {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "division builtins require compatible numeric operands")
}
if right.integer == 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
}
is_quotient := kind == .Trunc || kind == .Floor || kind == .Exact || kind == .Ceil
if is_quotient && types.is_signed(result_type, state.checker.target) {
minimum := -(i128(1) << u32(types.bits(result_type, state.checker.target)-1))
if left.integer == minimum && right.integer == -1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Overflow, span, "signed integer division overflow")
}
}
quotient := left.integer / right.integer
remainder := left.integer % right.integer
result := quotient
#partial switch kind {
case .Floor:
if remainder != 0 && (left.integer < 0) != (right.integer < 0) {
result -= 1
}
case .Ceil:
if remainder != 0 && (left.integer < 0) == (right.integer < 0) {
result += 1
}
case .Exact:
if remainder != 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
}
case .Rem: result = remainder
case .Mod:
result = remainder
if result != 0 && (result < 0) != (right.integer < 0) {
result += right.integer
}
case:
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true
}
ct_eval_division_call :: proc(
state: ^Ct_State,
expr: ast.Expr,
kind: Division_Builtin,
expected: types.Type,
depth: int,
) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span, "%s expects 2 arguments, got %d",
symbol_text(checker, expr.name), len(expr.args),
)
}
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.args[0])
right_const := is_numeric_constant_expr(checker, expr.args[1])
left, right := INVALID_CT_VALUE, INVALID_CT_VALUE
flow := ct_flow(.Normal)
ok := false
if left_const && !right_const && !types.is_valid(hint) {
right, flow, ok = ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
left, flow, ok = ct_eval_expr(state, expr.args[0], state.values[right].type, depth+1)
} else {
left, flow, ok = ct_eval_expr(state, expr.args[0], hint, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right_hint := hint if types.is_valid(hint) else state.values[left].type
right, flow, ok = ct_eval_expr(state, expr.args[1], right_hint, depth+1)
}
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
return ct_eval_division_builtin(state, kind, left, right, expr.span)
}
ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
@@ -1943,6 +2068,9 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=type_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true
}
if builtin := division_builtin_call(checker, expr); builtin != .None {
return ct_eval_division_call(state, expr, builtin, expected, depth+1)
}
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
if !available {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
@@ -2899,6 +3027,10 @@ eval_integer_constant_in_context :: proc(
return Constant{kind=.Overflow}
case .Div_By_Zero:
return Constant{kind=.Div_By_Zero}
case .Non_Exact:
return Constant{kind=.Non_Exact}
case .Integer_Division:
return Constant{kind=.Integer_Division}
}
return Constant{kind=.Not_Constant}
}
@@ -2927,6 +3059,10 @@ eval_comptime_statements :: proc(
return Constant{kind=.Overflow}, false, false
case .Div_By_Zero:
return Constant{kind=.Div_By_Zero}, false, false
case .Non_Exact:
return Constant{kind=.Non_Exact}, false, false
case .Integer_Division:
return Constant{kind=.Integer_Division}, false, false
}
return Constant{kind=.Not_Constant}, false, false
}
@@ -2958,6 +3094,10 @@ eval_comptime_call :: proc(
return Constant{kind=.Overflow}
case .Div_By_Zero:
return Constant{kind=.Div_By_Zero}
case .Non_Exact:
return Constant{kind=.Non_Exact}
case .Integer_Division:
return Constant{kind=.Integer_Division}
}
return Constant{kind=.Not_Constant}
}
@@ -2996,7 +3136,7 @@ infer_comptime_expr_type :: proc(
}
}
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) {
if state.error == .Overflow || state.error == .Div_By_Zero {
if state.error == .Overflow || state.error == .Div_By_Zero || state.error == .Non_Exact || state.error == .Integer_Division {
return types.I64
}
return types.INVALID
@@ -3038,6 +3178,12 @@ build_comptime_expr :: proc(
if state.error == .Overflow {
return build_constant_expr(checker, expr, Constant{kind=.Overflow}, expected)
}
if state.error == .Non_Exact {
return build_constant_expr(checker, expr, Constant{kind=.Non_Exact}, expected)
}
if state.error == .Integer_Division {
return build_constant_expr(checker, expr, Constant{kind=.Integer_Division}, expected)
}
diagnostic := state.diagnostic
if diagnostic == source.INVALID_DIAGNOSTIC {
diagnostic = source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime")
+10
View File
@@ -113,6 +113,12 @@ Expr_Kind :: enum u8 {
Sub,
Mul,
Div,
Div_Trunc,
Div_Floor,
Div_Exact,
Div_Ceil,
Rem,
Mod,
Pointer_Add,
Eq,
Ne,
@@ -253,6 +259,8 @@ Module :: struct {
functions: [dynamic]Function,
globals: [dynamic]Global,
strings: [dynamic]string,
injected_main: Function_Id,
io_provider: Function_Id,
types: types.Store,
target: target.Target,
allocator: mem.Allocator,
@@ -261,6 +269,8 @@ Module :: struct {
init_module :: proc(selected := target.DEFAULT, allocator := context.allocator) -> Module {
module: Module
module.target = selected
module.injected_main = INVALID_FUNCTION
module.io_provider = INVALID_FUNCTION
module.types = types.init_store(allocator)
module.types.selected = selected
module.allocator = allocator
+6
View File
@@ -109,6 +109,12 @@ Opcode :: enum u8 {
Sub_Checked,
Mul_Checked,
Div_Checked,
Div_Trunc_Checked,
Div_Floor_Checked,
Div_Exact_Checked,
Div_Ceil_Checked,
Rem_Checked,
Mod_Checked,
Pointer_Add,
Not,
Compare,
+1
View File
@@ -105,6 +105,7 @@ lex :: proc(
) -> token.Stream {
stream: token.Stream
stream.items.allocator = allocator
stream.symbols = symbols
bytes := transmute([]byte)source_file.text
cursor := 0
+241 -49
View File
@@ -257,7 +257,10 @@ valid_value :: proc(
.Fallible_Error, .Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse,
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked,
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked,
.Pointer_Add, .Not, .Compare, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
@@ -582,9 +585,7 @@ emit_checked_arithmetic :: proc(
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
}
// emit_checked_division emits a trapping integer division guarding divide-by-zero
// and signed `INT_MIN / -1` overflow, or a plain floating-point division.
emit_checked_division :: proc(
emit_division_zero_guard :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
@@ -592,56 +593,225 @@ emit_checked_division :: proc(
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = fdiv %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
fmt.sbprintf(&emitter.builder, " %%divzero%d = fcmp oeq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
return
strings.write_string(&emitter.builder, ", 0.000000e+00\n")
} else {
fmt.sbprintf(&emitter.builder, " %%divzero%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0\n")
}
signed := !types.is_unsigned(instruction.type, emitter.module.target)
fmt.sbprintf(&emitter.builder, " %%divzero%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0\n")
fmt.sbprintf(
&emitter.builder,
" br i1 %%divzero%d, label %%divzero_trap%d, label %%divzero_ok%d\n",
" br i1 %%divzero%d, label %%divzero_trap%d, label %%divzero_ok%d\ndivzero_trap%d:\n",
instruction_index,
instruction_index,
instruction_index,
instruction_index,
)
fmt.sbprintf(&emitter.builder, "divzero_trap%d:\n", instruction_index)
zero_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer division by zero")
emit_trap_call(emitter, zero_message)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "division builtin denominator is zero")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\ndivzero_ok%d:\n", instruction_index)
if signed {
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target) - 1))
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
}
emit_division_overflow_guard :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n %%divminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(
&emitter.builder,
", -1\n %%divovf%d = and i1 %%divminlo%d, %%divminhi%d\n br i1 %%divovf%d, label %%divovf_trap%d, label %%divovf_ok%d\ndivovf_trap%d:\n",
instruction_index,
instruction_index,
instruction_index,
instruction_index,
instruction_index,
instruction_index,
instruction_index,
)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer division overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\ndivovf_ok%d:\n", instruction_index)
}
emit_float_division_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
suffix := "f32" if types.bits(instruction.type, emitter.module.target) == 32 else "f64"
if instruction.op == .Rem_Checked || instruction.op == .Mod_Checked {
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked else fmt.tprintf("%%rawrem%d", instruction_index)
fmt.sbprintf(&emitter.builder, " %s = frem %s ", name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n", min_value)
fmt.sbprintf(&emitter.builder, " %%divminhi%d = icmp eq %s ", instruction_index, type_name)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", -1\n")
fmt.sbprintf(&emitter.builder, " %%divovf%d = and i1 %%divminlo%d, %%divminhi%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(
&emitter.builder,
" br i1 %%divovf%d, label %%divovf_trap%d, label %%divovf_ok%d\n",
instruction_index,
instruction_index,
instruction_index,
)
fmt.sbprintf(&emitter.builder, "divovf_trap%d:\n", instruction_index)
ovf_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer division overflow")
emit_trap_call(emitter, ovf_message)
fmt.sbprintf(&emitter.builder, " unreachable\ndivovf_ok%d:\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%v%d = sdiv %s ", instruction_index, type_name)
} else {
fmt.sbprintf(&emitter.builder, " %%v%d = udiv %s ", instruction_index, type_name)
strings.write_string(&emitter.builder, "\n")
if instruction.op == .Rem_Checked {
return
}
fmt.sbprintf(&emitter.builder, " %%remnonzero%d = fcmp one %s %%rawrem%d, 0.000000e+00\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%remsign%d = fcmp olt %s %%rawrem%d, 0.000000e+00\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%denomsign%d = fcmp olt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0.000000e+00\n")
fmt.sbprintf(&emitter.builder, " %%signsdiffer%d = xor i1 %%remsign%d, %%denomsign%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%modadjust%d = and i1 %%remnonzero%d, %%signsdiffer%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%adjustedrem%d = fadd %s %%rawrem%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%v%d = select i1 %%modadjust%d, %s %%adjustedrem%d, %s %%rawrem%d\n", instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
return
}
fmt.sbprintf(&emitter.builder, " %%divq%d = fdiv %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
intrinsic := "trunc"
if instruction.op == .Div_Floor_Checked {
intrinsic = "floor"
} else if instruction.op == .Div_Ceil_Checked {
intrinsic = "ceil"
}
fmt.sbprintf(&emitter.builder, " %%v%d = call %s @llvm.%s.%s(%s %%divq%d)\n", instruction_index, type_name, intrinsic, suffix, type_name, instruction_index)
if instruction.op != .Div_Exact_Checked {
return
}
fmt.sbprintf(&emitter.builder, " %%exactprod%d = fmul %s %%v%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%exact%d = fcmp oeq %s %%exactprod%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n br i1 %%exact%d, label %%exact_ok%d, label %%exact_trap%d\nexact_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "exact division has a remainder")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nexact_ok%d:\n", instruction_index)
}
emit_integer_remainder_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
signed := types.is_signed(instruction.type, emitter.module.target)
raw_name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked || !signed else fmt.tprintf("%%rawrem%d", instruction_index)
if !signed {
fmt.sbprintf(&emitter.builder, " %s = urem %s ", raw_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
} else {
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
fmt.sbprintf(&emitter.builder, " %%remminlo%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n %%remminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", -1\n %%remspecial%d = and i1 %%remminlo%d, %%remminhi%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " br i1 %%remspecial%d, label %%rem_special%d, label %%rem_normal%d\nrem_special%d:\n br label %%rem_join%d\nrem_normal%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%remnormal%d = srem %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n br label %%rem_join%d\nrem_join%d:\n %s = phi %s [ 0, %%rem_special%d ], [ %%remnormal%d, %%rem_normal%d ]\n", instruction_index, instruction_index, raw_name, type_name, instruction_index, instruction_index, instruction_index)
}
if instruction.op == .Rem_Checked || !signed {
return
}
fmt.sbprintf(&emitter.builder, " %%remnonzero%d = icmp ne %s %%rawrem%d, 0\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%remsign%d = icmp slt %s %%rawrem%d, 0\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%denomsign%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%signsdiffer%d = xor i1 %%remsign%d, %%denomsign%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%modadjust%d = and i1 %%remnonzero%d, %%signsdiffer%d\n %%adjustedrem%d = add %s %%rawrem%d, ", instruction_index, instruction_index, instruction_index, instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%v%d = select i1 %%modadjust%d, %s %%adjustedrem%d, %s %%rawrem%d\n", instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
}
emit_integer_quotient_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
signed := types.is_signed(instruction.type, emitter.module.target)
operation := "sdiv" if signed else "udiv"
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Div_Trunc_Checked else fmt.tprintf("%%divq%d", instruction_index)
fmt.sbprintf(&emitter.builder, " %s = %s %s ", name, operation, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
if instruction.op == .Div_Trunc_Checked {
return
}
fmt.sbprintf(&emitter.builder, " %%divprod%d = mul %s %%divq%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%divrem%d = sub %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %%divprod%d\n", instruction_index)
if instruction.op == .Div_Exact_Checked {
fmt.sbprintf(&emitter.builder, " %%exact%d = icmp eq %s %%divrem%d, 0\n br i1 %%exact%d, label %%exact_ok%d, label %%exact_trap%d\nexact_trap%d:\n", instruction_index, type_name, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "exact division has a remainder")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nexact_ok%d:\n %%v%d = add %s %%divq%d, 0\n", instruction_index, instruction_index, type_name, instruction_index)
return
}
fmt.sbprintf(&emitter.builder, " %%remnonzero%d = icmp ne %s %%divrem%d, 0\n", instruction_index, type_name, instruction_index)
if signed {
fmt.sbprintf(&emitter.builder, " %%numsign%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%denomsign%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%signsdiffer%d = xor i1 %%numsign%d, %%denomsign%d\n", instruction_index, instruction_index, instruction_index)
predicate := fmt.tprintf("%%signsdiffer%d", instruction_index)
if instruction.op == .Div_Ceil_Checked {
fmt.sbprintf(&emitter.builder, " %%signssame%d = xor i1 %%signsdiffer%d, true\n", instruction_index, instruction_index)
predicate = fmt.tprintf("%%signssame%d", instruction_index)
}
fmt.sbprintf(&emitter.builder, " %%divadjust%d = and i1 %%remnonzero%d, %s\n", instruction_index, instruction_index, predicate)
} else {
fmt.sbprintf(&emitter.builder, " %%divadjust%d = and i1 %%remnonzero%d, true\n", instruction_index, instruction_index)
}
adjustment := "sub" if instruction.op == .Div_Floor_Checked else "add"
fmt.sbprintf(&emitter.builder, " %%adjustedq%d = %s %s %%divq%d, 1\n %%v%d = select i1 %%divadjust%d, %s %%adjustedq%d, %s %%divq%d\n", instruction_index, adjustment, type_name, instruction_index, instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
}
emit_division_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
emit_division_zero_guard(emitter, instructions, instruction_index, instruction)
if types.is_float(instruction.type, emitter.module.target) {
emit_float_division_builtin(emitter, instructions, instruction_index, instruction)
return
}
quotient := instruction.op == .Div_Trunc_Checked || instruction.op == .Div_Floor_Checked ||
instruction.op == .Div_Exact_Checked || instruction.op == .Div_Ceil_Checked
if quotient && types.is_signed(instruction.type, emitter.module.target) {
emit_division_overflow_guard(emitter, instructions, instruction_index, instruction)
}
if quotient {
emit_integer_quotient_builtin(emitter, instructions, instruction_index, instruction)
} else {
emit_integer_remainder_builtin(emitter, instructions, instruction_index, instruction)
}
}
emit_instruction_stream :: proc(
@@ -1478,18 +1648,23 @@ emit_instruction_stream :: proc(
)
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
case .Scalar_Cast:
if !valid_instruction(instructions, instruction.a) ||
!types.is_concrete_scalar(instructions[instruction.a].type) ||
!types.is_concrete_scalar(instruction.type) ||
types.is_bool(instructions[instruction.a].type) ||
types.is_bool(instruction.type) {
if !valid_instruction(instructions, instruction.a) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid scalar cast operand")
continue
}
from_type := instructions[instruction.a].type
from_bits := types.bits(from_type, emitter.module.target)
from_repr := types.runtime_representation(from_type, &emitter.module.types)
from_item, from_item_ok := types.node(&emitter.module.types, from_type)
explicit_enum := from_item_ok && from_item.kind == .Enum && from_item.explicit_backing
valid_from := (types.is_concrete_scalar(from_type) || explicit_enum) &&
types.is_concrete_scalar(from_repr) && !types.is_bool(from_repr)
if !valid_from || !types.is_concrete_scalar(instruction.type) || types.is_bool(instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid scalar cast operand")
continue
}
from_bits := types.bits(from_repr, emitter.module.target)
to_bits := types.bits(instruction.type, emitter.module.target)
from_float := types.is_float(from_type, emitter.module.target)
from_float := types.is_float(from_repr, emitter.module.target)
to_float := types.is_float(instruction.type, emitter.module.target)
if types.equal(from_type, instruction.type) || from_bits == to_bits && from_float == to_float {
type_name := llvm_type(instruction.type, &emitter.module.types)
@@ -1505,11 +1680,11 @@ emit_instruction_stream :: proc(
case from_float && to_float:
operation = "fpext" if from_bits < to_bits else "fptrunc"
case !from_float && !to_float:
operation = "trunc" if from_bits > to_bits else ("sext" if types.is_signed(from_type, emitter.module.target) else "zext")
operation = "trunc" if from_bits > to_bits else ("sext" if types.is_signed(from_repr, emitter.module.target) else "zext")
case from_float:
operation = "fptosi" if types.is_signed(instruction.type, emitter.module.target) else "fptoui"
case:
operation = "sitofp" if types.is_signed(from_type, emitter.module.target) else "uitofp"
operation = "sitofp" if types.is_signed(from_repr, emitter.module.target) else "uitofp"
}
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
@@ -1609,11 +1784,22 @@ emit_instruction_stream :: proc(
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow")
case .Div_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
!types.is_float(instruction.type, emitter.module.target) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand")
continue
}
emit_checked_division(emitter, instructions, instruction_index, instruction)
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "div", "fdiv", "")
case .Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
(!types.is_concrete_integer(instruction.type) &&
!types.is_float(instruction.type, emitter.module.target)) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division builtin operands")
continue
}
emit_division_builtin(emitter, instructions, instruction_index, instruction)
case .Pointer_Add:
result_item, result_ok := types.node(&emitter.module.types, instruction.type)
base_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
@@ -2176,6 +2362,11 @@ emit_constructor :: proc(emitter: ^Emitter) {
emit_functions :: proc(emitter: ^Emitter) {
for function, function_index in emitter.module.functions {
// bro.trap already declares libc write. A demanded std/io binding shares
// that declaration instead of emitting an LLVM redefinition.
if function.implementation == .Declaration && function.link_name == "write" {
continue
}
if function.implementation == .Declaration {
duplicate := false
for previous in emitter.module.functions[:function_index] {
@@ -2283,6 +2474,7 @@ emit_messages :: proc(emitter: ^Emitter) {
emit_declarations :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\ndeclare void @llvm.memset.p0.i64(ptr, i8, i64, i1 immarg)\n")
strings.write_string(&emitter.builder, "declare float @llvm.trunc.f32(float)\ndeclare double @llvm.trunc.f64(double)\ndeclare float @llvm.floor.f32(float)\ndeclare double @llvm.floor.f64(double)\ndeclare float @llvm.ceil.f32(float)\ndeclare double @llvm.ceil.f64(double)\n")
widths := [?]int{8, 16, 32, 64}
overflow_intrinsics := [?]string{"sadd", "uadd", "ssub", "usub", "smul", "umul"}
for bits in widths {
+233 -2
View File
@@ -87,7 +87,8 @@ read_package_files :: proc(state: ^State, path: string) -> ([]os.File_Info, bool
files: [dynamic]os.File_Info
files.allocator = state.allocator
for entry in entries {
if !entry.is_dir && filepath.ext(entry.name) == ".bro" {
extension := filepath.ext(entry.name)
if !entry.is_dir && (extension == ".bro" || extension == ".hon") {
append(&files, entry)
} else {
os.file_info_delete(entry, state.allocator)
@@ -1170,7 +1171,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
if is_root {
state.root_failed = true
} else {
source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro files", canonical)
source.addf(state.diagnostics, import_span, "package '%s' contains no readable .bro or .hon files", canonical)
state.module.packages[pkg_id].available = false
}
os.file_info_slice_delete(files, state.allocator)
@@ -1293,6 +1294,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 +1686,7 @@ load :: proc(
state.root_failed = true
}
validate_imports(&state)
validate_declaration_aliases(&state)
canonicalize_types(&module, allocator)
return module, !state.root_failed
}
+72 -1
View File
@@ -681,7 +681,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Negate:
stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add, .Sub, .Mul, .Div, .Pointer_Add:
case .Add, .Sub, .Mul, .Div, .Div_Trunc, .Div_Floor, .Div_Exact, .Div_Ceil,
.Rem, .Mod, .Pointer_Add:
stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call:
@@ -760,6 +761,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Sub: op = .Sub_Checked
case .Mul: op = .Mul_Checked
case .Div: op = .Div_Checked
case .Div_Trunc: op = .Div_Trunc_Checked
case .Div_Floor: op = .Div_Floor_Checked
case .Div_Exact: op = .Div_Exact_Checked
case .Div_Ceil: op = .Div_Ceil_Checked
case .Rem: op = .Rem_Checked
case .Mod: op = .Mod_Checked
case .Pointer_Add: op = .Pointer_Add
}
last = append_instruction(state, ir.Instruction{
@@ -1601,6 +1608,69 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al
return state.instructions[:]
}
append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, allocator: mem.Allocator) {
main_index, main_ok := hir.index(hir_module.injected_main, hir.INVALID_FUNCTION, len(hir_module.functions))
provider_index, provider_ok := hir.index(hir_module.io_provider, hir.INVALID_FUNCTION, len(hir_module.functions))
if !main_ok || !provider_ok {
return
}
instructions: [dynamic]ir.Instruction
instructions.allocator = allocator
provider_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Call,
type=hir_module.functions[provider_index].result,
target=ir.function_ref(ir.Function_Id(provider_index)),
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
args := make([]ir.Instruction_Id, 1, allocator)
args[0] = provider_call
main_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Call,
type=hir_module.functions[main_index].result,
args=args,
target=ir.function_ref(ir.Function_Id(main_index)),
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
if types.is_void(hir_module.functions[main_index].result) {
append(&instructions, ir.Instruction{
op=.Return_Void,
type=types.VOID,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
append(&instructions, ir.Instruction{
op=.Return,
type=hir_module.functions[main_index].result,
target=ir.INVALID_REF,
a=main_call,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
append(&module.functions, ir.Function{
link_name=fmt.aprintf("main", allocator=allocator),
calling_convention=.C,
implementation=.Definition,
linkage=.External,
is_main=true,
result=hir_module.functions[main_index].result,
instructions=instructions[:],
problematic=hir_module.functions[main_index].problematic ||
hir_module.functions[provider_index].problematic,
})
}
lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module {
module := ir.init_module(hir_module.target, allocator)
types.destroy_store(&module.types)
@@ -1642,5 +1712,6 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
problematic=function.problematic,
})
}
append_injected_main(&module, hir_module, allocator)
return module
}
+59 -17
View File
@@ -86,6 +86,18 @@ allow :: proc(parser: ^Parser, kind: token.Kind) -> (token.Token, bool) {
return current(parser), false
}
parse_member_name :: proc(parser: ^Parser, allow_keyword := true) -> (token.Token, bool) {
name := current(parser)
if name.kind != .Identifier && (!allow_keyword || !token.is_keyword(name.kind)) {
return name, false
}
advance(parser)
if name.kind != .Identifier {
name.symbol = symbol.intern(parser.tokens.symbols, token_text(parser, name))
}
return name, true
}
skip_newlines :: proc(parser: ^Parser) {
for current(parser).kind == .Newline {
advance(parser)
@@ -539,12 +551,11 @@ parse_keyed_initializers :: proc(
args.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
field := current(parser)
if field.kind != .Identifier {
field, field_ok := parse_member_name(parser)
if !field_ok {
source.add(parser.diagnostics, field.span, "expected a keyed struct field initializer")
break
}
advance(parser)
// A bare key (`T{ variant }`, no `= value`) constructs a void-payload union
// variant; the checker validates that the field actually has a void type.
value := ast.INVALID_EXPR
@@ -804,11 +815,10 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
return parse_array_literal(parser, nesting)
case .Dot:
start := advance(parser)
member := current(parser)
if member.kind != .Identifier {
member, member_ok := parse_member_name(parser)
if !member_ok {
return invalid_expr(parser, member.span, "expected an enum member after '.'")
}
advance(parser)
payload := ast.INVALID_EXPR
end := member.span
if left_brace, ok := allow(parser, .Left_Brace); ok {
@@ -868,11 +878,12 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
name := first
qualifier := symbol.INVALID
if _, ok := allow(parser, .Dot); ok {
if current(parser).kind != .Identifier {
member, member_ok := parse_member_name(parser)
if !member_ok {
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
}
qualifier = first.symbol
name = advance(parser)
name = member
}
if current(parser).kind == .Left_Paren {
call := parse_call(parser, qualifier, first, name, nesting)
@@ -1074,12 +1085,11 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
}
if current(parser).kind == .Dot {
advance(parser)
field := current(parser)
if field.kind != .Identifier {
field, field_ok := parse_member_name(parser)
if !field_ok {
left = invalid_expr(parser, field.span, "expected a field name after '.'")
continue
}
advance(parser)
left_expr := parser.module.exprs[left]
left = add_expr(parser, ast.Expr{
kind=.Field,
@@ -2252,6 +2262,7 @@ parse_record_body :: proc(
fields: ^[dynamic]types.Field,
expected_open: string,
allow_anonymous_struct_payload := false,
allow_keyword_names := false,
) -> bool {
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, expected_open)
@@ -2259,7 +2270,8 @@ parse_record_body :: proc(
}
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
field_name, field_ok := parse_member_name(parser, allow_keyword_names)
if !field_ok {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
@@ -2269,7 +2281,6 @@ parse_record_body :: proc(
skip_newlines(parser)
continue
}
field_name := advance(parser)
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
if _, ok := allow(parser, .Comma); ok {
@@ -2317,7 +2328,7 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
defer delete(fields)
if !parse_record_body(parser, &fields, "expected '{' after inline union error type", true) || !valid {
if !parse_record_body(parser, &fields, "expected '{' after inline union error type", true, true) || !valid {
return types.INVALID
}
tag := synthesize_union_tag(parser, fields[:])
@@ -2372,7 +2383,13 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
fields.allocator = parser.module.allocator
defer delete(fields)
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
_ = parse_record_body(parser, &fields, "expected '{' after struct fields", allow_anonymous_struct_payload)
_ = parse_record_body(
parser,
&fields,
"expected '{' after struct fields",
allow_anonymous_struct_payload,
allow_anonymous_struct_payload,
)
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:])
}
@@ -2425,6 +2442,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) {
@@ -2454,7 +2496,8 @@ parse_enum_body :: proc(
has_previous := false
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
member, member_ok := parse_member_name(parser)
if !member_ok {
source.add(parser.diagnostics, current(parser).span, "expected an enum member name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
@@ -2464,7 +2507,6 @@ parse_enum_body :: proc(
skip_newlines(parser)
continue
}
member := advance(parser)
duplicate := false
for existing in members^ {
if existing.name == u32(member.symbol) {
+6 -1
View File
@@ -114,6 +114,10 @@ Kind :: enum u8 {
Keyword_C_Longdouble,
}
is_keyword :: proc(kind: Kind) -> bool {
return kind >= .Keyword_Func && kind <= .Keyword_C_Longdouble
}
Token :: struct {
span: source.Span,
symbol: symbol.Id,
@@ -122,5 +126,6 @@ Token :: struct {
}
Stream :: struct {
items: [dynamic]Token,
items: [dynamic]Token,
symbols: ^symbol.Table,
}
+676 -13
View File
@@ -855,7 +855,7 @@ lexer_diagnoses_invalid_import_strings :: proc(t: ^testing.T) {
}
@(test)
package_loader_discovers_lexical_immediate_bro_files :: proc(t: ^testing.T) {
package_loader_discovers_lexical_immediate_source_files :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
@@ -870,7 +870,7 @@ package_loader_discovers_lexical_immediate_bro_files :: proc(t: ^testing.T) {
testing.expect_value(t, len(module.packages), 2)
testing.expect_value(t, len(module.files), 3)
testing.expect(t, strings.has_suffix(sources.items[module.files[0].source].path, "/main.bro"))
testing.expect(t, strings.has_suffix(sources.items[module.files[1].source].path, "/value.bro"))
testing.expect(t, strings.has_suffix(sources.items[module.files[1].source].path, "/value.hon"))
testing.expect(t, strings.has_suffix(sources.items[module.files[2].source].path, "/math.bro"))
}
@@ -2051,6 +2051,116 @@ bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) {
testing.expect(t, !strings.contains(llvm_text, "declare i32 @main()"))
}
@(test)
milestone_33_injects_explicit_io_provider_and_runs_std_io :: 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/programs/io",
&sources,
&diagnostics,
&symbols,
project_root_path=".",
)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, hir_module.injected_main != hir.INVALID_FUNCTION)
testing.expect(t, hir_module.io_provider != hir.INVALID_FUNCTION)
testing.expect_value(t, count_substring_occurrences(llvm_text, "define i32 @main()"), 1)
testing.expect_value(t, count_substring_occurrences(llvm_text, "define internal fastcc i32 @bro__p0__main__"), 1)
testing.expect_value(t, count_substring_occurrences(llvm_text, "declare i64 @write("), 1)
output := "/tmp/brolang-test-io"
defer _ = os.remove(output)
status := compiler_core.compile_package(
"examples/programs/io",
output,
nil,
target.DEFAULT,
cimport.Options{},
".",
)
testing.expect_value(t, status, 0)
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{output}},
context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(t, string(stdout), "io-ok\n")
}
@(test)
milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
cases := [?]string{
"main func(value i32) void {}\n",
"main func(left, right i32) void {}\n",
}
for text in cases {
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
symbols := symbol.init_table()
stream := lexer.lex(&source_file, &diagnostics, &symbols)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(
diagnostic.message,
"take no parameters or one @std/io Io",
)
}
testing.expect(t, found)
hir.destroy_module(&hir_module)
ast.destroy_module(&ast_module)
delete(stream.items)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
}
}
@(test)
milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
main func() void {}
`
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)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(
diagnostic.message,
"external C function 'write' conflicts with the compiler runtime declaration",
)
}
testing.expect(t, found)
}
@(test)
literal_addition_trees_fold_with_contextual_types :: proc(t: ^testing.T) {
text := `return_i16 func() i16 {
@@ -6246,7 +6356,7 @@ main func() void {}
@(test)
constant_division_by_zero_has_a_precise_diagnostic :: proc(t: ^testing.T) {
text := `value :: 5 / 0
text := `value :: div_trunc(5, 0)
main func() void {}
`
source_file := source.Source{path="test.bro", text=text}
@@ -6265,7 +6375,7 @@ main func() void {}
found_overflow := false
for diagnostic in diagnostics.items {
found_division_by_zero = found_division_by_zero ||
strings.contains(diagnostic.message, "division by zero in constant expression")
strings.contains(diagnostic.message, "division builtin denominator is zero")
found_overflow = found_overflow ||
strings.contains(diagnostic.message, "integer constant expression exceeds signed i64 range")
}
@@ -6404,11 +6514,19 @@ malformed_hir_references_lower_to_valid_trapped_llvm :: proc(t: ^testing.T) {
malformed_ir_emits_traps_and_typed_sentinels :: proc(t: ^testing.T) {
module := ir.init_module()
defer ir.destroy_module(&module)
instructions := make([]ir.Instruction, 4)
instructions := make([]ir.Instruction, 11)
instructions[0] = ir.Instruction{op=.Store, type=types.I8, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[1] = ir.Instruction{op=.Add_Checked, type=types.I32, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[2] = ir.Instruction{op=.Neg_Checked, type=types.I16, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
instructions[3] = ir.Instruction{op=.Return, type=types.I32, a=ir.Instruction_Id(1), b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
division_ops := [?]ir.Opcode{
.Div_Checked,
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked,
}
for op, index in division_ops {
instructions[3+index] = ir.Instruction{op=op, type=types.I32, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
}
instructions[10] = ir.Instruction{op=.Return, type=types.I32, a=ir.Instruction_Id(1), b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}
append(&module.functions, ir.Function{
link_name=strings.clone("main"),
calling_convention=.C,
@@ -6430,6 +6548,9 @@ malformed_ir_emits_traps_and_typed_sentinels :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(text, "%v0 = add i8 0, -86"))
testing.expect(t, strings.contains(text, "%v1 = add i32 0, -1431655766"))
testing.expect(t, strings.contains(text, "%v2 = add i16 0, -21846"))
for index in 3..=9 {
testing.expect(t, strings.contains(text, fmt.tprintf("%%v%d = add i32 0, -1431655766", index)))
}
testing.expect(t, strings.contains(text, "@bro.trap(ptr %message, i64 %length) noreturn"))
testing.expect(t, !strings.contains(text, "%v-1"))
llvm_path := "/tmp/brolang-test-malformed-recovery.ll"
@@ -7004,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"
@@ -9200,12 +9464,12 @@ compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T)
signed += 6
signed -= 2
signed *= 3
signed /= 4
signed = div_trunc(signed, 4)
unsigned u32 = 24
unsigned += 6
unsigned -= 2
unsigned *= 3
unsigned /= 4
unsigned = div_trunc(unsigned, 4)
real f64 = 24.0
real += 6.0
real -= 2.0
@@ -9239,25 +9503,28 @@ compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T)
testing.expect_value(t, operation_counts[.Add], 3)
testing.expect_value(t, operation_counts[.Sub], 3)
testing.expect_value(t, operation_counts[.Mul], 3)
testing.expect_value(t, operation_counts[.Div], 3)
testing.expect_value(t, operation_counts[.Div], 1)
add_count := 0
sub_count := 0
mul_count := 0
div_count := 0
div_trunc_count := 0
for instruction in ir_module.functions[0].instructions {
#partial switch instruction.op {
case .Add_Checked: add_count += 1
case .Sub_Checked: sub_count += 1
case .Mul_Checked: mul_count += 1
case .Div_Checked: div_count += 1
case .Div_Trunc_Checked: div_trunc_count += 1
case:
}
}
testing.expect_value(t, add_count, 3)
testing.expect_value(t, sub_count, 3)
testing.expect_value(t, mul_count, 3)
testing.expect_value(t, div_count, 3)
testing.expect_value(t, div_count, 1)
testing.expect_value(t, div_trunc_count, 2)
}
@(test)
@@ -9310,7 +9577,7 @@ binary_arithmetic_rejects_non_numeric_operands :: proc(t: ^testing.T) {
text := `main func() i32 {
a i32 = 1
b u32 = 2
_ = a / b
_ = a + b
return 0
}
`
@@ -9366,7 +9633,7 @@ checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
a i32 = 10
b i32 = 3
c i32 = a - b
return c / b
return div_trunc(c, b)
}
`
source_file := source.Source{path="test.bro", text=text}
@@ -9392,6 +9659,313 @@ checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(llvm_text, "divovf_trap"))
}
@(test)
integer_slash_is_rejected_and_float_slash_remains_available :: proc(t: ^testing.T) {
Case :: struct {text, want: string}
invalid := []Case{
{text=`main func() void {
a i32 = 4
b i32 = 2
_ = a / b
}`, want="integer '/' is not allowed"},
{text=`main func() void {
a u32 = 4
b u32 = 2
_ = a / b
}`, want="integer '/' is not allowed"},
{text=`main func() void {
_ = 4 / 2
}`, want="integer '/' is not allowed"},
{text=`main func() void {
values [4 / 2]u8 = undefined
_ = &values
}`, want="integer '/' is not allowed"},
{text=`half func($value i32) i32 { return value / 2 }
main func() void { _ = $half(4) }`, want="integer '/' is not allowed"},
{text=`main func() void {
value i32 = 8
value /= 2
}`, want="assign through an explicit division builtin"},
}
for test_case in invalid {
source_file := source.Source{path="test.bro", text=test_case.text}
diagnostics := source.init_diagnostics(&source_file)
symbols := symbol.init_table()
stream := lexer.lex(&source_file, &diagnostics, &symbols)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, test_case.want)
}
testing.expect(t, found)
hir.destroy_module(&hir_module)
ast.destroy_module(&ast_module)
delete(stream.items)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
}
text := `main func() void {
value f32 = 5.0 / 2.0
value /= 2.0
_ = value
}
`
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)
division_builtins_diagnose_arity_operands_and_comptime_failures :: proc(t: ^testing.T) {
text := `bad_arity :: div_floor(1)
bad_bool :: rem(true, false)
bad_family :: mod(i32(5), f32(3))
zero_trunc :: div_trunc(1, 0)
zero_floor :: div_floor(1.0, 0.0)
zero_exact :: div_exact(1, 0)
zero_ceil :: div_ceil(1.0, 0.0)
zero_rem :: rem(1, 0)
zero_mod :: mod(1.0, 0.0)
inexact :: div_exact(5, 3)
overflow_trunc :: div_trunc(min_value(i32), -1)
overflow_floor :: div_floor(min_value(i32), -1)
overflow_exact :: div_exact(min_value(i32), -1)
overflow_ceil :: div_ceil(min_value(i32), -1)
main func() void {}
`
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)
zero_count := 0
found_arity, found_operands, found_exact := false, false, false
overflow_count := 0
for diagnostic in diagnostics.items {
found_arity = found_arity || strings.contains(diagnostic.message, "expects 2 arguments")
found_operands = found_operands || strings.contains(diagnostic.message, "compatible numeric operands")
found_exact = found_exact || strings.contains(diagnostic.message, "exact division has a remainder")
if strings.contains(diagnostic.message, "signed integer division overflow") {
overflow_count += 1
}
if strings.contains(diagnostic.message, "division builtin denominator is zero") {
zero_count += 1
}
}
testing.expect(t, found_arity)
testing.expect(t, found_operands)
testing.expect(t, found_exact)
testing.expect_value(t, overflow_count, 4)
testing.expect_value(t, zero_count, 6)
}
@(test)
division_family_compiles_and_runs_for_integer_and_float_scalars :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-division-family"
main_path := "/tmp/brolang-test-division-family/main.bro"
output := "/tmp/brolang-test-division-family-output"
text := `COUNT :: div_exact(8, 2)
items [div_ceil(10, 3)]u8 :: [0, 0, 0, 0]
OPEN :: 5
open_ceil i32 :: div_ceil(OPEN, 3)
check_i32 func(a, b, qt, qf, qc, r, m i32) bool {
return div_trunc(a, b) == qt and div_floor(a, b) == qf and
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m
}
check_f32 func(a, b, qt, qf, qc, r, m f32) bool {
return div_trunc(a, b) == qt and div_floor(a, b) == qf and
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m
}
check_f64 func(a, b, qt, qf, qc, r, m f64) bool {
return div_trunc(a, b) == qt and div_floor(a, b) == qf and
div_ceil(a, b) == qc and rem(a, b) == r and mod(a, b) == m
}
edge_rem func(a, b i32) i32 { return rem(a, b) }
edge_mod func(a, b i32) i32 { return mod(a, b) }
main func() i32 {
if COUNT != 4 or items.len != 4 or open_ceil != 2 { return 1 }
if !check_i32(5, 3, 1, 1, 2, 2, 2) { return 2 }
if !check_i32(5, -3, -1, -2, -1, 2, -1) { return 3 }
if !check_i32(-5, 3, -1, -2, -1, -2, 1) { return 4 }
if !check_i32(-5, -3, 1, 1, 2, -2, -2) { return 5 }
if div_trunc(u32(5), u32(3)) != 1 or div_floor(u32(5), u32(3)) != 1 or
div_ceil(u32(5), u32(3)) != 2 or rem(u32(5), u32(3)) != 2 or mod(u32(5), u32(3)) != 2 { return 6 }
if div_exact(i32(6), i32(3)) != 2 or div_exact(u32(6), u32(3)) != 2 { return 7 }
if !check_f32(f32(5.0), f32(3.0), f32(1.0), f32(1.0), f32(2.0), f32(2.0), f32(2.0)) or
!check_f32(f32(5.0), f32(-3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(2.0), f32(-1.0)) or
!check_f32(f32(-5.0), f32(3.0), f32(-1.0), f32(-2.0), f32(-1.0), f32(-2.0), f32(1.0)) or
!check_f32(f32(-5.0), f32(-3.0), f32(1.0), f32(1.0), f32(2.0), f32(-2.0), f32(-2.0)) { return 8 }
if !check_f64(5.0, 3.0, 1.0, 1.0, 2.0, 2.0, 2.0) or
!check_f64(5.0, -3.0, -1.0, -2.0, -1.0, 2.0, -1.0) or
!check_f64(-5.0, 3.0, -1.0, -2.0, -1.0, -2.0, 1.0) or
!check_f64(-5.0, -3.0, 1.0, 1.0, 2.0, -2.0, -2.0) { return 9 }
if div_exact(f32(6.0), f32(3.0)) != 2.0 or div_exact(f64(6.0), f64(3.0)) != 2.0 { return 10 }
if edge_rem(-2147483648, -1) != 0 or edge_mod(-2147483648, -1) != 0 { return 11 }
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
status := compiler_core.compile_package(directory, output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
division_builtins_trap_for_runtime_zero_overflow_and_inexact_results :: proc(t: ^testing.T) {
Case :: struct {
name: string,
type_name: string,
left: string,
right: string,
}
cases := [?]Case{
{name="div_trunc", type_name="i32", left="1", right="0"},
{name="div_floor", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
{name="div_exact", type_name="f64", left="1.0", right="0.0"},
{name="div_ceil", type_name="i32", left="1", right="0"},
{name="rem", type_name="f32", left="f32(1.0)", right="f32(0.0)"},
{name="mod", type_name="f64", left="1.0", right="0.0"},
{name="div_exact", type_name="i32", left="5", right="3"},
{name="div_trunc", type_name="i32", left="-2147483648", right="-1"},
{name="div_floor", type_name="i32", left="-2147483648", right="-1"},
{name="div_exact", type_name="i32", left="-2147483648", right="-1"},
{name="div_ceil", type_name="i32", left="-2147483648", right="-1"},
}
for test_case, index in cases {
directory := fmt.aprintf("/tmp/brolang-test-division-trap-%d", index)
main_path := fmt.aprintf("%s/main.bro", directory)
output := fmt.aprintf("/tmp/brolang-test-division-trap-output-%d", index)
text := fmt.aprintf(
"invoke func(a, b %s) %s {{ return %s(a, b) }}\nmain func() void {{ _ = invoke(%s, %s) }}\n",
test_case.type_name, test_case.type_name, test_case.name, test_case.left, test_case.right,
)
_ = os2.remove_all(directory)
_ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
status := compiler_core.compile_package(directory, output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect(t, !state.success)
_ = os.remove(output)
_ = os2.remove_all(directory)
delete(text)
delete(output)
delete(main_path)
delete(directory)
}
}
@(test)
qualified_division_builtin_names_resolve_as_package_functions :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-qualified-division"
math_directory := "/tmp/brolang-test-qualified-division/math"
app_directory := "/tmp/brolang-test-qualified-division/app"
math_path := "/tmp/brolang-test-qualified-division/math/math.bro"
main_path := "/tmp/brolang-test-qualified-division/app/main.bro"
output := "/tmp/brolang-test-qualified-division-output"
math_text := `div_floor func(a, b i32) i32 { return a + b }
`
main_text := `math :: import "../math"
main func() i32 { return math.div_floor(20, 22) }
`
_ = 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(math_directory) == nil)
testing.expect(t, os.make_directory(app_directory) == nil)
testing.expect(t, os.write_entire_file(math_path, transmute([]byte)math_text))
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)main_text))
status := compiler_core.compile_package(app_directory, output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
division_builtins_emit_guards_rounding_and_single_integer_divisions :: proc(t: ^testing.T) {
text := `floor_i32 func(a, b i32) i32 { return div_floor(a, b) }
ceil_i32 func(a, b i32) i32 { return div_ceil(a, b) }
exact_i32 func(a, b i32) i32 { return div_exact(a, b) }
floor_u32 func(a, b u32) u32 { return div_floor(a, b) }
rem_i16 func(a, b i16) i16 { return rem(a, b) }
mod_i16 func(a, b i16) i16 { return mod(a, b) }
floor_f32 func(a, b f32) f32 { return div_floor(a, b) }
ceil_f64 func(a, b f64) f64 { return div_ceil(a, b) }
exact_f32 func(a, b f32) f32 { return div_exact(a, b) }
rem_f64 func(a, b f64) f64 { return rem(a, b) }
mod_f32 func(a, b f32) f32 { return mod(a, b) }
main func() void {
_ = floor_i32(5, 3)
_ = ceil_i32(5, 3)
_ = exact_i32(6, 3)
_ = floor_u32(5, 3)
_ = rem_i16(5, 3)
_ = mod_i16(5, 3)
_ = floor_f32(f32(5.0), f32(3.0))
_ = ceil_f64(5.0, 3.0)
_ = exact_f32(f32(6.0), f32(3.0))
_ = rem_f64(5.0, 3.0)
_ = mod_f32(f32(5.0), f32(3.0))
}
`
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)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, strings.count(llvm_text, "sdiv i32"), 3)
testing.expect(t, !strings.contains(llvm_text, "srem i32"))
testing.expect(t, strings.contains(llvm_text, "udiv i32"))
testing.expect(t, strings.contains(llvm_text, "srem i16"))
testing.expect(t, strings.contains(llvm_text, "remspecial"))
testing.expect(t, strings.contains(llvm_text, "divzero_trap"))
testing.expect(t, strings.contains(llvm_text, "divovf_trap"))
testing.expect(t, strings.contains(llvm_text, "call float @llvm.floor.f32"))
testing.expect(t, strings.contains(llvm_text, "call double @llvm.ceil.f64"))
testing.expect(t, strings.contains(llvm_text, "call float @llvm.trunc.f32"))
testing.expect(t, strings.contains(llvm_text, "frem double"))
}
@(test)
distinct_types_preserve_nominal_identity_and_backing_representation :: proc(t: ^testing.T) {
text := `Point :: struct {
@@ -9590,8 +10164,9 @@ main func() i32 {
value Animal = .cat
values [2]Animal :: [.dog, Animal.bird]
number Nat = identity(.two)
ordinal c_int :: c_int(number)
_ = variadic(0, number)
if take(value) == Animal.cat and values[0] != values[1] and number == Nat.two {
if take(value) == Animal.cat and values[0] != values[1] and number == Nat.two and ordinal == 2 {
return 0
}
return 1
@@ -9633,6 +10208,7 @@ main func() i32 {
testing.expect_value(t, nat_members[0].value, i128(1))
testing.expect_value(t, nat_members[1].value, i128(2))
testing.expect_value(t, nat_members[2].value, i128(5))
testing.expect(t, strings.contains(llvm_text, "zext i16"))
testing.expect_value(t, types.runtime_representation(animal, &ast_module.type_store), types.U16)
testing.expect_value(t, types.size(animal, &ast_module.type_store), u64(2))
testing.expect(t, hir_module.globals[0].is_static)
@@ -9647,6 +10223,93 @@ main func() i32 {
testing.expect(t, found_promotion)
}
@(test)
keywords_are_valid_enum_members_and_tagged_union_variants :: proc(t: ^testing.T) {
testing.expect(t, token.is_keyword(.Keyword_Func))
testing.expect(t, token.is_keyword(.Keyword_C_Longdouble))
testing.expect(t, !token.is_keyword(.Identifier))
testing.expect(t, !token.is_keyword(.Underscore))
text := `TokenKind :: enum {
if
else
return
}
Token :: union(TokenKind) {
if i32
else void
return i32
}
kind func(value bool) TokenKind {
if value {
return .if
}
return TokenKind.else
}
main func() i32 {
first TokenKind = kind(true)
second TokenKind = .return
a Token = Token{ if = 1 }
b Token = Token{ else }
c Token = .return{2}
total i32 = a.if + c.return
match first {
.if: total = total + 1
.else: total = total + 2
.return: total = total + 3
}
match b {
.if |value|: total = total + value
.else: total = total + 4
.return |value|: total = total + value
}
_ = second
return total
}
`
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)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, len(llvm_text) > 0)
}
@(test)
keyword_names_remain_invalid_for_struct_fields :: proc(t: ^testing.T) {
text := `Bad :: struct {
if i32
}
`
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)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "expected a struct field name")
}
testing.expect(t, found)
}
@(test)
unbacked_enum_uses_global_u16_backing :: proc(t: ^testing.T) {
builder := strings.builder_make()
+2 -1
View File
@@ -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
@@ -1,5 +1,4 @@
# Milestone 6: compound assignment (`+=`, `-=`, `*=`, `/=`) and the binary
# arithmetic operators `-`, `*`, `/` with multiplicative precedence.
# Compound assignment (`+=`, `-=`, `*=`, `/=`) and explicit integer division.
check_float func() i32 {
x f64 = 10.0
@@ -15,7 +14,7 @@ check_float func() i32 {
check_unsigned func() i32 {
n u32 = 100
n /= 7 # 14 (truncating integer division)
n = div_trunc(n, 7) # 14
n -= 4 # 10
if n == 10 {
return 1
@@ -28,7 +27,7 @@ main func() i32 {
total += 10 # 10
total -= 3 # 7
total *= 4 # 28
total /= 2 # 14
total = div_trunc(total, 2) # 14
# binary operators honour precedence: 14 + (2 * 3) - 4 == 16
total = total + 2 * 3 - 4
+124
View File
@@ -0,0 +1,124 @@
io :: import "@std/io"
_read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
if buffer.len == 0 {
return 0
}
buffer[0] = 'o'
if buffer.len == 1 {
return 1
}
buffer[1] = 'k'
return 2
}
_read_too_much func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
return buffer.len + 1
}
_read_eof func(_ ?*mut anyopaque, _ io.ReadStream, _ []mut u8) usize ! io.ReadError {
return 0
}
_write_short func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
if bytes.len > 2 {
return 2
}
return bytes.len
}
_write_none func(_ ?*mut anyopaque, _ io.WriteStream, _ []u8) usize ! io.WriteError {
return 0
}
_write_too_much func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
return bytes.len + 1
}
_ok_vtable io.IoVTable :: io.IoVTable {
read = _read_ok,
write = _write_short,
}
_read_bad_vtable io.IoVTable :: io.IoVTable {
read = _read_too_much,
write = _write_short,
}
_eof_vtable io.IoVTable :: io.IoVTable {
read = _read_eof,
write = _write_short,
}
_write_none_vtable io.IoVTable :: io.IoVTable {
read = _read_ok,
write = _write_none,
}
_write_bad_vtable io.IoVTable :: io.IoVTable {
read = _read_ok,
write = _write_too_much,
}
reader_for func(vtable @io.IoVTable) io.Reader {
return io.Reader {
impl = io.Io {context = none, vtable = vtable},
stream = .stdin,
}
}
writer_for func(vtable @io.IoVTable) io.Writer {
return io.Writer {
impl = io.Io {context = none, vtable = vtable},
stream = .stdout,
}
}
rejects_bad_read func() bool {
buffer [1]mut u8 = [0]
_ = io.read(reader_for(&_read_bad_vtable), buffer[..]) catch |err| {
return err == .read_failed
}
return false
}
rejects_no_progress func() bool {
io.write_all(writer_for(&_write_none_vtable), "x") catch |err| {
return err == .no_progress
}
return false
}
rejects_bad_write func() bool {
_ = io.write(writer_for(&_write_bad_vtable), "x") catch |err| {
return err == .write_failed
}
return false
}
main func(system io.Io) i32 {
buffer [2]mut u8 = [0, 0]
count usize :: io.read(reader_for(&_ok_vtable), buffer[..]) catch 0
if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' {
return 1
}
eof usize :: io.read(reader_for(&_eof_vtable), buffer[..]) catch 1
empty_read usize :: io.read(reader_for(&_read_bad_vtable), buffer[0..0]) catch 1
empty_write usize :: io.write(writer_for(&_write_bad_vtable), "") catch 1
if eof != 0 or empty_read != 0 or empty_write != 0 {
return 5
}
io.write_all(writer_for(&_ok_vtable), "partial") catch |_| {
return 2
}
if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() {
return 3
}
io.write_all(io.Writer {
impl = system,
stream = .stdout,
}, "io-ok\n") catch |_| {
return 4
}
return 0
}
+13
View File
@@ -0,0 +1,13 @@
id = "brolang"
name = "Brolang"
version = "0.1.0"
schema_version = 1
authors = ["Brolang contributors"]
description = "Brolang language support"
repository = "ssh://git@gitea.hl-valdemar.dev:2222/hl-valdemar/brolang.git"
languages = ["languages/brolang"]
[grammars.brolang]
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
rev = "zed-dev"
path = "tree-sitter-brolang"
+3
View File
@@ -0,0 +1,3 @@
read c_func(_ c_int, _ ?*mut anyopaque, _ c_ulong) c_long
write c_func(_ c_int, _ ?*anyopaque, _ c_ulong) c_long
__error c_func() *mut c_int
BIN
View File
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
name = "Brolang"
grammar = "brolang"
path_suffixes = ["bro", "hon"]
line_comments = ["# "]
hard_tabs = true
tab_size = 4
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
]
+110
View File
@@ -0,0 +1,110 @@
(comment) @comment
[
(string)
(multiline_string)
] @string
(character) @string
(escape_sequence) @string.escape
[
(integer)
(float)
] @number
(boolean) @boolean
[
(none)
(undefined)
] @constant.builtin
(builtin_type) @type.builtin
(named_type) @type
(type_declaration name: (identifier) @type)
(function_declaration name: (identifier) @function)
(parameter name: (identifier) @variable.parameter)
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
(field_initializer name: (identifier) @property)
(keyed_field_initializer name: (identifier) @property)
(record_field name: (identifier) @property)
(enum_member name: (identifier) @property)
(enum_literal name: (identifier) @property)
(import_declaration alias: (identifier) @variable)
(opaque_type) @keyword
[
"func"
"c_func"
"struct"
"c_struct"
"union"
"enum"
"distinct"
"alias"
"import"
"return"
"try"
"catch"
"mut"
"orelse"
"and"
"or"
"if"
"while"
"for"
"break"
"continue"
"defer"
"yield"
"match"
"else"
] @keyword
[
"::"
"="
"+="
"-="
"*="
"/="
"=="
"!="
"<"
"<="
">"
">="
"+"
"-"
"*"
"/"
"!"
"&"
"?"
"^"
".."
"..="
"|"
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
","
"."
":"
";"
] @punctuation.delimiter
+1 -1
View File
@@ -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 {
+122
View File
@@ -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,
}
}
+3 -3
View File
@@ -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
}
+3
View File
@@ -0,0 +1,3 @@
import "arraylist"
ArrayList :: alias arraylist.ArrayList
+420
View File
@@ -0,0 +1,420 @@
# generated by brolang translate-c from stdio.h
# unsupported in bindings: C union '__mbstate_t' has no native spelling
__darwin_pthread_handler_rec :: c_struct {
__routine ?*c_func(_ ?*mut anyopaque) void
__arg ?*mut anyopaque
__next ?*mut __darwin_pthread_handler_rec
}
_opaque_pthread_attr_t :: c_struct {
__sig c_long
__opaque [56]c_char
}
_opaque_pthread_cond_t :: c_struct {
__sig c_long
__opaque [40]c_char
}
_opaque_pthread_condattr_t :: c_struct {
__sig c_long
__opaque [8]c_char
}
_opaque_pthread_mutex_t :: c_struct {
__sig c_long
__opaque [56]c_char
}
_opaque_pthread_mutexattr_t :: c_struct {
__sig c_long
__opaque [8]c_char
}
_opaque_pthread_once_t :: c_struct {
__sig c_long
__opaque [8]c_char
}
_opaque_pthread_rwlock_t :: c_struct {
__sig c_long
__opaque [192]c_char
}
_opaque_pthread_rwlockattr_t :: c_struct {
__sig c_long
__opaque [16]c_char
}
_opaque_pthread_t :: c_struct {
__sig c_long
__cleanup_stack ?*mut __darwin_pthread_handler_rec
__opaque [8176]c_char
}
__sbuf :: c_struct {
_base ?*mut c_uchar
_size c_int
}
__sFILEX :: opaque
__sFILE :: c_struct {
_p ?*mut c_uchar
_r c_int
_w c_int
_flags c_short
_file c_short
_bf __sbuf
_lbfsize c_int
_cookie ?*mut anyopaque
_close ?*c_func(_ ?*mut anyopaque) c_int
_read ?*c_func(_ ?*mut anyopaque, _ ?*mut c_char, _ c_int) c_int
_seek ?*c_func(_ ?*mut anyopaque, _ c_longlong, _ c_int) c_longlong
_write ?*c_func(_ ?*mut anyopaque, _ ?*c_char, _ c_int) c_int
_ub __sbuf
_extra ?*mut __sFILEX
_ur c_int
_ubuf [3]c_uchar
_nbuf [1]c_uchar
_lb __sbuf
_blksize c_int
_offset c_longlong
}
__int8_t :: alias c_schar
__uint8_t :: alias c_uchar
__int16_t :: alias c_short
__uint16_t :: alias c_ushort
__int32_t :: alias c_int
__uint32_t :: alias c_uint
__int64_t :: alias c_longlong
__uint64_t :: alias c_ulonglong
__darwin_intptr_t :: alias c_long
__darwin_natural_t :: alias c_uint
__darwin_ct_rune_t :: alias c_int
__darwin_mbstate_t :: alias __mbstate_t
__darwin_ptrdiff_t :: alias c_long
__darwin_size_t :: alias c_ulong
__darwin_va_list :: alias ?*mut c_char
__darwin_wchar_t :: alias c_int
__darwin_rune_t :: alias c_int
__darwin_wint_t :: alias c_int
__darwin_clock_t :: alias c_ulong
__darwin_socklen_t :: alias c_uint
__darwin_ssize_t :: alias c_long
__darwin_time_t :: alias c_long
__darwin_blkcnt_t :: alias c_longlong
__darwin_blksize_t :: alias c_int
__darwin_dev_t :: alias c_int
__darwin_fsblkcnt_t :: alias c_uint
__darwin_fsfilcnt_t :: alias c_uint
__darwin_gid_t :: alias c_uint
__darwin_id_t :: alias c_uint
__darwin_ino64_t :: alias c_ulonglong
__darwin_ino_t :: alias c_ulonglong
__darwin_mach_port_name_t :: alias c_uint
__darwin_mach_port_t :: alias c_uint
__darwin_mode_t :: alias c_ushort
__darwin_off_t :: alias c_longlong
__darwin_pid_t :: alias c_int
__darwin_sigset_t :: alias c_uint
__darwin_suseconds_t :: alias c_int
__darwin_uid_t :: alias c_uint
__darwin_useconds_t :: alias c_uint
__darwin_uuid_t :: alias [16]c_uchar
__darwin_uuid_string_t :: alias [37]c_char
__darwin_pthread_attr_t :: alias _opaque_pthread_attr_t
__darwin_pthread_cond_t :: alias _opaque_pthread_cond_t
__darwin_pthread_condattr_t :: alias _opaque_pthread_condattr_t
__darwin_pthread_key_t :: alias c_ulong
__darwin_pthread_mutex_t :: alias _opaque_pthread_mutex_t
__darwin_pthread_mutexattr_t :: alias _opaque_pthread_mutexattr_t
__darwin_pthread_once_t :: alias _opaque_pthread_once_t
__darwin_pthread_rwlock_t :: alias _opaque_pthread_rwlock_t
__darwin_pthread_rwlockattr_t :: alias _opaque_pthread_rwlockattr_t
__darwin_pthread_t :: alias ?*mut _opaque_pthread_t
__darwin_nl_item :: alias c_int
__darwin_wctrans_t :: alias c_int
__darwin_wctype_t :: alias c_uint
int8_t :: alias c_schar
int16_t :: alias c_short
int32_t :: alias c_int
int64_t :: alias c_longlong
u_int8_t :: alias c_uchar
u_int16_t :: alias c_ushort
u_int32_t :: alias c_uint
u_int64_t :: alias c_ulonglong
register_t :: alias c_longlong
intptr_t :: alias c_long
uintptr_t :: alias c_ulong
user_addr_t :: alias c_ulonglong
user_size_t :: alias c_ulonglong
user_ssize_t :: alias c_longlong
user_long_t :: alias c_longlong
user_ulong_t :: alias c_ulonglong
user_time_t :: alias c_longlong
user_off_t :: alias c_longlong
syscall_arg_t :: alias c_ulonglong
va_list :: alias ?*mut c_char
size_t :: alias c_ulong
fpos_t :: alias c_longlong
FILE :: alias __sFILE
off_t :: alias c_longlong
ssize_t :: alias c_long
_DARWIN_FEATURE_64_BIT_INODE c_int :: 1
_DARWIN_FEATURE_ONLY_64_BIT_INODE c_int :: 1
_DARWIN_FEATURE_ONLY_VERS_1050 c_int :: 1
_DARWIN_FEATURE_ONLY_UNIX_CONFORMANCE c_int :: 1
_DARWIN_FEATURE_UNIX_CONFORMANCE c_int :: 3
_FORTIFY_SOURCE c_int :: 2
RENAME_SECLUDE c_int :: 1
RENAME_SWAP c_int :: 2
RENAME_EXCL c_int :: 4
RENAME_RESERVED1 c_int :: 8
RENAME_NOFOLLOW_ANY c_int :: 16
RENAME_RESOLVE_BENEATH c_int :: 32
SEEK_SET c_int :: 0
SEEK_CUR c_int :: 1
SEEK_END c_int :: 2
SEEK_HOLE c_int :: 3
SEEK_DATA c_int :: 4
_IOFBF c_int :: 0
_IOLBF c_int :: 1
_IONBF c_int :: 2
BUFSIZ c_int :: 1024
FOPEN_MAX c_int :: 20
FILENAME_MAX c_int :: 1024
L_tmpnam c_int :: 1024
TMP_MAX c_int :: 308915776
L_ctermid c_int :: 1024
_USE_FORTIFY_LEVEL c_int :: 2
renameat c_func(_ c_int, _ ?*c_char, _ c_int, _ ?*c_char) c_int
renamex_np c_func(_ ?*c_char, _ ?*c_char, _ c_uint) c_int
renameatx_np c_func(_ c_int, _ ?*c_char, _ c_int, _ ?*c_char, _ c_uint) c_int
printf c_func(_ ?*c_char, ...) c_int
clearerr c_func(_ ?*mut __sFILE) void
fclose c_func(_ ?*mut __sFILE) c_int
feof c_func(_ ?*mut __sFILE) c_int
ferror c_func(_ ?*mut __sFILE) c_int
fflush c_func(_ ?*mut __sFILE) c_int
fgetc c_func(_ ?*mut __sFILE) c_int
fgetpos c_func(_ ?*mut __sFILE, _ ?*mut c_longlong) c_int
fgets c_func(_ ?*mut c_char, __size c_int, _ ?*mut __sFILE) ?*mut c_char
fopen c_func(__filename ?*c_char, __mode ?*c_char) ?*mut __sFILE
fprintf c_func(_ ?*mut __sFILE, _ ?*c_char, ...) c_int
fputc c_func(_ c_int, _ ?*mut __sFILE) c_int
fputs c_func(_ ?*c_char, _ ?*mut __sFILE) c_int
fread c_func(__ptr ?*mut anyopaque, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong
freopen c_func(_ ?*c_char, _ ?*c_char, _ ?*mut __sFILE) ?*mut __sFILE
fscanf c_func(_ ?*mut __sFILE, _ ?*c_char, ...) c_int
fseek c_func(_ ?*mut __sFILE, _ c_long, _ c_int) c_int
fsetpos c_func(_ ?*mut __sFILE, _ ?*c_longlong) c_int
ftell c_func(_ ?*mut __sFILE) c_long
fwrite c_func(__ptr ?*anyopaque, __size c_ulong, __nitems c_ulong, __stream ?*mut __sFILE) c_ulong
getc c_func(_ ?*mut __sFILE) c_int
getchar c_func() c_int
gets c_func(_ ?*mut c_char) ?*mut c_char
perror c_func(_ ?*c_char) void
putc c_func(_ c_int, _ ?*mut __sFILE) c_int
putchar c_func(_ c_int) c_int
puts c_func(_ ?*c_char) c_int
remove c_func(_ ?*c_char) c_int
rename c_func(__old ?*c_char, __new ?*c_char) c_int
rewind c_func(_ ?*mut __sFILE) void
scanf c_func(_ ?*c_char, ...) c_int
setbuf c_func(_ ?*mut __sFILE, _ ?*mut c_char) void
setvbuf c_func(_ ?*mut __sFILE, _ ?*mut c_char, _ c_int, __size c_ulong) c_int
sprintf c_func(_ ?*mut c_char, _ ?*c_char, ...) c_int
sscanf c_func(_ ?*c_char, _ ?*c_char, ...) c_int
tmpfile c_func() ?*mut __sFILE
tmpnam c_func(_ ?*mut c_char) ?*mut c_char
ungetc c_func(_ c_int, _ ?*mut __sFILE) c_int
vfprintf c_func(_ ?*mut __sFILE, _ ?*c_char, _ ?*mut c_char) c_int
vprintf c_func(_ ?*c_char, _ ?*mut c_char) c_int
vsprintf c_func(_ ?*mut c_char, _ ?*c_char, _ ?*mut c_char) c_int
ctermid c_func(_ ?*mut c_char) ?*mut c_char
fdopen c_func(_ c_int, _ ?*c_char) ?*mut __sFILE
fileno c_func(_ ?*mut __sFILE) c_int
pclose c_func(_ ?*mut __sFILE) c_int
popen c_func(_ ?*c_char, _ ?*c_char) ?*mut __sFILE
__srget c_func(_ ?*mut __sFILE) c_int
__svfscanf c_func(_ ?*mut __sFILE, _ ?*c_char, _ ?*mut c_char) c_int
__swbuf c_func(_ c_int, _ ?*mut __sFILE) c_int
__sputc c_func(_c c_int, _p ?*mut __sFILE) c_int
flockfile c_func(_ ?*mut __sFILE) void
ftrylockfile c_func(_ ?*mut __sFILE) c_int
funlockfile c_func(_ ?*mut __sFILE) void
getc_unlocked c_func(_ ?*mut __sFILE) c_int
getchar_unlocked c_func() c_int
putc_unlocked c_func(_ c_int, _ ?*mut __sFILE) c_int
putchar_unlocked c_func(_ c_int) c_int
getw c_func(_ ?*mut __sFILE) c_int
putw c_func(_ c_int, _ ?*mut __sFILE) c_int
tempnam c_func(__dir ?*c_char, __prefix ?*c_char) ?*mut c_char
fseeko c_func(__stream ?*mut __sFILE, __offset c_longlong, __whence c_int) c_int
ftello c_func(__stream ?*mut __sFILE) c_longlong
snprintf c_func(__str ?*mut c_char, __size c_ulong, __format ?*c_char, ...) c_int
vfscanf c_func(__stream ?*mut __sFILE, __format ?*c_char, _ ?*mut c_char) c_int
vscanf c_func(__format ?*c_char, _ ?*mut c_char) c_int
vsnprintf c_func(__str ?*mut c_char, __size c_ulong, __format ?*c_char, _ ?*mut c_char) c_int
vsscanf c_func(__str ?*c_char, __format ?*c_char, _ ?*mut c_char) c_int
dprintf c_func(_ c_int, _ ?*c_char, ...) c_int
vdprintf c_func(_ c_int, _ ?*c_char, _ ?*mut c_char) c_int
getdelim c_func(__linep ?*mut ?*mut c_char, __linecapp ?*mut c_ulong, __delimiter c_int, __stream ?*mut __sFILE) c_long
getline c_func(__linep ?*mut ?*mut c_char, __linecapp ?*mut c_ulong, __stream ?*mut __sFILE) c_long
fmemopen c_func(__buf ?*mut anyopaque, __size c_ulong, __mode ?*c_char) ?*mut __sFILE
open_memstream c_func(__bufp ?*mut ?*mut c_char, __sizep ?*mut c_ulong) ?*mut __sFILE
asprintf c_func(_ ?*mut ?*mut c_char, _ ?*c_char, ...) c_int
ctermid_r c_func(_ ?*mut c_char) ?*mut c_char
fgetln c_func(_ ?*mut __sFILE, __len ?*mut c_ulong) ?*mut c_char
fmtcheck c_func(_ ?*c_char, _ ?*c_char) ?*c_char
fpurge c_func(_ ?*mut __sFILE) c_int
setbuffer c_func(_ ?*mut __sFILE, _ ?*mut c_char, __size c_int) void
setlinebuf c_func(_ ?*mut __sFILE) c_int
vasprintf c_func(_ ?*mut ?*mut c_char, _ ?*c_char, _ ?*mut c_char) c_int
funopen c_func(_ ?*anyopaque, _ ?*c_func(_ ?*mut anyopaque, _ ?*mut c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut anyopaque, _ ?*c_char, _ c_int) c_int, _ ?*c_func(_ ?*mut anyopaque, _ c_longlong, _ c_int) c_longlong, _ ?*c_func(_ ?*mut anyopaque) c_int) ?*mut __sFILE
__snprintf_chk c_func(_ ?*mut c_char, __maxlen c_ulong, _ c_int, _ c_ulong, _ ?*c_char, ...) c_int
__vsnprintf_chk c_func(_ ?*mut c_char, __maxlen c_ulong, _ c_int, _ c_ulong, _ ?*c_char, _ ?*mut c_char) c_int
__sprintf_chk c_func(_ ?*mut c_char, _ c_int, _ c_ulong, _ ?*c_char, ...) c_int
__vsprintf_chk c_func(_ ?*mut c_char, _ c_int, _ c_ulong, _ ?*c_char, _ ?*mut c_char) c_int
# unsupported in bindings: external variable '__stdinp' has no native spelling
# unsupported in bindings: external variable '__stdoutp' has no native spelling
# unsupported in bindings: external variable '__stderrp' has no native spelling
# unsupported in bindings: external variable 'sys_nerr' has no native spelling
# unsupported in bindings: external variable 'sys_errlist' has no native spelling
# unsupported in bindings: _STDIO_H_ — C macro has no replacement value
# unsupported in bindings: _LIBC_BOUNDS_H_ — C macro has no replacement value
# unsupported in bindings: _CDEFS_H_ — C macro has no replacement value
# unsupported in bindings: _LIBC_COUNT — C function-like macros are not supported
# unsupported in bindings: _LIBC_COUNT_OR_NULL — C function-like macros are not supported
# unsupported in bindings: _LIBC_SIZE — C function-like macros are not supported
# unsupported in bindings: _LIBC_SIZE_OR_NULL — C function-like macros are not supported
# unsupported in bindings: _LIBC_ENDED_BY — C function-like macros are not supported
# unsupported in bindings: _LIBC_SINGLE — C macro has no replacement value
# unsupported in bindings: _LIBC_UNSAFE_INDEXABLE — C macro has no replacement value
# unsupported in bindings: _LIBC_CSTR — C macro has no replacement value
# unsupported in bindings: _LIBC_NULL_TERMINATED — C macro has no replacement value
# unsupported in bindings: _LIBC_FLEX_COUNT — C function-like macros are not supported
# unsupported in bindings: _LIBC_SINGLE_BY_DEFAULT — C function-like macros are not supported
# unsupported in bindings: _LIBC_PTRCHECK_REPLACED — C function-like macros are not supported
# unsupported in bindings: _LIBC_FORGE_PTR — C function-like macros are not supported
# unsupported in bindings: MAC_OS_X_VERSION_10_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_8 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_9 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_10 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_10_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_10_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_11_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_12_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_13_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_14_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_15 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_15_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_15_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_X_VERSION_10_16 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_11_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_12_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_13_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_14_7 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_4 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_5 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_15_6 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_16_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_0 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_1 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_2 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_3 — C macro is not a supported constant
# unsupported in bindings: MAC_OS_VERSION_26_4 — C macro is not a supported constant
# unsupported in bindings: _SYS__TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _BSD_MACHINE__TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _BSD_ARM__TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _SYS__PTHREAD_TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _VA_LIST_T — C macro has no replacement value
# unsupported in bindings: _BSD_MACHINE_TYPES_H_ — C macro has no replacement value
# unsupported in bindings: _ARM_MACHTYPES_H_ — C macro has no replacement value
# unsupported in bindings: _MACHTYPES_H_ — C macro has no replacement value
# unsupported in bindings: _INT8_T — C macro has no replacement value
# unsupported in bindings: _INT16_T — C macro has no replacement value
# unsupported in bindings: _INT32_T — C macro has no replacement value
# unsupported in bindings: _INT64_T — C macro has no replacement value
# unsupported in bindings: _U_INT8_T — C macro has no replacement value
# unsupported in bindings: _U_INT16_T — C macro has no replacement value
# unsupported in bindings: _U_INT32_T — C macro has no replacement value
# unsupported in bindings: _U_INT64_T — C macro has no replacement value
# unsupported in bindings: _INTPTR_T — C macro has no replacement value
# unsupported in bindings: _UINTPTR_T — C macro has no replacement value
# unsupported in bindings: USER_ADDR_NULL — C macro is not a supported constant
# unsupported in bindings: CAST_USER_ADDR_T — C function-like macros are not supported
# unsupported in bindings: _SIZE_T — C macro has no replacement value
# unsupported in bindings: NULL — C macro is not a supported constant
# unsupported in bindings: _SYS_STDIO_H_ — C macro has no replacement value
# unsupported in bindings: _FSTDIO — C macro has no replacement value
# unsupported in bindings: _SEEK_SET_H_ — C macro has no replacement value
# unsupported in bindings: EOF — C macro is not a supported constant
# unsupported in bindings: P_tmpdir — C macro is not a supported constant
# unsupported in bindings: stdin — C macro is not a supported constant
# unsupported in bindings: stdout — C macro is not a supported constant
# unsupported in bindings: stderr — C macro is not a supported constant
# unsupported in bindings: _LIBC_COUNT__L_CTERMID — C macro is not a supported constant
# unsupported in bindings: _CTERMID_H_ — C macro has no replacement value
# unsupported in bindings: getc_unlocked — C function-like macros are not supported
# unsupported in bindings: putc_unlocked — C function-like macros are not supported
# unsupported in bindings: getchar_unlocked — C function-like macros are not supported
# unsupported in bindings: putchar_unlocked — C function-like macros are not supported
# unsupported in bindings: _OFF_T — C macro has no replacement value
# unsupported in bindings: _SSIZE_T — C macro has no replacement value
# unsupported in bindings: fropen — C function-like macros are not supported
# unsupported in bindings: fwopen — C function-like macros are not supported
# unsupported in bindings: feof_unlocked — C function-like macros are not supported
# unsupported in bindings: ferror_unlocked — C function-like macros are not supported
# unsupported in bindings: clearerr_unlocked — C function-like macros are not supported
# unsupported in bindings: fileno_unlocked — C function-like macros are not supported
# unsupported in bindings: _SECURE__STDIO_H_ — C macro has no replacement value
# unsupported in bindings: _SECURE__COMMON_H_ — C macro has no replacement value
# unsupported in bindings: sprintf — C function-like macros are not supported
# unsupported in bindings: vsprintf — C function-like macros are not supported
# unsupported in bindings: snprintf — C function-like macros are not supported
# unsupported in bindings: vsnprintf — C function-like macros are not supported
+4
View File
@@ -0,0 +1,4 @@
malloc c_func(__size c_ulong) ?*mut anyopaque
realloc c_func(__ptr ?*mut anyopaque, __size c_ulong) ?*mut anyopaque
free c_func(_ ?*mut anyopaque) void
posix_memalign c_func(__memptr ?*mut ?*mut anyopaque, __alignment c_ulong, __size c_ulong) c_int
+155
View File
@@ -0,0 +1,155 @@
import "@ffi/c"
import "@std"
import "@std/mem"
import "@std/arraylist"
Kind :: enum(u8) {
invalid
eof
newline
identifier
keyword
integer
string
punctuation
}
Token :: struct {
start usize
length usize
kind Kind
}
_is_alpha func(value u8) bool {
return value == '_' or
value >= 'a' and value <= 'z' or
value >= 'A' and value <= 'Z'
}
_is_digit func(value u8) bool {
return value >= '0' and value <= '9'
}
_word_kind func(word []u8) Kind {
# ponytail: enough keywords for the demo; add the full language set when a parser needs it.
if mem.eql(word, "func") or mem.eql(word, "void") {
return .keyword
}
return .identifier
}
_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,
kind = kind,
})
return _
}
lex func(source []u8, tokens @mut std.ArrayList(Token)) void ! mem.AllocError {
cursor usize = 0
while cursor < source.len {
value u8 :: source[cursor]
if value == ' ' or value == '\t' or value == '\r' {
cursor += 1
} else if value == '\n' {
try _append(tokens, .newline, cursor, cursor + 1)
cursor += 1
} else if value == '#' {
while cursor < source.len and source[cursor] != '\n' : cursor += 1 {}
} else if _is_alpha(value) {
start usize :: cursor
cursor += 1
while cursor < source.len and (_is_alpha(source[cursor]) or _is_digit(source[cursor])) : cursor += 1 {}
try _append(tokens, _word_kind(source[start..cursor]), start, cursor)
} else if _is_digit(value) {
start usize :: cursor
while cursor < source.len and _is_digit(source[cursor]) : cursor += 1 {}
try _append(tokens, .integer, start, cursor)
} else if value == '"' {
start usize :: cursor
cursor += 1
while cursor < source.len and source[cursor] != '"' and source[cursor] != '\n' {
if source[cursor] == '\\' and cursor + 1 < source.len {
cursor += 1
}
cursor += 1
}
if cursor < source.len and source[cursor] == '"' {
cursor += 1
try _append(tokens, .string, start, cursor)
} else {
try _append(tokens, .invalid, start, cursor)
}
} else {
start usize :: cursor
cursor += 1
if value == ':' and cursor < source.len and source[cursor] == ':' {
cursor += 1
}
try _append(tokens, .punctuation, start, cursor)
}
}
try _append(tokens, .eof, cursor, cursor)
return _
}
_kind_name func(kind Kind) *c_char {
return match kind {
.invalid: "invalid"
.eof: "eof"
.newline: "newline"
.identifier: "identifier"
.keyword: "keyword"
.integer: "integer"
.string: "string"
.punctuation: "punctuation"
}
}
_print_token func(source []u8, token Token) void {
_ = c.printf("%-11s", _kind_name(token.kind))
if token.length != 0 {
_ = c.printf(" `")
i usize = 0
while i < token.length : i += 1 {
value u8 :: source[token.start + i]
if value == '\n' {
_ = c.printf("\\n")
} else {
_ = c.putchar(c_int(value))
}
}
_ = c.putchar('`')
}
_ = c.putchar('\n')
}
main func() i32 {
source ::
`main func() void {
` hello()
`}
tokens std.ArrayList(Token) = arraylist.init(mem.c_allocator)
defer arraylist.deinit(&tokens)
lex(source, &tokens) catch |_| {
_ = c.printf("out of memory\n")
return 1
}
# Small executable self-check for the lexer and ArrayList path.
if (tokens.items.len != 13 or
tokens.items[0].kind != .identifier or
tokens.items[1].kind != .keyword or
tokens.items[12].kind != .eof) {
return 2
}
for tokens.items |token| {
_print_token(source, token)
}
return 0
}
+67
View File
@@ -0,0 +1,67 @@
mem :: import "@std/mem"
ArrayList func($T type) type {
return struct {
items []mut T
capacity usize
allocator mem.Allocator
}
}
init func($T type, allocator mem.Allocator) ArrayList(T) {
return ArrayList(T) {
items = mem.empty(T),
capacity = 0,
allocator = allocator,
}
}
deinit func($T type, list @mut ArrayList(T)) void {
allocation []mut T :: list.items.ptr[..list.capacity]
mem.free(list.allocator, allocation)
list.items = mem.empty(T)
list.capacity = 0
}
reserve func($T type, list @mut ArrayList(T), minimum_capacity usize) void ! mem.AllocError {
if minimum_capacity <= list.capacity {
return _
}
new_capacity usize = 8
if list.capacity >= 8 {
half usize :: div_trunc(list.capacity, 2)
if list.capacity > max_value(usize) - half {
new_capacity = minimum_capacity
} else {
new_capacity = list.capacity + half
}
}
if new_capacity < minimum_capacity {
new_capacity = minimum_capacity
}
length usize :: list.items.len
allocation []mut T :: list.items.ptr[..list.capacity]
grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
return .out_of_memory
}
list.items = grown.ptr[..length]
list.capacity = new_capacity
return _
}
append func($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
length usize :: list.items.len
if length == max_value(usize) {
return .out_of_memory
}
try reserve(list, length + 1)
list.items = list.items.ptr[..length + 1]
list.items[length] = value
return _
}
clear func($T type, list @mut ArrayList(T)) void {
list.items = list.items.ptr[..0]
}
+17
View File
@@ -0,0 +1,17 @@
# Build configuration surface for `brolang build` (v0).
#
# A project's `build.bro` imports this module and declares a top-level constant
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
# build.bro, reads the config, and writes root/build/name.
#
# Declarative and literal-only: one executable per build. List fields take an
# address-of an array literal (`&["raylib"]`); empty lists are written `&[]`.
BuildConfig :: struct {
name []u8 # output executable name under root/build
source []u8 # program package directory, relative to build.bro
libraries [][]u8 # library names to link (-l)
lib_paths [][]u8 # library search directories (-L)
includes [][]u8 # C include directories (-I)
defines [][]u8 # C preprocessor defines (name or name=value)
links [][]u8 # extra linker inputs (object/source files, -framework pairs)
}
+122
View File
@@ -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,
}
}
+202
View File
@@ -0,0 +1,202 @@
c :: import "@ffi/c"
AllocError :: enum {
out_of_memory
}
Allocator :: struct {
context ?*mut anyopaque
vtable @AllocatorVTable
}
AllocatorVTable :: struct {
alloc @func(context ?*mut anyopaque, size usize, alignment usize) ?*mut u8
realloc @func(context ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8
free @func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
}
raw_alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
return allocator.vtable.alloc(allocator.context, size, alignment)
}
raw_realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment)
}
raw_free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
allocator.vtable.free(allocator.context, memory, size, alignment)
}
eql func($T type, left, right []T) bool {
if left.len != right.len {
return false
}
i usize = 0
while i < left.len : i += 1 {
if left[i] != right[i] {
return false
}
}
return true
}
_empty_storage [1]mut u64 = [0]
_empty_slice func($T type, count usize) []mut T {
pointer *mut T :: ptr_cast(T, (&_empty_storage).ptr)
return pointer[..count]
}
empty func($T type) []mut T {
return _empty_slice(T, 0)
}
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
if count == 0 {
return _empty_slice(T, 0)
}
element_size usize :: size_of(T)
if element_size == 0 {
return _empty_slice(T, count)
}
if count > div_trunc(max_value(usize), element_size) {
return .out_of_memory
}
memory ?*mut u8 = raw_alloc(allocator, count * element_size, align_of(T))
if memory |bytes| {
pointer *mut T :: ptr_cast(T, bytes)
return pointer[..count]
}
return .out_of_memory
}
realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
if new_count == memory.len {
return memory
}
if new_count == 0 {
free(allocator, memory)
return _empty_slice(T, 0)
}
element_size usize :: size_of(T)
if element_size == 0 {
return _empty_slice(T, new_count)
}
if new_count > div_trunc(max_value(usize), element_size) {
return .out_of_memory
}
old_memory ?*mut u8 = none
old_size usize = 0
if memory.len != 0 {
old_memory = ptr_cast(u8, memory.ptr)
old_size = memory.len * element_size
}
resized ?*mut u8 = raw_realloc(
allocator,
old_memory,
old_size,
new_count * element_size,
align_of(T),
)
if resized |bytes| {
pointer *mut T :: ptr_cast(T, bytes)
return pointer[..new_count]
}
return .out_of_memory
}
free func($T type, allocator Allocator, memory []mut T) void {
if memory.len != 0 and size_of(T) != 0 {
raw_free(allocator, ptr_cast(u8, memory.ptr), memory.len * size_of(T), align_of(T))
}
}
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
_power_of_two func(value usize) bool {
if value == 0 {
return false
}
current usize = value
while current > 1 {
half usize = div_trunc(current, 2)
if half * 2 != current {
return false
}
current = half
}
return true
}
_c_alloc func(_ ?*mut anyopaque, size usize, alignment usize) ?*mut u8 {
if _power_of_two(alignment) == false {
return none
}
if alignment <= _malloc_alignment {
return ptr_cast(u8, c.malloc(c_ulong(size)))
}
memory [1]mut ?*mut anyopaque = [none]
status c_int = c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
if status != 0 {
return none
}
return ptr_cast(u8, memory[0])
}
_c_realloc func(_ ?*mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
if _power_of_two(alignment) == false {
return none
}
if new_size == 0 {
c.free(memory)
return none
}
if memory |old_memory| {
if alignment <= _malloc_alignment {
return ptr_cast(u8, c.realloc(old_memory, c_ulong(new_size)))
}
new_memory ?*mut u8 = _c_alloc(none, new_size, alignment)
if new_memory |new_bytes| {
copy_size usize = old_size
if new_size < copy_size {
copy_size = new_size
}
i usize = 0
while i < copy_size : i += 1 {
new_bytes[i] = old_memory[i]
}
c.free(old_memory)
}
return new_memory
}
return _c_alloc(none, new_size, alignment)
}
_c_free func(_ ?*mut anyopaque, memory ?*mut u8, _ usize, _ usize) void {
c.free(memory)
}
_c_vtable AllocatorVTable :: AllocatorVTable {
alloc = _c_alloc,
realloc = _c_realloc,
free = _c_free,
}
c_allocator Allocator :: Allocator {
context = none,
vtable = &_c_vtable,
}
+3
View File
@@ -0,0 +1,3 @@
import "arraylist"
ArrayList :: alias arraylist.ArrayList
+594
View File
@@ -0,0 +1,594 @@
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
const PREC = {
RANGE: 1,
FALLBACK: 2,
OR: 3,
AND: 4,
COMPARE: 5,
SUM: 6,
PRODUCT: 7,
PREFIX: 8,
POSTFIX: 9,
};
module.exports = grammar({
name: 'brolang',
word: $ => $.identifier,
extras: $ => [/[ \t\r]/, $.comment],
conflicts: $ => [
[$.expression, $.qualified_identifier],
[$.constant_declaration, $.variable_declaration, $.expression],
[$.function_declaration],
[$.if_statement],
[$.enum_literal],
[$.array_type],
[$.array_type, $.expression],
[$.array_type, $.array_literal],
[$.expression_statement, $.parenthesized_expression],
],
rules: {
source_file: $ => repeat(choice($._newline, $._top_level_declaration)),
_top_level_declaration: $ => choice(
$.import_declaration,
$.function_declaration,
$.type_declaration,
$.global_constant_declaration,
$.global_variable_declaration,
),
import_declaration: $ => seq(
optional(seq(field('alias', $.identifier), '::', repeat($._newline))),
'import',
repeat($._newline),
field('path', $.string),
),
function_declaration: $ => seq(
field('name', $.identifier),
field('kind', choice('func', 'c_func')),
field('parameters', $.parameter_list),
repeat($._newline),
field('result', $.type),
optional(seq('!', field('error', $._error_type))),
optional(choice(
field('body', $.block),
seq(repeat1($._newline), field('body', $.block)),
)),
),
type_declaration: $ => prec(1, seq(
field('name', $.identifier),
'::',
repeat($._newline),
field('value', choice(
$.struct_type,
$.c_struct_type,
$.union_type,
$.enum_type,
$.opaque_type,
$.distinct_type,
$.alias_type,
)),
)),
global_constant_declaration: $ => seq(
field('name', $.identifier),
optional(field('type', $.type)),
'::',
repeat($._newline),
field('value', $._value),
),
global_variable_declaration: $ => seq(
field('name', $.identifier),
optional(field('type', $.type)),
'=',
repeat($._newline),
field('value', $._value),
),
struct_type: $ => seq('struct', repeat($._newline), $.record_body),
c_struct_type: $ => seq('c_struct', repeat($._newline), $.record_body),
opaque_type: _ => 'opaque',
distinct_type: $ => seq('distinct', field('type', $.type)),
alias_type: $ => seq('alias', field('type', $.type)),
union_type: $ => seq(
'union',
optional(seq('(', choice('enum', $.type), ')')),
repeat($._newline),
$.record_body,
),
enum_type: $ => seq(
'enum',
optional(seq('(', field('backing', $.type), ')')),
repeat($._newline),
$.enum_body,
),
record_body: $ => seq(
'{',
repeat(choice($._newline, seq($.record_field, optional(',')))),
'}',
),
record_field: $ => seq(
field('name', $.identifier),
field('type', choice($.type, $.struct_type)),
),
enum_body: $ => seq(
'{',
repeat(choice($._newline, seq($.enum_member, optional(',')))),
'}',
),
enum_member: $ => seq(
field('name', $.identifier),
optional(seq('=', optional('-'), field('value', $.integer))),
),
parameter_list: $ => seq(
'(',
commaSep($, choice($.parameter, '...')),
')',
),
parameter: $ => seq(
optional('$'),
field('name', choice($.identifier, $.sink)),
repeat(seq(',', repeat($._newline), field('name', choice($.identifier, $.sink)))),
field('type', $.type),
),
type: $ => prec.left(seq(
$._type_atom,
repeat(seq('|', $._type_atom)),
)),
_type_atom: $ => choice(
$.builtin_type,
$.named_type,
$.optional_type,
$.pointer_type,
$.array_type,
$.function_type,
),
named_type: $ => prec.right(seq(
$.qualified_identifier,
optional($.argument_list),
)),
optional_type: $ => seq('?', $.type),
pointer_type: $ => seq(
choice('@', '*'),
optional('mut'),
$.type,
),
array_type: $ => seq(
'[',
repeat($._newline),
optional(choice(
seq('*', ';', field('sentinel', $._type_constant)),
seq(';', field('sentinel', $._type_constant)),
seq(
field('length', choice($.sink, $.expression)),
optional(seq(';', field('sentinel', $._type_constant))),
),
)),
repeat($._newline),
']',
optional('mut'),
field('element', $.type),
),
function_type: $ => prec.right(seq(
choice('func', 'c_func'),
$.parameter_list,
repeat($._newline),
field('result', $.type),
optional(seq('!', field('error', $._error_type))),
)),
_error_type: $ => choice($.type, $.enum_type, $.union_type),
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
builtin_type: _ => choice(
'void', 'anyopaque', 'bool', 'int', 'float', 'range',
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
'isize', 'usize', 'f32', 'f64',
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
'c_int', 'c_uint', 'c_long', 'c_ulong', 'c_longlong',
'c_ulonglong', 'c_float', 'c_double', 'c_longdouble',
),
block: $ => seq(
'{',
repeat(choice($._newline, $.statement)),
'}',
),
statement: $ => choice(
$.constant_declaration,
$.variable_declaration,
$.assignment_statement,
$.return_statement,
$.yield_statement,
$.if_statement,
$.while_statement,
$.for_statement,
$.match_statement,
$.break_statement,
$.continue_statement,
$.defer_statement,
$.labeled_block,
$.block,
$.expression_statement,
),
constant_declaration: $ => seq(
field('name', choice($.identifier, $.sink)),
optional(field('type', $.type)),
'::',
repeat($._newline),
field('value', $._value),
),
variable_declaration: $ => seq(
field('name', choice($.identifier, $.sink)),
field('type', $.type),
'=',
repeat($._newline),
field('value', $._value),
),
assignment_statement: $ => seq(
field('left', $.expression),
field('operator', choice('=', '+=', '-=', '*=', '/=')),
repeat($._newline),
field('right', $._value),
),
expression_statement: $ => $.expression,
return_statement: $ => seq(
'return',
repeat($._newline),
field('value', $._value),
),
yield_statement: $ => seq(
'yield',
repeat($._newline),
optional(seq(':', field('label', $.identifier))),
field('value', $._value),
),
break_statement: $ => seq('break', optional(seq(':', field('label', $.identifier)))),
continue_statement: $ => seq('continue', optional(seq(':', field('label', $.identifier)))),
defer_statement: $ => seq('defer', repeat($._newline), field('body', $.statement)),
labeled_block: $ => seq(
field('label', $.identifier),
':',
repeat($._newline),
$.block,
),
if_statement: $ => seq(
'if',
repeat($._newline),
field('condition', $.expression),
optional($.capture_list),
repeat($._newline),
field('consequence', $._branch_body),
optional(seq(
repeat($._newline),
'else',
repeat($._newline),
field('alternative', $._branch_body),
)),
),
capture_list: $ => seq(
'|',
commaSep1($, choice($.identifier, $.sink)),
optional(seq(':', field('guard', $.expression))),
'|',
),
while_statement: $ => seq(
'while',
repeat($._newline),
field('condition', $.expression),
optional(seq(':', repeat($._newline), field('update', choice(
$.assignment_statement,
$.expression_statement,
seq('(', repeat($._newline), choice($.assignment_statement, $.expression_statement), repeat($._newline), ')'),
)))),
repeat($._newline),
optional(seq(field('label', $.identifier), ':', repeat($._newline))),
field('body', $.block),
),
for_statement: $ => seq(
'for',
repeat($._newline),
field('iterable', $.expression),
repeat($._newline),
'|',
optional('@'),
field('item', $.identifier),
optional(seq(',', field('index', $.identifier))),
'|',
repeat($._newline),
optional(seq(field('label', $.identifier), ':', repeat($._newline))),
field('body', $.block),
),
match_statement: $ => seq(
'match',
repeat($._newline),
field('subject', $.expression),
repeat($._newline),
'{',
repeat(choice($._newline, $.match_arm)),
'}',
),
match_arm: $ => seq(
field('pattern', choice('else', commaSep1($, $.expression))),
optional($.match_capture),
':',
repeat($._newline),
field('body', $._branch_body),
),
match_capture: $ => seq('|', optional('@'), field('name', choice($.identifier, $.sink)), '|'),
_branch_body: $ => $.statement,
_value: $ => choice(
$.labeled_block,
$.block,
$.if_statement,
$.while_statement,
$.for_statement,
$.match_statement,
$.expression,
),
expression: $ => choice(
$.binary_expression,
$.catch_expression,
$.unary_expression,
$.field_expression,
$.call_expression,
$.index_expression,
$.slice_expression,
$.postfix_expression,
$.struct_literal,
$.comptime_block,
$.function_literal,
$.struct_type,
$.array_type,
$.enum_literal,
$.array_literal,
$.parenthesized_expression,
$.identifier,
$.sink,
$.builtin_type,
$.integer,
$.float,
$.string,
$.multiline_string,
$.character,
$.boolean,
$.none,
$.undefined,
),
binary_expression: $ => choice(
prec.left(PREC.RANGE, seq(field('left', $.expression), field('operator', choice('..', '..=')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.FALLBACK, seq(field('left', $.expression), field('operator', choice('orelse', 'catch')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.OR, seq(field('left', $.expression), 'or', repeat($._newline), field('right', $.expression))),
prec.left(PREC.AND, seq(field('left', $.expression), 'and', repeat($._newline), field('right', $.expression))),
prec.left(PREC.COMPARE, seq(field('left', $.expression), field('operator', choice('==', '!=', '<', '<=', '>', '>=')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.SUM, seq(field('left', $.expression), field('operator', choice('+', '-')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.PRODUCT, seq(field('left', $.expression), field('operator', choice('*', '/')), repeat($._newline), field('right', $.expression))),
),
catch_expression: $ => prec.left(PREC.FALLBACK, seq(
field('value', $.expression),
'catch',
'|',
field('name', choice($.identifier, $.sink)),
'|',
repeat($._newline),
field('body', $.block),
)),
unary_expression: $ => prec(PREC.PREFIX, seq(
field('operator', choice('-', '&', '!', '$', 'try')),
repeat($._newline),
field('operand', $.expression),
)),
field_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'.',
field('field', $.identifier),
)),
call_expression: $ => prec.left(PREC.POSTFIX, seq(
field('function', $.expression),
field('arguments', $.argument_list),
)),
argument_list: $ => prec(PREC.POSTFIX, seq('(', commaSep($, $.expression), ')')),
index_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'[',
repeat($._newline),
field('index', $.expression),
repeat($._newline),
']',
)),
slice_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'[',
repeat($._newline),
optional(field('start', $.expression)),
'..',
optional(field('end', $.expression)),
repeat($._newline),
']',
)),
postfix_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
field('operator', choice('?', '^')),
)),
struct_literal: $ => prec(PREC.POSTFIX, seq(
field('type', $.qualified_identifier),
optional($.argument_list),
field('fields', $.initializer_list),
)),
initializer_list: $ => seq(
'{',
choice(
repeat($._newline),
seq(
repeat($._newline),
$.field_initializer,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.field_initializer)),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
),
),
'}',
),
field_initializer: $ => seq(
field('name', $.identifier),
optional(seq('=', repeat($._newline), field('value', $.expression))),
),
enum_literal: $ => seq(
'.',
field('name', $.identifier),
optional($.variant_payload),
),
variant_payload: $ => seq(
'{',
choice(
repeat($._newline),
seq(
repeat($._newline),
choice(
$.expression,
seq(
$.keyed_field_initializer,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.keyed_field_initializer)),
optional(seq(repeat($._newline), ',')),
),
),
repeat($._newline),
),
),
'}',
),
keyed_field_initializer: $ => seq(
field('name', $.identifier),
'=',
repeat($._newline),
field('value', $.expression),
),
array_literal: $ => seq('[', commaSep($, $.expression), ']'),
parenthesized_expression: $ => seq(
'(',
repeat($._newline),
$.expression,
repeat($._newline),
')',
),
comptime_block: $ => seq('$', repeat($._newline), $.block),
function_literal: $ => seq(
'func',
$.parameter_list,
repeat($._newline),
field('result', $.type),
optional(seq('!', field('error', $._error_type))),
repeat($._newline),
field('body', $.block),
),
qualified_identifier: $ => prec.right(seq(
field('qualifier', $.identifier),
optional(seq('.', field('name', $.identifier))),
)),
boolean: _ => choice('true', 'false'),
none: _ => 'none',
undefined: _ => 'undefined',
sink: _ => '_',
identifier: _ => /[A-Za-z_][A-Za-z0-9_]*/,
integer: _ => /[0-9]+/,
float: _ => token(prec(1, /[0-9]+\.[0-9]+/)),
string: $ => seq(
'"',
repeat(choice($.string_content, $.escape_sequence)),
'"',
),
string_content: _ => token.immediate(prec(1, /[^"\\\n]+/)),
escape_sequence: _ => token.immediate(/\\(?:\\|"|n|r|t|0)/),
multiline_string: _ => token(/`[^\n]*(?:\n[ \t]*`[^\n]*)*/),
character: _ => token(/'(?:[^'\\\n]|\\(?:\\|'|n|r|t|0))'/),
comment: _ => token(seq('#', /[^\n]*/)),
_newline: _ => /\n/,
},
});
function commaSep($, rule) {
return choice(
repeat($._newline),
seq(
repeat($._newline),
rule,
repeat(seq(repeat($._newline), ',', repeat($._newline), rule)),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
),
);
}
function commaSep1($, rule) {
return seq(
rule,
repeat(seq(repeat($._newline), ',', repeat($._newline), rule)),
);
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "tree-sitter-brolang",
"version": "0.1.0",
"private": true,
"scripts": {
"generate": "tree-sitter generate",
"test": "tree-sitter test"
}
}
+110
View File
@@ -0,0 +1,110 @@
(comment) @comment
[
(string)
(multiline_string)
] @string
(character) @string
(escape_sequence) @escape
[
(integer)
(float)
] @number
(boolean) @constant.builtin
[
(none)
(undefined)
] @constant.builtin
(builtin_type) @type.builtin
(named_type) @type
(type_declaration name: (identifier) @type)
(function_declaration name: (identifier) @function)
(parameter name: (identifier) @variable.parameter)
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
(field_initializer name: (identifier) @property)
(keyed_field_initializer name: (identifier) @property)
(record_field name: (identifier) @property)
(enum_member name: (identifier) @property)
(enum_literal name: (identifier) @property)
(import_declaration alias: (identifier) @module)
(opaque_type) @keyword
[
"func"
"c_func"
"struct"
"c_struct"
"union"
"enum"
"distinct"
"alias"
"import"
"return"
"try"
"catch"
"mut"
"orelse"
"and"
"or"
"if"
"while"
"for"
"break"
"continue"
"defer"
"yield"
"match"
"else"
] @keyword
[
"::"
"="
"+="
"-="
"*="
"/="
"=="
"!="
"<"
"<="
">"
">="
"+"
"-"
"*"
"/"
"!"
"&"
"?"
"^"
".."
"..="
"|"
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
","
"."
":"
";"
] @punctuation.delimiter
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
#ifndef TREE_SITTER_ALLOC_H_
#define TREE_SITTER_ALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
// Allow clients to override allocation functions
#ifdef TREE_SITTER_REUSE_ALLOCATOR
extern void *(*ts_current_malloc)(size_t size);
extern void *(*ts_current_calloc)(size_t count, size_t size);
extern void *(*ts_current_realloc)(void *ptr, size_t size);
extern void (*ts_current_free)(void *ptr);
#ifndef ts_malloc
#define ts_malloc ts_current_malloc
#endif
#ifndef ts_calloc
#define ts_calloc ts_current_calloc
#endif
#ifndef ts_realloc
#define ts_realloc ts_current_realloc
#endif
#ifndef ts_free
#define ts_free ts_current_free
#endif
#else
#ifndef ts_malloc
#define ts_malloc malloc
#endif
#ifndef ts_calloc
#define ts_calloc calloc
#endif
#ifndef ts_realloc
#define ts_realloc realloc
#endif
#ifndef ts_free
#define ts_free free
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ALLOC_H_
+347
View File
@@ -0,0 +1,347 @@
#ifndef TREE_SITTER_ARRAY_H_
#define TREE_SITTER_ARRAY_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "./alloc.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#define Array(T) \
struct { \
T *contents; \
uint32_t size; \
uint32_t capacity; \
}
/// Initialize an array.
#define array_init(self) \
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
/// Create an empty array.
#define array_new() \
{ NULL, 0, 0 }
/// Get a pointer to the element at a given `index` in the array.
#define array_get(self, _index) \
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
/// Get a pointer to the first element in the array.
#define array_front(self) array_get(self, 0)
/// Get a pointer to the last element in the array.
#define array_back(self) array_get(self, (self)->size - 1)
/// Clear the array, setting its size to zero. Note that this does not free any
/// memory allocated for the array's contents.
#define array_clear(self) ((self)->size = 0)
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
/// less than the array's current capacity, this function has no effect.
#define array_reserve(self, new_capacity) \
((self)->contents = _array__reserve( \
(void *)(self)->contents, &(self)->capacity, \
array_elem_size(self), new_capacity) \
)
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) _array__delete((self), (void *)(self)->contents, sizeof(*self))
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
do { \
(self)->contents = _array__grow( \
(void *)(self)->contents, (self)->size, &(self)->capacity, \
1, array_elem_size(self) \
); \
(self)->contents[(self)->size++] = (element); \
} while(0)
/// Increase the array's size by `count` elements.
/// New elements are zero-initialized.
#define array_grow_by(self, count) \
do { \
if ((count) == 0) break; \
(self)->contents = _array__grow( \
(self)->contents, (self)->size, &(self)->capacity, \
count, array_elem_size(self) \
); \
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
(self)->size += (count); \
} while (0)
/// Append all elements from one array to the end of another.
#define array_push_all(self, other) \
array_extend((self), (other)->size, (other)->contents)
/// Append `count` elements to the end of the array, reading their values from the
/// `contents` pointer.
#define array_extend(self, count, other_contents) \
(self)->contents = _array__splice( \
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), (self)->size, 0, count, other_contents \
)
/// Remove `old_count` elements from the array starting at the given `index`. At
/// the same index, insert `new_count` new elements, reading their values from the
/// `new_contents` pointer.
#define array_splice(self, _index, old_count, new_count, new_contents) \
(self)->contents = _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, old_count, new_count, new_contents \
)
/// Insert one `element` into the array at the given `index`.
#define array_insert(self, _index, element) \
(self)->contents = _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, 0, 1, &(element) \
)
/// Remove one element from the array at the given `index`.
#define array_erase(self, _index) \
_array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index)
/// Pop the last element off the array, returning the element by value.
#define array_pop(self) ((self)->contents[--(self)->size])
/// Assign the contents of one array to another, reallocating if necessary.
#define array_assign(self, other) \
(self)->contents = _array__assign( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
)
/// Swap one array with another
#define array_swap(self, other) \
do { \
struct Swap swapped_contents = _array__swap( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(void *)(other)->contents, &(other)->size, &(other)->capacity \
); \
(self)->contents = swapped_contents.self_contents; \
(other)->contents = swapped_contents.other_contents; \
} while (0)
/// Get the size of the array contents
#define array_elem_size(self) (sizeof *(self)->contents)
/// Search a sorted array for a given `needle` value, using the given `compare`
/// callback to determine the order.
///
/// If an existing element is found to be equal to `needle`, then the `index`
/// out-parameter is set to the existing value's index, and the `exists`
/// out-parameter is set to true. Otherwise, `index` is set to an index where
/// `needle` should be inserted in order to preserve the sorting, and `exists`
/// is set to false.
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
/// Search a sorted array for a given `needle` value, using integer comparisons
/// of a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_with`.
#define array_search_sorted_by(self, field, needle, _index, _exists) \
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
/// Insert a given `value` into a sorted array, using the given `compare`
/// callback to determine the order.
#define array_insert_sorted_with(self, compare, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
/// Insert a given `value` into a sorted array, using integer comparisons of
/// a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_by`.
#define array_insert_sorted_by(self, field, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
// Private
// Pointers to individual `Array` fields (rather than the entire `Array` itself)
// are passed to the various `_array__*` functions below to address strict aliasing
// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`.
//
// The `Array` type itself was not altered as a solution in order to avoid breakage
// with existing consumers (in particular, parsers with external scanners).
/// This is not what you're looking for, see `array_delete`.
static inline void _array__delete(void *self, void *contents, size_t self_size) {
if (contents) ts_free(contents);
if (self) memset(self, 0, self_size);
}
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(void* self_contents, uint32_t *size,
size_t element_size, uint32_t index) {
assert(index < *size);
char *contents = (char *)self_contents;
memmove(contents + index * element_size, contents + (index + 1) * element_size,
(*size - index - 1) * element_size);
(*size)--;
}
/// This is not what you're looking for, see `array_reserve`.
static inline void *_array__reserve(void *contents, uint32_t *capacity,
size_t element_size, uint32_t new_capacity) {
void *new_contents = contents;
if (new_capacity > *capacity) {
if (contents) {
new_contents = ts_realloc(contents, new_capacity * element_size);
} else {
new_contents = ts_malloc(new_capacity * element_size);
}
*capacity = new_capacity;
}
return new_contents;
}
/// This is not what you're looking for, see `array_assign`.
static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity,
const void *other_contents, uint32_t other_size, size_t element_size) {
void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size);
*self_size = other_size;
memcpy(new_contents, other_contents, *self_size * element_size);
return new_contents;
}
struct Swap {
void *self_contents;
void *other_contents;
};
/// This is not what you're looking for, see `array_swap`.
// static inline void _array__swap(Array *self, Array *other) {
static inline struct Swap _array__swap(void *self_contents, uint32_t *self_size, uint32_t *self_capacity,
void *other_contents, uint32_t *other_size, uint32_t *other_capacity) {
void *new_self_contents = other_contents;
uint32_t new_self_size = *other_size;
uint32_t new_self_capacity = *other_capacity;
void *new_other_contents = self_contents;
*other_size = *self_size;
*other_capacity = *self_capacity;
*self_size = new_self_size;
*self_capacity = new_self_capacity;
struct Swap out = {
.self_contents = new_self_contents,
.other_contents = new_other_contents,
};
return out;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity,
uint32_t count, size_t element_size) {
void *new_contents = contents;
uint32_t new_size = size + count;
if (new_size > *capacity) {
uint32_t new_capacity = *capacity * 2;
if (new_capacity < 8) new_capacity = 8;
if (new_capacity < new_size) new_capacity = new_size;
new_contents = _array__reserve(contents, capacity, element_size, new_capacity);
}
return new_contents;
}
/// This is not what you're looking for, see `array_splice`.
static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity,
size_t element_size,
uint32_t index, uint32_t old_count,
uint32_t new_count, const void *elements) {
uint32_t new_size = *size + new_count - old_count;
uint32_t old_end = index + old_count;
uint32_t new_end = index + new_count;
assert(old_end <= *size);
void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size);
char *contents = (char *)new_contents;
if (*size > old_end) {
memmove(
contents + new_end * element_size,
contents + old_end * element_size,
(*size - old_end) * element_size
);
}
if (new_count > 0) {
if (elements) {
memcpy(
(contents + index * element_size),
elements,
new_count * element_size
);
} else {
memset(
(contents + index * element_size),
0,
new_count * element_size
);
}
}
*size += new_count - old_count;
return new_contents;
}
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
do { \
*(_index) = start; \
*(_exists) = false; \
uint32_t size = (self)->size - *(_index); \
if (size == 0) break; \
int comparison; \
while (size > 1) { \
uint32_t half_size = size / 2; \
uint32_t mid_index = *(_index) + half_size; \
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
if (comparison <= 0) *(_index) = mid_index; \
size -= half_size; \
} \
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
if (comparison == 0) *(_exists) = true; \
else if (comparison < 0) *(_index) += 1; \
} while (0)
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
/// parameter by reference in order to work with the generic sorting function above.
#define _compare_int(a, b) ((int)*(a) - (int)(b))
#ifdef _MSC_VER
#pragma warning(pop)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ARRAY_H_
@@ -0,0 +1,286 @@
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
typedef struct TSLanguageMetadata {
uint8_t major_version;
uint8_t minor_version;
uint8_t patch_version;
} TSLanguageMetadata;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
// Used to index the field and supertype maps.
typedef struct {
uint16_t index;
uint16_t length;
} TSMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
void (*log)(const TSLexer *, const char *, ...);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
uint16_t reserved_word_set_id;
} TSLexerMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
typedef struct {
int32_t start;
int32_t end;
} TSCharacterRange;
struct TSLanguage {
uint32_t abi_version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexerMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
const char *name;
const TSSymbol *reserved_words;
uint16_t max_reserved_word_set_size;
uint32_t supertype_count;
const TSSymbol *supertype_symbols;
const TSMapSlice *supertype_map_slices;
const TSSymbol *supertype_map_entries;
TSLanguageMetadata metadata;
};
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
uint32_t index = 0;
uint32_t size = len - index;
while (size > 1) {
uint32_t half_size = size / 2;
uint32_t mid_index = index + half_size;
const TSCharacterRange *range = &ranges[mid_index];
if (lookahead >= range->start && lookahead <= range->end) {
return true;
} else if (lookahead > range->end) {
index = mid_index;
}
size -= half_size;
}
const TSCharacterRange *range = &ranges[index];
return (lookahead >= range->start && lookahead <= range->end);
}
/*
* Lexer Macros
*/
#ifdef _MSC_VER
#define UNUSED __pragma(warning(suppress : 4101))
#else
#define UNUSED __attribute__((unused))
#endif
#define START_LEXER() \
bool result = false; \
bool skip = false; \
UNUSED \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define ADVANCE_MAP(...) \
{ \
static const uint16_t map[] = { __VA_ARGS__ }; \
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
if (map[i] == lookahead) { \
state = map[i + 1]; \
goto next_state; \
} \
} \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value) \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value), \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_name, children, precedence, prod_id) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_name, \
.child_count = children, \
.dynamic_precedence = precedence, \
.production_id = prod_id \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_
@@ -0,0 +1,97 @@
==================
Core syntax
==================
io :: import "@std/io"
Status :: enum {
ok
bad
}
Pair :: struct {
left i32
right i32
}
sum func(a, b i32) i32 ! Status {
result :: a + b
if result > 0 {
return result
} else {
return .bad
}
}
---
(source_file
(import_declaration
(identifier)
(string
(string_content)))
(type_declaration
(identifier)
(enum_type
(enum_body
(enum_member
(identifier))
(enum_member
(identifier)))))
(type_declaration
(identifier)
(struct_type
(record_body
(record_field
(identifier)
(type
(builtin_type)))
(record_field
(identifier)
(type
(builtin_type))))))
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(identifier)
(type
(builtin_type))))
(type
(builtin_type))
(type
(named_type
(qualified_identifier
(identifier))))
(block
(statement
(constant_declaration
(identifier)
(expression
(binary_expression
(expression
(identifier))
(expression
(identifier))))))
(statement
(if_statement
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))
(statement
(block
(statement
(return_statement
(expression
(identifier))))))
(statement
(block
(statement
(return_statement
(expression
(enum_literal
(identifier))))))))))))
+27
View File
@@ -0,0 +1,27 @@
{
"grammars": [
{
"name": "brolang",
"camelcase": "Brolang",
"scope": "source.bro",
"path": ".",
"file-types": ["bro", "hon"],
"highlights": "queries/highlights.scm"
}
],
"metadata": {
"version": "0.1.0",
"description": "Tree-sitter grammar for Brolang",
"authors": [{"name": "Brolang contributors"}]
},
"bindings": {
"c": false,
"go": false,
"java": false,
"node": false,
"python": false,
"rust": false,
"swift": false,
"zig": false
}
}