Compare commits
7 Commits
0a424ec6d2
...
9e75549d02
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e75549d02 | |||
| 5a958d9bfd | |||
| de56dc7315 | |||
| 6455df52b4 | |||
| 288df082e2 | |||
| 2ed333c70d | |||
| a4d0fb1e26 |
@@ -1,2 +1,3 @@
|
||||
/build/
|
||||
/grammars/
|
||||
.DS_Store
|
||||
|
||||
+80
-3
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
Executable
BIN
Binary file not shown.
@@ -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"] },
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import "arraylist"
|
||||
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import "arraylist"
|
||||
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
@@ -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)),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "tree-sitter-brolang",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"generate": "tree-sitter generate",
|
||||
"test": "tree-sitter test"
|
||||
}
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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))))))))))))
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user