refactor: apply project style guidelines
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Style: before writing or reviewing `.hon` code, read [docs/style.md](docs/style.md).
|
style: before writing or reviewing `.hon` code, read [docs/style.md](docs/style.md).
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
# Honey
|
# Honey
|
||||||
|
|
||||||
Honey is currently bootstrapped with the Bro compiler. The expected development workspace places the Bro project at [`../brolang`](../brolang); consult that project for compiler behavior and documentation.
|
Honey is currently bootstrapped with the Bro compiler. the expected development workspace places the Bro project at [`../brolang`](../brolang); consult that project for compiler behavior and documentation.
|
||||||
|
|
||||||
Style: see the [language guidelines](docs/style.md).
|
style: see the [language guidelines](docs/style.md).
|
||||||
|
|
||||||
Build Honey from this directory:
|
build Honey from this directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bro build
|
bro build
|
||||||
```
|
```
|
||||||
|
|
||||||
The parser supports arithmetic expressions with nested parenthesized groups, such as `a + (b - c) * 2 / 3`. Groups control precedence without adding AST nodes; inner expression spans exclude the grouping parentheses.
|
the parser supports arithmetic expressions with nested parenthesized groups, such as `a + (b - c) * 2 / 3`. groups control precedence without adding AST nodes; inner expression spans exclude the grouping parentheses.
|
||||||
|
|
||||||
Function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. Calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. Argument lists allow a trailing comma; newlines remain statement boundaries. Argument/type checking belongs to semantic analysis and is not yet implemented.
|
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow a trailing comma; newlines remain statement boundaries. argument/type checking belongs to semantic analysis and is not yet implemented.
|
||||||
|
|
||||||
Name resolution (`@source/sema`) runs after parsing. It predeclares package names and function-body locals, allowing forward references and mutually recursive functions. Parameters and locals share a function scope; signatures resolve names in the enclosing package scope. Shadowing is forbidden, including reuse of builtin type names (`int`, `float`, `string`, `void`); sibling function scopes may independently reuse names.
|
name resolution (`@source/sema`) runs after parsing. it predeclares package names and function-body locals, allowing forward references and mutually recursive functions. parameters and locals share a function scope; signatures resolve names in the enclosing package scope. shadowing is forbidden, including reuse of builtin type names (`int`, `float`, `string`, `void`); sibling function scopes may independently reuse names.
|
||||||
|
|
||||||
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. Release the result with `sema.deinit`. Unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax retains its existing diagnostic identity. Same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration. Type checking and read-before-initialization checks remain separate, unimplemented stages.
|
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax retains its existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration. type checking and read-before-initialization checks remain separate, unimplemented stages.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
b :: import "@std/build"
|
build :: import "@std/build"
|
||||||
|
|
||||||
config :: b.BuildConfig{
|
config :: build.BuildConfig{
|
||||||
name = "honey",
|
name = "honey",
|
||||||
source = "source",
|
source = "source",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Compile through recoverable source errors
|
# compile through recoverable source errors
|
||||||
|
|
||||||
Honey produces an executable whenever source errors can be represented safely: structurally valid syntax trees may contain typed invalid nodes carrying source spans and diagnostic identities, and semantic checking or lowering turns demanded invalid code into diagnostic runtime traps. Clean compilation returns status `0`, compilation that emits an executable with source diagnostics returns `1`, and unrecoverable infrastructure or backend failure returns `2`. This deliberately favors testing unaffected program paths over conventional fail-fast compilation; parser recovery must preserve valid surrounding syntax, while compact syntax-tree storage remains private behind typed views.
|
Honey produces an executable whenever source errors can be represented safely: structurally valid syntax trees may contain typed invalid nodes carrying source spans and diagnostic identities, and semantic checking or lowering turns demanded invalid code into diagnostic runtime traps. clean compilation returns status `0`, compilation that emits an executable with source diagnostics returns `1`, and unrecoverable infrastructure or backend failure returns `2`. this deliberately favors testing unaffected program paths over conventional fail-fast compilation; parser recovery must preserve valid surrounding syntax, while compact syntax-tree storage remains private behind typed views.
|
||||||
|
|||||||
+21
-21
@@ -1,27 +1,27 @@
|
|||||||
# Honey style
|
# Honey style
|
||||||
|
|
||||||
These guidelines apply to Honey programs, the compiler, and the standard library. The compiler is currently written in Bro; apply the same conventions using syntax supported by the bootstrap compiler.
|
these guidelines apply to Honey programs, the compiler, and the standard library. the compiler is currently written in Bro; apply the same conventions using syntax supported by the bootstrap compiler. generated C bindings retain the generator's declarations and formatting.
|
||||||
|
|
||||||
## Names
|
## names
|
||||||
|
|
||||||
- Use `snake_case` for procedures, variables, fields, and enum members; `PascalCase` for types; `SCREAMING_SNAKE_CASE` for named sentinel constants.
|
- use `snake_case` for procedures, variables, fields, and enum members; `PascalCase` for types; `SCREAMING_SNAKE_CASE` for named sentinel constants.
|
||||||
- Choose names that describe the value or operation. Short loop indices are appropriate when their meaning is local.
|
- choose names that describe the value or operation. short loop indices are appropriate when their meaning is local. preserve external ABI names.
|
||||||
- Qualify imported operations with their module, such as `mem.alloc` and `ast.node_count`. Mark file-local implementation details with `@hide:file`.
|
- qualify imported operations with their module, such as `mem.alloc` and `ast.node_count`. mark file-local implementation details with `@hide:file`.
|
||||||
|
|
||||||
## Bindings and types
|
## bindings and types
|
||||||
|
|
||||||
- Prefer `::` for bindings that stay fixed; use `:=` for bindings that change.
|
- prefer `::` for bindings that stay fixed; use `:=` for bindings that change, including values mutated through pointers.
|
||||||
- Infer types when the initializer makes the intended type clear. Write an explicit type when it determines representation, mutability, or a public contract.
|
- infer types when the initializer makes the intended type clear. write an explicit type when it determines representation, mutability, or a public contract.
|
||||||
- Request mutable pointers and slices only when the operation needs to modify their contents.
|
- request mutable pointers and slices only when the operation needs to modify their contents.
|
||||||
- Give aggregates meaningful defaults. Use `undefined` only when every value that can be read is assigned first.
|
- give aggregates meaningful defaults. use `undefined` only when every value that can be read is assigned first.
|
||||||
|
|
||||||
## Layout and control flow
|
## layout and control flow
|
||||||
|
|
||||||
- Use four-space indentation and put opening braces on the declaration or control-flow line.
|
- use four-space indentation and put opening braces on the declaration or control-flow line.
|
||||||
- Use single-line bodies for simple operations. Use braces for multiple statements or nested control flow.
|
- use single-line bodies for simple operations. use braces for multiple statements or nested control flow.
|
||||||
- Break long parameter lists and aggregate initializers across lines, with a trailing comma on the last item.
|
- break long parameter lists and aggregate initializers across lines, with a trailing comma on the last item.
|
||||||
- Use early returns for terminal cases so the main path stays shallow.
|
- use early returns for terminal cases so the main path stays shallow.
|
||||||
- Register `defer` cleanup immediately after acquiring a resource. Use `errdefer` for cleanup needed only until ownership is transferred successfully.
|
- register `defer` cleanup immediately after acquiring a resource. use `errdefer` for cleanup needed only until ownership is transferred successfully.
|
||||||
|
|
||||||
## allocation and ownership
|
## allocation and ownership
|
||||||
|
|
||||||
@@ -36,10 +36,10 @@ These guidelines apply to Honey programs, the compiler, and the standard library
|
|||||||
- prefer lowercase prose in documentation, including headings and sentence starts. preserve the case of code identifiers, proper names, and acronyms.
|
- prefer lowercase prose in documentation, including headings and sentence starts. preserve the case of code identifiers, proper names, and acronyms.
|
||||||
- write comments for invariants, ownership, or reasons that the code alone does not explain. use `#!` for API documentation.
|
- write comments for invariants, ownership, or reasons that the code alone does not explain. use `#!` for API documentation.
|
||||||
|
|
||||||
## Library operations
|
## library operations
|
||||||
|
|
||||||
- Reuse an existing standard-library operation when it expresses the work directly.
|
- reuse an existing standard-library operation when it expresses the work directly.
|
||||||
- Use `mem.set(destination, value)` when assigning the same complete typed value to every element of a mutable slice. This preserves the value's fields; it is not a byte-wise zeroing operation.
|
- use `mem.set(destination, value)` when assigning the same complete typed value to every element of a mutable slice. this preserves the value's fields; it is not a byte-wise zeroing operation.
|
||||||
- Keep a loop when only one field is initialized, values differ by element, or initialization shares a pass with other work. A helper should not introduce unnecessary writes or another traversal.
|
- keep a loop when only one field is initialized, values differ by element, or initialization shares a pass with other work. a helper should not introduce unnecessary writes or another traversal.
|
||||||
|
|
||||||
When editing or reviewing code, apply every relevant guideline to the changed code. Keep unrelated formatting changes separate.
|
when editing or reviewing code, apply every relevant guideline to the changed code. keep unrelated formatting changes separate.
|
||||||
|
|||||||
+35
-7
@@ -1,4 +1,4 @@
|
|||||||
#! GRAMMAR:
|
#! grammar:
|
||||||
#! * type ..... -> identifier
|
#! * type ..... -> identifier
|
||||||
#! * assignment -> :: | :=
|
#! * assignment -> :: | :=
|
||||||
#! * literal .. -> int | float | string
|
#! * literal .. -> int | float | string
|
||||||
@@ -99,11 +99,13 @@ Storage :: struct {
|
|||||||
spans arraylist.ArrayList(Span)
|
spans arraylist.ArrayList(Span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! owns node storage, retains its allocator, and borrows source until finish or deinit_builder.
|
||||||
Builder :: struct {
|
Builder :: struct {
|
||||||
storage Storage
|
storage Storage
|
||||||
source []u8
|
source []u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! owns node storage and borrows source; source must remain valid until deinit.
|
||||||
Tree :: struct {
|
Tree :: struct {
|
||||||
storage Storage
|
storage Storage
|
||||||
source []u8
|
source []u8
|
||||||
@@ -154,6 +156,7 @@ InvalidView :: struct {
|
|||||||
diagnostic DiagnosticId
|
diagnostic DiagnosticId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! retains allocator for node storage and borrows source; release with deinit_builder.
|
||||||
init_builder proc(allocator mem.Allocator, source []u8) Builder {
|
init_builder proc(allocator mem.Allocator, source []u8) Builder {
|
||||||
return Builder{
|
return Builder{
|
||||||
storage = Storage{
|
storage = Storage{
|
||||||
@@ -165,12 +168,14 @@ init_builder proc(allocator mem.Allocator, source []u8) Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases builder-owned node storage with its retained allocator, without freeing source.
|
||||||
deinit_builder proc(builder @mut Builder) void {
|
deinit_builder proc(builder @mut Builder) void {
|
||||||
arraylist.deinit(&builder.storage.nodes)
|
arraylist.deinit(&builder.storage.nodes)
|
||||||
arraylist.deinit(&builder.storage.extra)
|
arraylist.deinit(&builder.storage.extra)
|
||||||
arraylist.deinit(&builder.storage.spans)
|
arraylist.deinit(&builder.storage.spans)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! transfers node storage and the source borrow to a caller-owned Tree; release with deinit.
|
||||||
finish proc(builder @mut Builder, root NodeId) Tree {
|
finish proc(builder @mut Builder, root NodeId) Tree {
|
||||||
tree :: Tree{ storage = builder.storage, source = builder.source, root = root }
|
tree :: Tree{ storage = builder.storage, source = builder.source, root = root }
|
||||||
allocator :: builder.storage.nodes.allocator
|
allocator :: builder.storage.nodes.allocator
|
||||||
@@ -183,6 +188,7 @@ finish proc(builder @mut Builder, root NodeId) Tree {
|
|||||||
return tree
|
return tree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases owned node storage with its retained allocator, without freeing source.
|
||||||
deinit proc(tree @mut Tree) void {
|
deinit proc(tree @mut Tree) void {
|
||||||
arraylist.deinit(&tree.storage.nodes)
|
arraylist.deinit(&tree.storage.nodes)
|
||||||
arraylist.deinit(&tree.storage.extra)
|
arraylist.deinit(&tree.storage.extra)
|
||||||
@@ -324,21 +330,33 @@ add_package proc(builder @mut Builder, span Span, declarations []NodeId) NodeId
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_invalid_expr proc(builder @mut Builder, span Span, diagnostic DiagnosticId) NodeId ! mem.AllocError {
|
add_invalid_expr proc(
|
||||||
|
builder @mut Builder,
|
||||||
|
span Span,
|
||||||
|
diagnostic DiagnosticId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
return try add_node(builder, Node{
|
return try add_node(builder, Node{
|
||||||
kind = .invalid_expr,
|
kind = .invalid_expr,
|
||||||
main = NodeData{ diagnostic = diagnostic },
|
main = NodeData{ diagnostic = diagnostic },
|
||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_invalid_stmt proc(builder @mut Builder, span Span, diagnostic DiagnosticId) NodeId ! mem.AllocError {
|
add_invalid_stmt proc(
|
||||||
|
builder @mut Builder,
|
||||||
|
span Span,
|
||||||
|
diagnostic DiagnosticId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
return try add_node(builder, Node{
|
return try add_node(builder, Node{
|
||||||
kind = .invalid_stmt,
|
kind = .invalid_stmt,
|
||||||
main = NodeData{ diagnostic = diagnostic },
|
main = NodeData{ diagnostic = diagnostic },
|
||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_invalid_decl proc(builder @mut Builder, span Span, diagnostic DiagnosticId) NodeId ! mem.AllocError {
|
add_invalid_decl proc(
|
||||||
|
builder @mut Builder,
|
||||||
|
span Span,
|
||||||
|
diagnostic DiagnosticId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
return try add_node(builder, Node{
|
return try add_node(builder, Node{
|
||||||
kind = .invalid_decl,
|
kind = .invalid_decl,
|
||||||
main = NodeData{ diagnostic = diagnostic },
|
main = NodeData{ diagnostic = diagnostic },
|
||||||
@@ -505,14 +523,24 @@ call_arg proc(tree @Tree, id NodeId, index usize) NodeId {
|
|||||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
}
|
}
|
||||||
|
|
||||||
node_span proc(tree @Tree, id NodeId) Span { return get_span(&tree.storage, id) }
|
node_span proc(tree @Tree, id NodeId) Span {
|
||||||
|
return get_span(&tree.storage, id)
|
||||||
|
}
|
||||||
|
|
||||||
invalid proc(tree @Tree, id NodeId) InvalidView {
|
invalid proc(tree @Tree, id NodeId) InvalidView {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .invalid_expr or node.kind == .invalid_stmt or node.kind == .invalid_decl)
|
debug.assert(
|
||||||
return InvalidView{ span = get_span(&tree.storage, id), diagnostic = node.main.diagnostic }
|
node.kind == .invalid_expr or
|
||||||
|
node.kind == .invalid_stmt or
|
||||||
|
node.kind == .invalid_decl
|
||||||
|
)
|
||||||
|
return InvalidView{
|
||||||
|
span = get_span(&tree.storage, id),
|
||||||
|
diagnostic = node.main.diagnostic,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! returns text borrowed from the source buffer; the caller must not free it.
|
||||||
source_text proc(tree @Tree, span Span) []u8 {
|
source_text proc(tree @Tree, span Span) []u8 {
|
||||||
debug.assert(span.start <= span.end and usize(span.end) <= tree.source.len)
|
debug.assert(span.start <= span.end and usize(span.end) <= tree.source.len)
|
||||||
return tree.source[usize(span.start)..usize(span.end)]
|
return tree.source[usize(span.start)..usize(span.end)]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ exposes_typed_declaration_views test {
|
|||||||
.constant,
|
.constant,
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
declarations [1]NodeId := [declaration]
|
declarations [1]NodeId :: [declaration]
|
||||||
package :: try add_package(&builder, Span{ start = 0, end = 11 }, declarations[..])
|
package :: try add_package(&builder, Span{ start = 0, end = 11 }, declarations[..])
|
||||||
tree := finish(&builder, package)
|
tree := finish(&builder, package)
|
||||||
defer deinit(&tree)
|
defer deinit(&tree)
|
||||||
|
|||||||
@@ -2,18 +2,22 @@ import "@std/mem"
|
|||||||
import "@std/arraylist"
|
import "@std/arraylist"
|
||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
|
|
||||||
|
#! owns output bytes and retains its allocator; release with deinit.
|
||||||
Renderer :: struct {
|
Renderer :: struct {
|
||||||
bytes arraylist.ArrayList(u8)
|
bytes arraylist.ArrayList(u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! retains allocator for rendered output; the caller owns the returned Renderer.
|
||||||
init proc(allocator mem.Allocator) Renderer {
|
init proc(allocator mem.Allocator) Renderer {
|
||||||
return Renderer{ bytes = arraylist.init(allocator) }
|
return Renderer{ bytes = arraylist.init(allocator) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases output bytes with the retained allocator.
|
||||||
deinit proc(renderer @mut Renderer) void {
|
deinit proc(renderer @mut Renderer) void {
|
||||||
arraylist.deinit(&renderer.bytes)
|
arraylist.deinit(&renderer.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrows tree during rendering; output stays renderer-owned until render or deinit.
|
||||||
render proc(renderer @mut Renderer, tree @ast.Tree) void ! mem.AllocError {
|
render proc(renderer @mut Renderer, tree @ast.Tree) void ! mem.AllocError {
|
||||||
arraylist.clear(&renderer.bytes)
|
arraylist.clear(&renderer.bytes)
|
||||||
try append_text(renderer, "package\n")
|
try append_text(renderer, "package\n")
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ renders_semantic_debug_view test {
|
|||||||
.constant,
|
.constant,
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
declarations [1]ast.NodeId := [declaration]
|
declarations [1]ast.NodeId :: [declaration]
|
||||||
package :: try ast.add_package(&builder, ast.Span{ start = 0, end = 11 }, declarations[..])
|
package :: try ast.add_package(&builder, ast.Span{ start = 0, end = 11 }, declarations[..])
|
||||||
tree := ast.finish(&builder, package)
|
tree := ast.finish(&builder, package)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|||||||
@@ -23,18 +23,22 @@ Diagnostic :: struct {
|
|||||||
span Span
|
span Span
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! owns diagnostic storage and retains its allocator; release with deinit.
|
||||||
Store :: struct { list arraylist.ArrayList(Diagnostic) }
|
Store :: struct { list arraylist.ArrayList(Diagnostic) }
|
||||||
|
|
||||||
CapacityError :: enum { diagnostic_limit_reached }
|
CapacityError :: enum { diagnostic_limit_reached }
|
||||||
|
|
||||||
|
#! retains allocator for added diagnostics; the caller owns the returned Store.
|
||||||
init proc(allocator mem.Allocator) Store {
|
init proc(allocator mem.Allocator) Store {
|
||||||
return Store{ list = arraylist.init(allocator) }
|
return Store{ list = arraylist.init(allocator) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases diagnostic storage with the retained allocator.
|
||||||
deinit proc(store @mut Store) void {
|
deinit proc(store @mut Store) void {
|
||||||
arraylist.deinit(&store.list)
|
arraylist.deinit(&store.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! allocates from the retained allocator; the returned ID is valid until deinit.
|
||||||
add proc(store @mut Store, code Code, span Span) DiagnosticId ! (mem.AllocError | CapacityError) {
|
add proc(store @mut Store, code Code, span Span) DiagnosticId ! (mem.AllocError | CapacityError) {
|
||||||
if (store.list.items.len >= usize(NO_DIAGNOSTIC)) return .diagnostic_limit_reached
|
if (store.list.items.len >= usize(NO_DIAGNOSTIC)) return .diagnostic_limit_reached
|
||||||
id :: DiagnosticId(store.list.items.len)
|
id :: DiagnosticId(store.list.items.len)
|
||||||
|
|||||||
+55
-32
@@ -5,6 +5,7 @@ import "@std/arraylist"
|
|||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
import "@source/strpool"
|
import "@source/strpool"
|
||||||
|
|
||||||
|
@hide:file
|
||||||
ScanError :: struct {
|
ScanError :: struct {
|
||||||
code ErrorCode
|
code ErrorCode
|
||||||
end usize
|
end usize
|
||||||
@@ -17,7 +18,7 @@ ErrorCode :: enum {
|
|||||||
}
|
}
|
||||||
SourceError :: enum { source_too_large }
|
SourceError :: enum { source_too_large }
|
||||||
|
|
||||||
|
@hide:file
|
||||||
keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||||
{ "proc", .proc },
|
{ "proc", .proc },
|
||||||
{ "return", .return },
|
{ "return", .return },
|
||||||
@@ -27,18 +28,25 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
|||||||
{ "while", .while },
|
{ "while", .while },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
#! owns token storage and retains its allocator; release with deinit.
|
||||||
|
#! identifier IDs refer to strings in the separately owned strpool.strings.
|
||||||
State :: struct {
|
State :: struct {
|
||||||
tokens arraylist.ArrayList(Token)
|
tokens arraylist.ArrayList(Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! retains allocator for scanned tokens; the caller owns the returned State.
|
||||||
init proc(allocator mem.Allocator) State {
|
init proc(allocator mem.Allocator) State {
|
||||||
return State{ tokens = arraylist.init(allocator) }
|
return State{ tokens = arraylist.init(allocator) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases token storage with the retained allocator, without releasing strpool.strings.
|
||||||
deinit proc(state @mut State) void {
|
deinit proc(state @mut State) void {
|
||||||
arraylist.deinit(&state.tokens)
|
arraylist.deinit(&state.tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrows program and diagnostic_store during scanning; tokens remain state-owned.
|
||||||
|
#! token allocation uses the retained allocator; identifiers use strpool.strings's allocator.
|
||||||
|
#! initialize strpool.strings before scanning and keep it alive while using identifier IDs.
|
||||||
scan proc(
|
scan proc(
|
||||||
state @mut State,
|
state @mut State,
|
||||||
program []u8,
|
program []u8,
|
||||||
@@ -49,24 +57,22 @@ scan proc(
|
|||||||
while cursor < program.len {
|
while cursor < program.len {
|
||||||
char :: program[cursor]
|
char :: program[cursor]
|
||||||
|
|
||||||
# whitespace
|
|
||||||
if char == '\n' {
|
if char == '\n' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 1), kind = .newline })
|
try add_token(state, Token{ span = make_span(cursor, cursor + 1), kind = .newline })
|
||||||
cursor += 1
|
cursor += 1
|
||||||
continue
|
continue
|
||||||
} else if is_whitespace(char) {
|
}
|
||||||
|
if is_whitespace(char) {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# comments
|
|
||||||
if char == '#' {
|
if char == '#' {
|
||||||
while (cursor < program.len and program[cursor] != '\n') cursor += 1
|
while (cursor < program.len and program[cursor] != '\n') cursor += 1
|
||||||
if (cursor < program.len) cursor += 1
|
if (cursor < program.len) cursor += 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# identifiers and keywords
|
|
||||||
if is_alpha(char) or char == '_' {
|
if is_alpha(char) or char == '_' {
|
||||||
start :: cursor
|
start :: cursor
|
||||||
result :: scan_ident(start, program)
|
result :: scan_ident(start, program)
|
||||||
@@ -79,7 +85,6 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# numeric literals
|
|
||||||
if is_digit(char) {
|
if is_digit(char) {
|
||||||
start :: cursor
|
start :: cursor
|
||||||
result :: scan_number(start, program) catch |err| {
|
result :: scan_number(start, program) catch |err| {
|
||||||
@@ -102,7 +107,6 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# string literals
|
|
||||||
if char == '"' {
|
if char == '"' {
|
||||||
start :: cursor
|
start :: cursor
|
||||||
result :: scan_string(start, program) catch |err| {
|
result :: scan_string(start, program) catch |err| {
|
||||||
@@ -124,15 +128,20 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# immutable decl, mutable decl, or single colon
|
|
||||||
if char == ':' {
|
if char == ':' {
|
||||||
if cursor + 1 < program.len and program[cursor + 1] == ':' {
|
if cursor + 1 < program.len and program[cursor + 1] == ':' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 2), kind = .double_colon })
|
try add_token(state, Token{
|
||||||
|
span = make_span(cursor, cursor + 2),
|
||||||
|
kind = .double_colon,
|
||||||
|
})
|
||||||
cursor += 2
|
cursor += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 2), kind = .colon_equal })
|
try add_token(state, Token{
|
||||||
|
span = make_span(cursor, cursor + 2),
|
||||||
|
kind = .colon_equal,
|
||||||
|
})
|
||||||
cursor += 2
|
cursor += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -141,10 +150,12 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# compound arithmetic assignment
|
|
||||||
if char == '+' {
|
if char == '+' {
|
||||||
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 2), kind = .plus_equal })
|
try add_token(state, Token{
|
||||||
|
span = make_span(cursor, cursor + 2),
|
||||||
|
kind = .plus_equal,
|
||||||
|
})
|
||||||
cursor += 2
|
cursor += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -154,7 +165,10 @@ scan proc(
|
|||||||
}
|
}
|
||||||
if char == '-' {
|
if char == '-' {
|
||||||
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 2), kind = .minus_equal })
|
try add_token(state, Token{
|
||||||
|
span = make_span(cursor, cursor + 2),
|
||||||
|
kind = .minus_equal,
|
||||||
|
})
|
||||||
cursor += 2
|
cursor += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -164,7 +178,10 @@ scan proc(
|
|||||||
}
|
}
|
||||||
if char == '*' {
|
if char == '*' {
|
||||||
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 2), kind = .star_equal })
|
try add_token(state, Token{
|
||||||
|
span = make_span(cursor, cursor + 2),
|
||||||
|
kind = .star_equal,
|
||||||
|
})
|
||||||
cursor += 2
|
cursor += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -174,7 +191,10 @@ scan proc(
|
|||||||
}
|
}
|
||||||
if char == '/' {
|
if char == '/' {
|
||||||
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
if cursor + 1 < program.len and program[cursor + 1] == '=' {
|
||||||
try add_token(state, Token{ span = make_span(cursor, cursor + 2), kind = .slash_equal })
|
try add_token(state, Token{
|
||||||
|
span = make_span(cursor, cursor + 2),
|
||||||
|
kind = .slash_equal,
|
||||||
|
})
|
||||||
cursor += 2
|
cursor += 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -183,7 +203,6 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# single character tokens
|
|
||||||
kind ?TokenKind :: match char {
|
kind ?TokenKind :: match char {
|
||||||
'=': .equal
|
'=': .equal
|
||||||
',': .comma
|
',': .comma
|
||||||
@@ -201,7 +220,6 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
# invalid character
|
|
||||||
id :: try diagnostics.add(
|
id :: try diagnostics.add(
|
||||||
diagnostic_store,
|
diagnostic_store,
|
||||||
.invalid_character,
|
.invalid_character,
|
||||||
@@ -218,15 +236,16 @@ scan proc(
|
|||||||
try add_token(state, Token{ span = make_span(cursor, cursor), kind = .eof })
|
try add_token(state, Token{ span = make_span(cursor, cursor), kind = .eof })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
ScanIdentResult :: struct {
|
ScanIdentResult :: struct {
|
||||||
end usize
|
end usize
|
||||||
kind TokenKind
|
kind TokenKind
|
||||||
str_id strpool.StringId
|
str_id strpool.StringId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
scan_ident proc(start usize, program []u8) ScanIdentResult {
|
scan_ident proc(start usize, program []u8) ScanIdentResult {
|
||||||
cursor := start + 1
|
cursor := start + 1
|
||||||
# scan whole identifier
|
|
||||||
while (cursor < program.len and (
|
while (cursor < program.len and (
|
||||||
is_alpha(program[cursor]) or
|
is_alpha(program[cursor]) or
|
||||||
is_digit(program[cursor]) or
|
is_digit(program[cursor]) or
|
||||||
@@ -236,58 +255,62 @@ scan_ident proc(start usize, program []u8) ScanIdentResult {
|
|||||||
# don't intern keywords (already O(1) lookup via token kind)
|
# don't intern keywords (already O(1) lookup via token kind)
|
||||||
kind :: strmap.get(&keywords, program[start..cursor]) orelse .ident
|
kind :: strmap.get(&keywords, program[start..cursor]) orelse .ident
|
||||||
str_id :: if (kind == .ident)
|
str_id :: if (kind == .ident)
|
||||||
strpool.intern(&strpool.STRINGS, program[start..cursor]) catch strpool.NO_STR
|
strpool.intern(&strpool.strings, program[start..cursor]) catch strpool.NO_STR
|
||||||
else
|
else
|
||||||
strpool.NO_STR
|
strpool.NO_STR
|
||||||
return ScanIdentResult{ end = cursor, kind = kind, str_id = str_id }
|
return ScanIdentResult{ end = cursor, kind = kind, str_id = str_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
ScanNumResult :: struct {
|
ScanNumResult :: struct {
|
||||||
end usize
|
end usize
|
||||||
has_decimal bool
|
has_decimal bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
scan_number proc(start usize, program []u8) ScanNumResult ! ScanError {
|
scan_number proc(start usize, program []u8) ScanNumResult ! ScanError {
|
||||||
cursor := start
|
cursor := start
|
||||||
has_decimal := false
|
has_decimal := false
|
||||||
# scan integer part
|
|
||||||
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
||||||
# check for decimal
|
|
||||||
if cursor < program.len and program[cursor] == '.' {
|
if cursor < program.len and program[cursor] == '.' {
|
||||||
has_decimal = true
|
has_decimal = true
|
||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
# assert non-terminating decimal
|
|
||||||
if has_decimal and (cursor >= program.len or !is_digit(program[cursor])) {
|
if has_decimal and (cursor >= program.len or !is_digit(program[cursor])) {
|
||||||
return ScanError{ code = .float_must_end_with_digit, end = cursor }
|
return ScanError{
|
||||||
|
code = .float_must_end_with_digit,
|
||||||
|
end = cursor,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
# scan fractional part
|
|
||||||
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
||||||
return ScanNumResult{ end = cursor, has_decimal = has_decimal }
|
return ScanNumResult{ end = cursor, has_decimal = has_decimal }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
ScanStrResult :: struct { end usize }
|
ScanStrResult :: struct { end usize }
|
||||||
|
|
||||||
|
@hide:file
|
||||||
scan_string proc(start usize, program []u8) ScanStrResult ! ScanError {
|
scan_string proc(start usize, program []u8) ScanStrResult ! ScanError {
|
||||||
cursor := start + 1 # skip first `"`
|
cursor := start + 1 # skip first `"`
|
||||||
# scan entire string
|
|
||||||
while cursor < program.len and program[cursor] != '"' and program[cursor] != '\n' : cursor += 1 {
|
while cursor < program.len and program[cursor] != '"' and program[cursor] != '\n' : cursor += 1 {
|
||||||
# ignore escaped characters
|
# escaped quotes do not terminate the string.
|
||||||
if (program[cursor] == '\\' and cursor + 1 < program.len) cursor += 1
|
if (program[cursor] == '\\' and cursor + 1 < program.len) cursor += 1
|
||||||
}
|
}
|
||||||
# assert string terminal
|
|
||||||
if (cursor >= program.len or program[cursor] != '"') {
|
if (cursor >= program.len or program[cursor] != '"') {
|
||||||
return ScanError{ code = .unterminated_string, end = cursor }
|
return ScanError{
|
||||||
|
code = .unterminated_string,
|
||||||
|
end = cursor,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ScanStrResult{ end = cursor + 1 } # skip last `"`
|
return ScanStrResult{ end = cursor + 1 } # skip last `"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
is_whitespace proc(char u8) bool {
|
is_whitespace proc(char u8) bool {
|
||||||
return char == ' ' or char == '\t' or char == '\n' or char == '\r'
|
return char == ' ' or char == '\t' or char == '\n' or char == '\r'
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
is_alpha proc(char u8) bool {
|
is_alpha proc(char u8) bool {
|
||||||
return match char {
|
return match char {
|
||||||
'a'..='z', 'A'..='Z': true
|
'a'..='z', 'A'..='Z': true
|
||||||
@@ -295,7 +318,7 @@ is_alpha proc(char u8) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
is_digit proc(char u8) bool {
|
is_digit proc(char u8) bool {
|
||||||
return match char {
|
return match char {
|
||||||
'0'..='9': true
|
'0'..='9': true
|
||||||
@@ -316,7 +339,7 @@ make_span proc(start, end usize) diagnostics.Span {
|
|||||||
return diagnostics.Span{ start = u32(start), end = u32(end) }
|
return diagnostics.Span{ start = u32(start), end = u32(end) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
add_token proc(state @mut State, token Token) void ! mem.AllocError {
|
add_token proc(state @mut State, token Token) void ! mem.AllocError {
|
||||||
_ = token_id(state.tokens.items.len)
|
_ = token_id(state.tokens.items.len)
|
||||||
try arraylist.append(&state.tokens, token)
|
try arraylist.append(&state.tokens, token)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import "@source/diagnostics"
|
|||||||
import "@source/strpool"
|
import "@source/strpool"
|
||||||
|
|
||||||
reports_invalid_input_and_keeps_scanning test {
|
reports_invalid_input_and_keeps_scanning test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
state := init(mem.c_allocator)
|
state := init(mem.c_allocator)
|
||||||
@@ -23,7 +23,10 @@ reports_invalid_input_and_keeps_scanning test {
|
|||||||
try testing.expect_equal(TokenKind.string, state.tokens.items[6].kind)
|
try testing.expect_equal(TokenKind.string, state.tokens.items[6].kind)
|
||||||
try testing.expect_equal(TokenKind.eof, state.tokens.items[7].kind)
|
try testing.expect_equal(TokenKind.eof, state.tokens.items[7].kind)
|
||||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
try testing.expect_equal("name", strpool.get_str(&strpool.STRINGS, state.tokens.items[1].data.str_id)?)
|
try testing.expect_equal(
|
||||||
|
"name",
|
||||||
|
strpool.get_str(&strpool.strings, state.tokens.items[1].data.str_id)?,
|
||||||
|
)
|
||||||
try testing.expect_equal(u32(3), state.tokens.items[1].span.start)
|
try testing.expect_equal(u32(3), state.tokens.items[1].span.start)
|
||||||
try testing.expect_equal(u32(7), state.tokens.items[1].span.end)
|
try testing.expect_equal(u32(7), state.tokens.items[1].span.end)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -57,22 +57,22 @@ TokenKind :: enum {
|
|||||||
invalid
|
invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
render_token proc(tok Token, program []u8) void {
|
render_token proc(token Token, program []u8) void {
|
||||||
match tok.kind {
|
match token.kind {
|
||||||
.ident: {
|
.ident: {
|
||||||
if (strpool.get_str(&strpool.STRINGS, tok.data.str_id)) |str| {
|
if (strpool.get_str(&strpool.strings, token.data.str_id)) |str| {
|
||||||
debug.print("{}({})\n", {tok.kind, str})
|
debug.print("{}({})\n", {token.kind, str})
|
||||||
} else {
|
} else {
|
||||||
debug.print("{}({})\n", {tok.kind, program[tok.span.start..tok.span.end]})
|
debug.print("{}({})\n", {token.kind, program[token.span.start..token.span.end]})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.string, .int, .float:
|
.string, .int, .float:
|
||||||
debug.print("{}({})\n", {tok.kind, program[tok.span.start..tok.span.end]})
|
debug.print("{}({})\n", {token.kind, program[token.span.start..token.span.end]})
|
||||||
else: debug.print("{}\n", {tok.kind})
|
else: debug.print("{}\n", {token.kind})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
token_id proc(idx uint) TokenId {
|
token_id proc(index uint) TokenId {
|
||||||
debug.assert(u64(idx) < u64(NO_TOKEN))
|
debug.assert(u64(index) < u64(NO_TOKEN))
|
||||||
return TokenId(idx)
|
return TokenId(index)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -24,6 +24,7 @@ test import "@source/ast/renderer"
|
|||||||
test import "@source/parser"
|
test import "@source/parser"
|
||||||
test import "@source/sema"
|
test import "@source/sema"
|
||||||
|
|
||||||
|
@hide:file
|
||||||
program ::
|
program ::
|
||||||
`# literals
|
`# literals
|
||||||
`x float :: 123.9
|
`x float :: 123.9
|
||||||
@@ -42,8 +43,8 @@ program ::
|
|||||||
`empty proc() void {}
|
`empty proc() void {}
|
||||||
|
|
||||||
main proc() void! {
|
main proc() void! {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
@@ -53,14 +54,14 @@ main proc() void! {
|
|||||||
try lexer.scan(&scan_state, program, &diagnostic_store)
|
try lexer.scan(&scan_state, program, &diagnostic_store)
|
||||||
|
|
||||||
tree := try parser.parse(
|
tree := try parser.parse(
|
||||||
|
mem.c_allocator,
|
||||||
scan_state.tokens.items,
|
scan_state.tokens.items,
|
||||||
program,
|
program,
|
||||||
&diagnostic_store,
|
&diagnostic_store,
|
||||||
mem.c_allocator,
|
|
||||||
)
|
)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
resolution := try sema.resolve(&tree, &diagnostic_store, mem.c_allocator)
|
resolution := try sema.resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
defer sema.deinit(&resolution)
|
defer sema.deinit(&resolution)
|
||||||
|
|
||||||
renderer := ast_renderer.init(mem.c_allocator)
|
renderer := ast_renderer.init(mem.c_allocator)
|
||||||
|
|||||||
+29
-16
@@ -29,11 +29,14 @@ State :: struct {
|
|||||||
next_token usize
|
next_token usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrows tokens and source while parsing; the returned Tree borrows source until ast.deinit.
|
||||||
|
#! allocator supplies returned node storage and temporary lists released before return.
|
||||||
|
#! the caller owns the Tree and must release it with ast.deinit.
|
||||||
parse proc(
|
parse proc(
|
||||||
|
allocator mem.Allocator,
|
||||||
tokens []lexer.Token,
|
tokens []lexer.Token,
|
||||||
source []u8,
|
source []u8,
|
||||||
diagnostic_store @mut diagnostics.Store,
|
diagnostic_store @mut diagnostics.Store,
|
||||||
allocator mem.Allocator,
|
|
||||||
) ast.Tree ! (mem.AllocError | diagnostics.CapacityError) {
|
) ast.Tree ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
state := State{
|
state := State{
|
||||||
builder = ast.init_builder(allocator, source),
|
builder = ast.init_builder(allocator, source),
|
||||||
@@ -55,7 +58,7 @@ parse_package proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.
|
|||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
while !next_is(state, .eof) {
|
while !next_is(state, .eof) {
|
||||||
start :: state.next_token
|
start :: state.next_token
|
||||||
declaration := parse_decl(state) catch |err| {
|
declaration :: parse_decl(state) catch |err| {
|
||||||
match err {
|
match err {
|
||||||
.unexpected_token: {
|
.unexpected_token: {
|
||||||
recovered :: try recover_decl(state, start)
|
recovered :: try recover_decl(state, start)
|
||||||
@@ -79,9 +82,7 @@ parse_package proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_decl proc(state @mut State) ast.NodeId ! ParseError {
|
parse_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
if next_is(state, .ident) and next_n_is(state, 1, .proc) {
|
if (next_is(state, .ident) and next_n_is(state, 1, .proc)) return try parse_func_decl(state)
|
||||||
return try parse_func_decl(state)
|
|
||||||
}
|
|
||||||
return try parse_statement_decl(state)
|
return try parse_statement_decl(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
while !next_is(state, .close_curly) and !next_is(state, .eof) {
|
while !next_is(state, .close_curly) and !next_is(state, .eof) {
|
||||||
start :: state.next_token
|
start :: state.next_token
|
||||||
statement := parse_statement(state) catch |err| {
|
statement :: parse_statement(state) catch |err| {
|
||||||
match err {
|
match err {
|
||||||
.unexpected_token: {
|
.unexpected_token: {
|
||||||
recovered :: try recover_statement(state, start)
|
recovered :: try recover_statement(state, start)
|
||||||
@@ -184,7 +185,7 @@ parse_statement proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
name :: try consume(state, .ident)
|
name :: try consume(state, .ident)
|
||||||
type_id ?ast.NodeId := if next_is(state, .ident)
|
type_id ?ast.NodeId :: if next_is(state, .ident)
|
||||||
try parse_type(state)
|
try parse_type(state)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
@@ -266,8 +267,7 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
|||||||
defer arraylist.deinit(&arguments)
|
defer arraylist.deinit(&arguments)
|
||||||
|
|
||||||
while !next_is(state, .close_paren) and !next_is(state, .newline) and
|
while !next_is(state, .close_paren) and !next_is(state, .newline) and
|
||||||
!next_is(state, .close_curly) and !next_is(state, .eof)
|
!next_is(state, .close_curly) and !next_is(state, .eof) {
|
||||||
{
|
|
||||||
argument :: try parse_expr(state)
|
argument :: try parse_expr(state)
|
||||||
try arraylist.append(&arguments, argument)
|
try arraylist.append(&arguments, argument)
|
||||||
if (!next_is(state, .comma)) break
|
if (!next_is(state, .comma)) break
|
||||||
@@ -276,7 +276,10 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
|||||||
close :: try consume(state, .close_paren)
|
close :: try consume(state, .close_paren)
|
||||||
return try ast.add_call(
|
return try ast.add_call(
|
||||||
&state.builder,
|
&state.builder,
|
||||||
ast.Span{ start = ast.builder_span(&state.builder, callee).start, end = close.span.end },
|
ast.Span{
|
||||||
|
start = ast.builder_span(&state.builder, callee).start,
|
||||||
|
end = close.span.end,
|
||||||
|
},
|
||||||
callee,
|
callee,
|
||||||
arguments.items,
|
arguments.items,
|
||||||
)
|
)
|
||||||
@@ -293,7 +296,11 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
return expression
|
return expression
|
||||||
}
|
}
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try diagnostics.add(
|
||||||
|
state.diagnostics,
|
||||||
|
.unexpected_token,
|
||||||
|
current(state).span,
|
||||||
|
)
|
||||||
return try ast.add_invalid_expr(&state.builder, ast.Span{
|
return try ast.add_invalid_expr(&state.builder, ast.Span{
|
||||||
start = token.span.start,
|
start = token.span.start,
|
||||||
end = ast.builder_span(&state.builder, expression).end,
|
end = ast.builder_span(&state.builder, expression).end,
|
||||||
@@ -321,9 +328,9 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
else
|
else
|
||||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||||
|
|
||||||
if (token.kind != .newline and token.kind != .comma and token.kind != .close_paren and token.kind != .close_curly and token.kind != .eof) {
|
if (token.kind != .newline and token.kind != .comma and
|
||||||
state.next_token += 1
|
token.kind != .close_paren and token.kind != .close_curly and
|
||||||
}
|
token.kind != .eof) state.next_token += 1
|
||||||
|
|
||||||
return try ast.add_invalid_expr(&state.builder, token.span, diagnostic)
|
return try ast.add_invalid_expr(&state.builder, token.span, diagnostic)
|
||||||
}
|
}
|
||||||
@@ -331,14 +338,20 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
recover_decl proc(state @mut State, start usize) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
recover_decl proc(
|
||||||
|
state @mut State,
|
||||||
|
start usize,
|
||||||
|
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||||
span :: synchronize(state, start, false)
|
span :: synchronize(state, start, false)
|
||||||
return try ast.add_invalid_decl(&state.builder, span, diagnostic)
|
return try ast.add_invalid_decl(&state.builder, span, diagnostic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
recover_statement proc(state @mut State, start usize) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
recover_statement proc(
|
||||||
|
state @mut State,
|
||||||
|
start usize,
|
||||||
|
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||||
span :: synchronize(state, start, true)
|
span :: synchronize(state, start, true)
|
||||||
return try ast.add_invalid_stmt(&state.builder, span, diagnostic)
|
return try ast.add_invalid_stmt(&state.builder, span, diagnostic)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import "@source/lexer"
|
|||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
|
|
||||||
parses_expression_precedence_through_tree_views test {
|
parses_expression_precedence_through_tree_views test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source :: "value :: 1 + 2 * 3\n"
|
source :: "value :: 1 + 2 * 3\n"
|
||||||
@@ -16,7 +16,7 @@ parses_expression_precedence_through_tree_views test {
|
|||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(1, ast.package_decl_count(&tree))
|
try testing.expect_equal(1, ast.package_decl_count(&tree))
|
||||||
@@ -34,8 +34,8 @@ parses_expression_precedence_through_tree_views test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parses_grouped_expression_precedence test {
|
parses_grouped_expression_precedence test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source :: "value :: a + ((b - c)) * 2 / 3\n"
|
source :: "value :: a + ((b - c)) * 2 / 3\n"
|
||||||
@@ -43,7 +43,7 @@ parses_grouped_expression_precedence test {
|
|||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
@@ -65,8 +65,8 @@ parses_grouped_expression_precedence test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parses_function_structure_through_tree_views test {
|
parses_function_structure_through_tree_views test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -77,7 +77,7 @@ parses_function_structure_through_tree_views test {
|
|||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
declaration :: ast.package_decl(&tree, 0)
|
declaration :: ast.package_decl(&tree, 0)
|
||||||
@@ -98,8 +98,8 @@ parses_function_structure_through_tree_views test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recovers_invalid_declaration_and_preserves_sibling test {
|
recovers_invalid_declaration_and_preserves_sibling test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source :: "@ broken\nvalid :: 1\n"
|
source :: "@ broken\nvalid :: 1\n"
|
||||||
@@ -107,7 +107,7 @@ recovers_invalid_declaration_and_preserves_sibling test {
|
|||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||||
@@ -122,8 +122,8 @@ recovers_invalid_declaration_and_preserves_sibling test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recovers_malformed_groups_and_preserves_following_statements test {
|
recovers_malformed_groups_and_preserves_following_statements test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -138,7 +138,7 @@ recovers_malformed_groups_and_preserves_following_statements test {
|
|||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||||
@@ -161,8 +161,8 @@ recovers_malformed_groups_and_preserves_following_statements test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parses_call_statements_and_postfix_precedence test {
|
parses_call_statements_and_postfix_precedence test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -174,7 +174,7 @@ parses_call_statements_and_postfix_precedence test {
|
|||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
@@ -185,7 +185,10 @@ parses_call_statements_and_postfix_precedence test {
|
|||||||
ping :: ast.statement_expr(&tree, ping_stmt)
|
ping :: ast.statement_expr(&tree, ping_stmt)
|
||||||
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, ping))
|
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, ping))
|
||||||
try testing.expect_equal(0, ast.call_arg_count(&tree, ping))
|
try testing.expect_equal(0, ast.call_arg_count(&tree, ping))
|
||||||
try testing.expect_equal("ping", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, ping).callee)))
|
try testing.expect_equal(
|
||||||
|
"ping",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, ping).callee)),
|
||||||
|
)
|
||||||
try testing.expect_equal("ping()", ast.source_text(&tree, ast.call_expr(&tree, ping).span))
|
try testing.expect_equal("ping()", ast.source_text(&tree, ast.call_expr(&tree, ping).span))
|
||||||
|
|
||||||
result :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
|
result :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
|
||||||
@@ -198,11 +201,20 @@ parses_call_statements_and_postfix_precedence test {
|
|||||||
try testing.expect_equal(2, ast.call_arg_count(&tree, call))
|
try testing.expect_equal(2, ast.call_arg_count(&tree, call))
|
||||||
factory :: ast.call_expr(&tree, call).callee
|
factory :: ast.call_expr(&tree, call).callee
|
||||||
try testing.expect_equal(0, ast.call_arg_count(&tree, factory))
|
try testing.expect_equal(0, ast.call_arg_count(&tree, factory))
|
||||||
try testing.expect_equal("factory", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, factory).callee)))
|
try testing.expect_equal(
|
||||||
|
"factory",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, factory).callee)),
|
||||||
|
)
|
||||||
nested :: ast.call_arg(&tree, call, 0)
|
nested :: ast.call_arg(&tree, call, 0)
|
||||||
try testing.expect_equal(2, ast.call_arg_count(&tree, nested))
|
try testing.expect_equal(2, ast.call_arg_count(&tree, nested))
|
||||||
try testing.expect_equal("add", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, nested).callee)))
|
try testing.expect_equal(
|
||||||
try testing.expect_equal("1", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, nested, 0))))
|
"add",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, nested).callee)),
|
||||||
|
)
|
||||||
|
try testing.expect_equal(
|
||||||
|
"1",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, nested, 0))),
|
||||||
|
)
|
||||||
multiply :: ast.binary_expr(&tree, ast.call_arg(&tree, nested, 1))
|
multiply :: ast.binary_expr(&tree, ast.call_arg(&tree, nested, 1))
|
||||||
try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator)
|
try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator)
|
||||||
try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, multiply.left)))
|
try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, multiply.left)))
|
||||||
@@ -213,14 +225,20 @@ parses_call_statements_and_postfix_precedence test {
|
|||||||
try testing.expect_equal("5", ast.source_text(&tree, ast.node_span(&tree, grouped.right)))
|
try testing.expect_equal("5", ast.source_text(&tree, ast.node_span(&tree, grouped.right)))
|
||||||
|
|
||||||
handler :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2))
|
handler :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2))
|
||||||
try testing.expect_equal("handler", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, handler).callee)))
|
try testing.expect_equal(
|
||||||
|
"handler",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, handler).callee)),
|
||||||
|
)
|
||||||
try testing.expect_equal(1, ast.call_arg_count(&tree, handler))
|
try testing.expect_equal(1, ast.call_arg_count(&tree, handler))
|
||||||
try testing.expect_equal("result", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, handler, 0))))
|
try testing.expect_equal(
|
||||||
|
"result",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, handler, 0))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
recovers_malformed_calls_and_preserves_siblings test {
|
recovers_malformed_calls_and_preserves_siblings test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -235,7 +253,7 @@ recovers_malformed_calls_and_preserves_siblings test {
|
|||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||||
@@ -245,16 +263,24 @@ recovers_malformed_calls_and_preserves_siblings test {
|
|||||||
for ([0, 2]) |index| {
|
for ([0, 2]) |index| {
|
||||||
statement :: ast.block_statement(&tree, function.body, usize(index))
|
statement :: ast.block_statement(&tree, function.body, usize(index))
|
||||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, statement))
|
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, statement))
|
||||||
try testing.expect(diagnostics.get(&diagnostic_store, ast.invalid(&tree, statement).diagnostic) != null)
|
try testing.expect(
|
||||||
|
diagnostics.get(&diagnostic_store, ast.invalid(&tree, statement).diagnostic) != null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
good :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 1))
|
good :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 1))
|
||||||
try testing.expect_equal("good", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, good).callee)))
|
try testing.expect_equal(
|
||||||
|
"good",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, good).callee)),
|
||||||
|
)
|
||||||
after :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 3))
|
after :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 3))
|
||||||
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
||||||
holes :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 4))
|
holes :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 4))
|
||||||
try testing.expect_equal(2, ast.call_arg_count(&tree, holes))
|
try testing.expect_equal(2, ast.call_arg_count(&tree, holes))
|
||||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.call_arg(&tree, holes, 0)))
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.call_arg(&tree, holes, 0)))
|
||||||
try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, holes, 1))))
|
try testing.expect_equal(
|
||||||
|
"2",
|
||||||
|
ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, holes, 1))),
|
||||||
|
)
|
||||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||||
try testing.expect_equal(0, ast.call_arg_count(&tree, tail.initializer))
|
try testing.expect_equal(0, ast.call_arg_count(&tree, tail.initializer))
|
||||||
|
|||||||
+10
-4
@@ -35,6 +35,8 @@ Storage :: struct {
|
|||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! owns binding storage and retains its allocator; release with deinit.
|
||||||
|
#! node and diagnostic IDs refer to the caller's Tree and diagnostics.Store.
|
||||||
Resolution :: struct { storage Storage }
|
Resolution :: struct { storage Storage }
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -51,10 +53,13 @@ State :: struct {
|
|||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrows tree and diagnostic_store during resolution, without retaining their storage.
|
||||||
|
#! allocator supplies caller-owned bindings and temporary scopes released before return.
|
||||||
|
#! release the returned Resolution with deinit, which uses the retained allocator.
|
||||||
resolve proc(
|
resolve proc(
|
||||||
|
allocator mem.Allocator,
|
||||||
tree @ast.Tree,
|
tree @ast.Tree,
|
||||||
diagnostic_store @mut diagnostics.Store,
|
diagnostic_store @mut diagnostics.Store,
|
||||||
allocator mem.Allocator,
|
|
||||||
) Resolution ! (mem.AllocError | diagnostics.CapacityError) {
|
) Resolution ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
bindings :: try mem.alloc(Binding, allocator, ast.node_count(tree))
|
bindings :: try mem.alloc(Binding, allocator, ast.node_count(tree))
|
||||||
errdefer mem.free(allocator, bindings)
|
errdefer mem.free(allocator, bindings)
|
||||||
@@ -90,6 +95,7 @@ resolve proc(
|
|||||||
return Resolution{ storage = Storage{ bindings = bindings, allocator = allocator } }
|
return Resolution{ storage = Storage{ bindings = bindings, allocator = allocator } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases binding storage with the allocator retained by resolve.
|
||||||
deinit proc(resolution @mut Resolution) void {
|
deinit proc(resolution @mut Resolution) void {
|
||||||
mem.free(resolution.storage.allocator, resolution.storage.bindings)
|
mem.free(resolution.storage.allocator, resolution.storage.bindings)
|
||||||
}
|
}
|
||||||
@@ -141,7 +147,7 @@ predeclare proc(
|
|||||||
value := Binding{ kind = .declaration, data = BindingData{ declaration = id } }
|
value := Binding{ kind = .declaration, data = BindingData{ declaration = id } }
|
||||||
if lookup(scope.parent, name) |_| {
|
if lookup(scope.parent, name) |_| {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .duplicate_name, name_span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .duplicate_name, name_span)
|
||||||
# Poison the inner name rather than silently binding uses to its ancestor.
|
# poison the inner name rather than silently binding uses to its ancestor.
|
||||||
value = Binding{ kind = .invalid, data = BindingData{ diagnostic = diagnostic } }
|
value = Binding{ kind = .invalid, data = BindingData{ diagnostic = diagnostic } }
|
||||||
}
|
}
|
||||||
try put_name(scope, name, value)
|
try put_name(scope, name, value)
|
||||||
@@ -156,7 +162,7 @@ resolve_function proc(
|
|||||||
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
function :: ast.function_decl(tree, id)
|
function :: ast.function_decl(tree, id)
|
||||||
# Signatures cannot see parameters or body-local declarations.
|
# signatures cannot see parameters or body-local declarations.
|
||||||
try resolve_expr(state, package_scope, function.return_type)
|
try resolve_expr(state, package_scope, function.return_type)
|
||||||
for (0..ast.function_param_count(tree, id)) |i| {
|
for (0..ast.function_param_count(tree, id)) |i| {
|
||||||
parameter :: ast.function_param(tree, id, i)
|
parameter :: ast.function_param(tree, id, i)
|
||||||
@@ -212,7 +218,7 @@ resolve_expr proc(
|
|||||||
scope @Scope,
|
scope @Scope,
|
||||||
id ast.NodeId,
|
id ast.NodeId,
|
||||||
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
# Grouped parameters share their type node, including any diagnostic it owns.
|
# grouped parameters share their type node, including any diagnostic it owns.
|
||||||
if (state.bindings[usize(id)].kind != .none) return
|
if (state.bindings[usize(id)].kind != .none) return
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import "@source/parser"
|
|||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
|
|
||||||
resolves_forward_references_recursion_and_builtin_signatures test {
|
resolves_forward_references_recursion_and_builtin_signatures test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -26,9 +26,9 @@ resolves_forward_references_recursion_and_builtin_signatures test {
|
|||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
tree := try parser.parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
resolution := try resolve(&tree, &diagnostic_store, mem.c_allocator)
|
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
defer deinit(&resolution)
|
defer deinit(&resolution)
|
||||||
|
|
||||||
first :: ast.package_decl(&tree, 0)
|
first :: ast.package_decl(&tree, 0)
|
||||||
@@ -85,10 +85,22 @@ resolves_forward_references_recursion_and_builtin_signatures test {
|
|||||||
} else {
|
} else {
|
||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
try testing.expect_equal(BindingKind.builtin, binding(&resolution, ast.param(&tree, q).type).kind)
|
try testing.expect_equal(
|
||||||
try testing.expect_equal(BuiltinType.float, binding(&resolution, ast.param(&tree, q).type).data.builtin)
|
BindingKind.builtin,
|
||||||
try testing.expect_equal(BindingKind.builtin, binding(&resolution, ast.param(&tree, s).type).kind)
|
binding(&resolution, ast.param(&tree, q).type).kind,
|
||||||
try testing.expect_equal(BuiltinType.string, binding(&resolution, ast.param(&tree, s).type).data.builtin)
|
)
|
||||||
|
try testing.expect_equal(
|
||||||
|
BuiltinType.float,
|
||||||
|
binding(&resolution, ast.param(&tree, q).type).data.builtin,
|
||||||
|
)
|
||||||
|
try testing.expect_equal(
|
||||||
|
BindingKind.builtin,
|
||||||
|
binding(&resolution, ast.param(&tree, s).type).kind,
|
||||||
|
)
|
||||||
|
try testing.expect_equal(
|
||||||
|
BuiltinType.string,
|
||||||
|
binding(&resolution, ast.param(&tree, s).type).data.builtin,
|
||||||
|
)
|
||||||
return_types [2]ast.NodeId :: [alpha_view.return_type, beta_view.return_type]
|
return_types [2]ast.NodeId :: [alpha_view.return_type, beta_view.return_type]
|
||||||
for return_types |id| {
|
for return_types |id| {
|
||||||
try testing.expect_equal(BindingKind.builtin, binding(&resolution, id).kind)
|
try testing.expect_equal(BindingKind.builtin, binding(&resolution, id).kind)
|
||||||
@@ -98,8 +110,8 @@ resolves_forward_references_recursion_and_builtin_signatures test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rejects_shadowing_and_duplicates_without_cascades test {
|
rejects_shadowing_and_duplicates_without_cascades test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -121,10 +133,10 @@ rejects_shadowing_and_duplicates_without_cascades test {
|
|||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
tree := try parser.parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
resolution := try resolve(&tree, &diagnostic_store, mem.c_allocator)
|
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
defer deinit(&resolution)
|
defer deinit(&resolution)
|
||||||
|
|
||||||
function :: ast.package_decl(&tree, 0)
|
function :: ast.package_decl(&tree, 0)
|
||||||
@@ -140,34 +152,64 @@ rejects_shadowing_and_duplicates_without_cascades test {
|
|||||||
local_p :: ast.block_statement(&tree, view.body, 2)
|
local_p :: ast.block_statement(&tree, view.body, 2)
|
||||||
local_int :: ast.block_statement(&tree, view.body, 3)
|
local_int :: ast.block_statement(&tree, view.body, 3)
|
||||||
local_pkg :: ast.block_statement(&tree, view.body, 4)
|
local_pkg :: ast.block_statement(&tree, view.body, 4)
|
||||||
rejected [8]ast.NodeId :: [outer_param, duplicate_p, first_outer, second_outer, local_p, local_int, local_pkg, second_same]
|
rejected [8]ast.NodeId :: [
|
||||||
|
outer_param,
|
||||||
|
duplicate_p,
|
||||||
|
first_outer,
|
||||||
|
second_outer,
|
||||||
|
local_p,
|
||||||
|
local_int,
|
||||||
|
local_pkg,
|
||||||
|
second_same,
|
||||||
|
]
|
||||||
for rejected |id| {
|
for rejected |id| {
|
||||||
invalid_binding :: binding(&resolution, id)
|
invalid_binding :: binding(&resolution, id)
|
||||||
try testing.expect_equal(BindingKind.invalid, invalid_binding.kind)
|
try testing.expect_equal(BindingKind.invalid, invalid_binding.kind)
|
||||||
if (diagnostics.get(&diagnostic_store, invalid_binding.data.diagnostic)) |diagnostic| {
|
if (diagnostics.get(&diagnostic_store, invalid_binding.data.diagnostic)) |diagnostic| {
|
||||||
try testing.expect_equal(diagnostics.Code.duplicate_name, diagnostic.code)
|
try testing.expect_equal(diagnostics.Code.duplicate_name, diagnostic.code)
|
||||||
name :: if (id == outer_param or id == duplicate_p) ast.param(&tree, id).name else ast.statement_decl(&tree, id).name
|
name :: if (id == outer_param or id == duplicate_p)
|
||||||
|
ast.param(&tree, id).name
|
||||||
|
else
|
||||||
|
ast.statement_decl(&tree, id).name
|
||||||
try testing.expect_equal(name.start, diagnostic.span.start)
|
try testing.expect_equal(name.start, diagnostic.span.start)
|
||||||
try testing.expect_equal(name.end, diagnostic.span.end)
|
try testing.expect_equal(name.end, diagnostic.span.end)
|
||||||
} else {
|
} else {
|
||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try testing.expect(binding(&resolution, first_outer).data.diagnostic != binding(&resolution, second_outer).data.diagnostic)
|
try testing.expect(
|
||||||
try testing.expect(binding(&resolution, outer_param).data.diagnostic != binding(&resolution, first_outer).data.diagnostic)
|
binding(&resolution, first_outer).data.diagnostic !=
|
||||||
try testing.expect(binding(&resolution, duplicate_p).data.diagnostic != binding(&resolution, local_p).data.diagnostic)
|
binding(&resolution, second_outer).data.diagnostic,
|
||||||
|
)
|
||||||
|
try testing.expect(
|
||||||
|
binding(&resolution, outer_param).data.diagnostic !=
|
||||||
|
binding(&resolution, first_outer).data.diagnostic,
|
||||||
|
)
|
||||||
|
try testing.expect(
|
||||||
|
binding(&resolution, duplicate_p).data.diagnostic !=
|
||||||
|
binding(&resolution, local_p).data.diagnostic,
|
||||||
|
)
|
||||||
outer_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 5))
|
outer_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 5))
|
||||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, outer_ref).kind)
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, outer_ref).kind)
|
||||||
try testing.expect_equal(binding(&resolution, outer_param).data.diagnostic, binding(&resolution, outer_ref).data.diagnostic)
|
try testing.expect_equal(
|
||||||
|
binding(&resolution, outer_param).data.diagnostic,
|
||||||
|
binding(&resolution, outer_ref).data.diagnostic,
|
||||||
|
)
|
||||||
p_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 6))
|
p_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 6))
|
||||||
try testing.expect_equal(BindingKind.declaration, binding(&resolution, p_ref).kind)
|
try testing.expect_equal(BindingKind.declaration, binding(&resolution, p_ref).kind)
|
||||||
try testing.expect_equal(p, binding(&resolution, p_ref).data.declaration)
|
try testing.expect_equal(p, binding(&resolution, p_ref).data.declaration)
|
||||||
int_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 7))
|
int_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 7))
|
||||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, int_ref).kind)
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, int_ref).kind)
|
||||||
try testing.expect_equal(binding(&resolution, local_int).data.diagnostic, binding(&resolution, int_ref).data.diagnostic)
|
try testing.expect_equal(
|
||||||
|
binding(&resolution, local_int).data.diagnostic,
|
||||||
|
binding(&resolution, int_ref).data.diagnostic,
|
||||||
|
)
|
||||||
pkg_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 8))
|
pkg_ref :: ast.statement_expr(&tree, ast.block_statement(&tree, view.body, 8))
|
||||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, pkg_ref).kind)
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, pkg_ref).kind)
|
||||||
try testing.expect_equal(binding(&resolution, local_pkg).data.diagnostic, binding(&resolution, pkg_ref).data.diagnostic)
|
try testing.expect_equal(
|
||||||
|
binding(&resolution, local_pkg).data.diagnostic,
|
||||||
|
binding(&resolution, pkg_ref).data.diagnostic,
|
||||||
|
)
|
||||||
try testing.expect_equal(BindingKind.declaration, binding(&resolution, view.return_type).kind)
|
try testing.expect_equal(BindingKind.declaration, binding(&resolution, view.return_type).kind)
|
||||||
try testing.expect_equal(outer, binding(&resolution, view.return_type).data.declaration)
|
try testing.expect_equal(outer, binding(&resolution, view.return_type).data.declaration)
|
||||||
parameter_type :: ast.param(&tree, outer_param).type
|
parameter_type :: ast.param(&tree, outer_param).type
|
||||||
@@ -189,8 +231,8 @@ rejects_shadowing_and_duplicates_without_cascades test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
|
preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
|
||||||
strpool.STRINGS = strpool.init(mem.c_allocator)
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strpool.STRINGS)
|
defer strpool.deinit(&strpool.strings)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
@@ -205,7 +247,7 @@ preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
|
|||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
tree := try parser.parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
syntax_count :: diagnostics.count(&diagnostic_store)
|
syntax_count :: diagnostics.count(&diagnostic_store)
|
||||||
invalid_decl :: ast.package_decl(&tree, 0)
|
invalid_decl :: ast.package_decl(&tree, 0)
|
||||||
@@ -216,7 +258,7 @@ preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
|
|||||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, empty))
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, empty))
|
||||||
invalid_statement :: ast.block_statement(&tree, view.body, 4)
|
invalid_statement :: ast.block_statement(&tree, view.body, 4)
|
||||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, invalid_statement))
|
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, invalid_statement))
|
||||||
resolution := try resolve(&tree, &diagnostic_store, mem.c_allocator)
|
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
defer deinit(&resolution)
|
defer deinit(&resolution)
|
||||||
|
|
||||||
invalid_nodes [3]ast.NodeId :: [invalid_decl, empty, invalid_statement]
|
invalid_nodes [3]ast.NodeId :: [invalid_decl, empty, invalid_statement]
|
||||||
@@ -229,8 +271,14 @@ preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
|
|||||||
a :: ast.function_param(&tree, function, 0)
|
a :: ast.function_param(&tree, function, 0)
|
||||||
b :: ast.function_param(&tree, function, 1)
|
b :: ast.function_param(&tree, function, 1)
|
||||||
shared_type :: ast.param(&tree, a).type
|
shared_type :: ast.param(&tree, a).type
|
||||||
try testing.expect_equal(binding(&resolution, shared_type).data.diagnostic, binding(&resolution, ast.param(&tree, b).type).data.diagnostic)
|
try testing.expect_equal(
|
||||||
bad :: ast.binary_expr(&tree, ast.statement_decl(&tree, ast.block_statement(&tree, view.body, 1)).initializer)
|
binding(&resolution, shared_type).data.diagnostic,
|
||||||
|
binding(&resolution, ast.param(&tree, b).type).data.diagnostic,
|
||||||
|
)
|
||||||
|
bad :: ast.binary_expr(
|
||||||
|
&tree,
|
||||||
|
ast.statement_decl(&tree, ast.block_statement(&tree, view.body, 1)).initializer,
|
||||||
|
)
|
||||||
unknown_nodes [3]ast.NodeId :: [shared_type, bad.left, bad.right]
|
unknown_nodes [3]ast.NodeId :: [shared_type, bad.left, bad.right]
|
||||||
for unknown_nodes |id| {
|
for unknown_nodes |id| {
|
||||||
invalid_binding :: binding(&resolution, id)
|
invalid_binding :: binding(&resolution, id)
|
||||||
@@ -243,7 +291,10 @@ preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
|
|||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try testing.expect(binding(&resolution, bad.left).data.diagnostic != binding(&resolution, bad.right).data.diagnostic)
|
try testing.expect(
|
||||||
|
binding(&resolution, bad.left).data.diagnostic !=
|
||||||
|
binding(&resolution, bad.right).data.diagnostic,
|
||||||
|
)
|
||||||
valid :: ast.block_statement(&tree, view.body, 2)
|
valid :: ast.block_statement(&tree, view.body, 2)
|
||||||
later :: ast.block_statement(&tree, view.body, 3)
|
later :: ast.block_statement(&tree, view.body, 3)
|
||||||
valid_ref :: ast.statement_decl(&tree, valid).initializer
|
valid_ref :: ast.statement_decl(&tree, valid).initializer
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ StringId :: alias u32
|
|||||||
|
|
||||||
NO_STR :: maxval!(StringId)
|
NO_STR :: maxval!(StringId)
|
||||||
|
|
||||||
# cross-cutting concern, hence global singleton (owned by main.hon)
|
#! global pool owned by main.hon; initialize before scanning and release with deinit.
|
||||||
STRINGS StringPool := undefined
|
strings StringPool := undefined
|
||||||
|
|
||||||
|
#! retains its allocator and owns interned copies; release all storage with deinit.
|
||||||
StringPool :: struct {
|
StringPool :: struct {
|
||||||
ids hashmap.StringHashMap(StringId) # string → id
|
ids hashmap.StringHashMap(StringId) # string → id
|
||||||
strings arraylist.ArrayList([]u8) # id → owned string
|
strings arraylist.ArrayList([]u8) # id → owned string
|
||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! retains allocator for interned copies and indexes; the caller owns the pool.
|
||||||
init proc(allocator mem.Allocator) StringPool {
|
init proc(allocator mem.Allocator) StringPool {
|
||||||
return StringPool{
|
return StringPool{
|
||||||
ids = hashmap.init(allocator),
|
ids = hashmap.init(allocator),
|
||||||
@@ -25,19 +27,19 @@ init proc(allocator mem.Allocator) StringPool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! releases interned strings and indexes with the retained allocator.
|
||||||
deinit proc(pool @mut StringPool) void {
|
deinit proc(pool @mut StringPool) void {
|
||||||
hashmap.deinit(&pool.ids)
|
hashmap.deinit(&pool.ids)
|
||||||
for pool.strings.items |str| {
|
for (pool.strings.items) |str| mem.free(pool.allocator, str)
|
||||||
mem.free(pool.allocator, str)
|
|
||||||
}
|
|
||||||
arraylist.deinit(&pool.strings)
|
arraylist.deinit(&pool.strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! copies new strings into pool-owned storage; IDs and borrowed strings last until deinit.
|
||||||
intern proc(pool @mut StringPool, str []u8) StringId ! (mem.AllocError | InternError) {
|
intern proc(pool @mut StringPool, str []u8) StringId ! (mem.AllocError | InternError) {
|
||||||
if hashmap.get(&pool.ids, str) |id| return id
|
if hashmap.get(&pool.ids, str) |id| return id
|
||||||
|
|
||||||
if (pool.strings.items.len >= usize(NO_STR)) return .out_of_space
|
if (pool.strings.items.len >= usize(NO_STR)) return .out_of_space
|
||||||
id StringId :: StringId(pool.strings.items.len)
|
id :: StringId(pool.strings.items.len)
|
||||||
|
|
||||||
owned_str []mut u8 :: try mem.alloc(u8, pool.allocator, str.len)
|
owned_str []mut u8 :: try mem.alloc(u8, pool.allocator, str.len)
|
||||||
errdefer mem.free(pool.allocator, owned_str)
|
errdefer mem.free(pool.allocator, owned_str)
|
||||||
@@ -56,6 +58,7 @@ intern proc(pool @mut StringPool, str []u8) StringId ! (mem.AllocError | InternE
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! returns pool-owned text borrowed until deinit; the caller must not free it.
|
||||||
get_str proc(pool @StringPool, id StringId) ?[]u8 {
|
get_str proc(pool @StringPool, id StringId) ?[]u8 {
|
||||||
if (usize(id) >= pool.strings.items.len) return null
|
if (usize(id) >= pool.strings.items.len) return null
|
||||||
return pool.strings.items[usize(id)]
|
return pool.strings.items[usize(id)]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import "@std/mem"
|
|||||||
import "@std/testing"
|
import "@std/testing"
|
||||||
|
|
||||||
handles_interning test {
|
handles_interning test {
|
||||||
pool StringPool := init(mem.c_allocator)
|
pool := init(mem.c_allocator)
|
||||||
defer deinit(&pool)
|
defer deinit(&pool)
|
||||||
|
|
||||||
input [5]mut u8 := ['h', 'e', 'l', 'l', 'o']
|
input [5]mut u8 := ['h', 'e', 'l', 'l', 'o']
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
|
|
||||||
|
#! owns item storage and retains its allocator; release storage with deinit.
|
||||||
|
#! items is a borrowed view invalidated by growth or deinit.
|
||||||
ArrayList proc($T type) type {
|
ArrayList proc($T type) type {
|
||||||
return struct {
|
return struct {
|
||||||
items []mut T
|
items []mut T
|
||||||
@@ -8,6 +10,7 @@ ArrayList proc($T type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! retain allocator for later growth and cleanup; this call does not allocate.
|
||||||
init proc($T type, allocator mem.Allocator) ArrayList(T) {
|
init proc($T type, allocator mem.Allocator) ArrayList(T) {
|
||||||
return ArrayList(T) {
|
return ArrayList(T) {
|
||||||
items = mem.empty(T),
|
items = mem.empty(T),
|
||||||
@@ -16,6 +19,7 @@ init proc($T type, allocator mem.Allocator) ArrayList(T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! release owned storage using the retained allocator and reset the list.
|
||||||
deinit proc($T type, list @mut ArrayList(T)) void {
|
deinit proc($T type, list @mut ArrayList(T)) void {
|
||||||
allocation :: list.items.ptr[..list.capacity]
|
allocation :: list.items.ptr[..list.capacity]
|
||||||
mem.free(list.allocator, allocation)
|
mem.free(list.allocator, allocation)
|
||||||
@@ -23,10 +27,13 @@ deinit proc($T type, list @mut ArrayList(T)) void {
|
|||||||
list.capacity = 0
|
list.capacity = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.AllocError {
|
#! grow owned storage with the retained allocator; growth invalidates borrowed item views.
|
||||||
if min_capacity <= list.capacity {
|
reserve proc(
|
||||||
return
|
$T type,
|
||||||
}
|
list @mut ArrayList(T),
|
||||||
|
min_capacity usize,
|
||||||
|
) void ! mem.AllocError {
|
||||||
|
if (min_capacity <= list.capacity) return
|
||||||
|
|
||||||
new_capacity := 8
|
new_capacity := 8
|
||||||
if list.capacity >= 8 {
|
if list.capacity >= 8 {
|
||||||
@@ -41,9 +48,9 @@ reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.All
|
|||||||
new_capacity = min_capacity
|
new_capacity = min_capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
length usize :: list.items.len
|
length :: list.items.len
|
||||||
allocation []mut T :: list.items.ptr[..list.capacity]
|
allocation :: list.items.ptr[..list.capacity]
|
||||||
grown []mut T :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
|
grown :: mem.realloc(list.allocator, allocation, new_capacity) catch |_| {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
list.items = grown.ptr[..length]
|
list.items = grown.ptr[..length]
|
||||||
@@ -52,7 +59,7 @@ reserve proc($T type, list @mut ArrayList(T), min_capacity usize) void ! mem.All
|
|||||||
}
|
}
|
||||||
|
|
||||||
append proc($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
append proc($T type, list @mut ArrayList(T), value T) void ! mem.AllocError {
|
||||||
length usize :: list.items.len
|
length :: list.items.len
|
||||||
if length == maxval!(usize) {
|
if length == maxval!(usize) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -1,11 +1,11 @@
|
|||||||
# Build configuration surface for `brolang build` (v0).
|
#! build configuration surface for `brolang build` (v0).
|
||||||
#
|
#!
|
||||||
# A project's `build.bro` imports this module and declares a top-level constant
|
#! a project's `build.bro` imports this module and declares a top-level constant
|
||||||
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
|
#! named `config` of type `BuildConfig`. `brolang build [root]` type-checks
|
||||||
# build.bro, reads the config, and writes root/build/name.
|
#! build.bro, reads the config, and writes root/build/name.
|
||||||
#
|
#!
|
||||||
# Declarative and literal-only: one executable per build. List fields take an
|
#! declarative and literal-only: one executable per build. list fields take an
|
||||||
# address-of an array literal (`&["raylib"]`) and default to empty.
|
#! address-of an array literal (`&["raylib"]`) and default to empty.
|
||||||
BuildConfig :: struct {
|
BuildConfig :: struct {
|
||||||
name []u8 # output executable name under root/build
|
name []u8 # output executable name under root/build
|
||||||
source []u8 # program package directory, relative to build.bro
|
source []u8 # program package directory, relative to build.bro
|
||||||
|
|||||||
+5
-11
@@ -17,23 +17,17 @@ print proc($format []u8, $Args type, args Args) void {
|
|||||||
handle = io.Handle{ file_desc = c_int(io.Stream.stderr) },
|
handle = io.Handle{ file_desc = c_int(io.Stream.stderr) },
|
||||||
write = write,
|
write = write,
|
||||||
}
|
}
|
||||||
io.print(writer, format, Args, args) catch |_| {}
|
io.print(format, Args, writer, args) catch |_| {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
write proc(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
|
write proc(_ ?@mut anyopaque, handle io.Handle, bytes []u8) usize ! io.WriteError {
|
||||||
request := bytes.len
|
request := bytes.len
|
||||||
maximum :: usize(maxval!(c_long))
|
maximum :: usize(maxval!(c_long))
|
||||||
if request > maximum {
|
if (request > maximum) request = maximum
|
||||||
request = maximum
|
|
||||||
}
|
|
||||||
while true {
|
while true {
|
||||||
count :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
|
count :: c.write(handle.file_desc, bytes.ptr, c_ulong(request))
|
||||||
if count >= 0 {
|
if (count >= 0) return usize(count)
|
||||||
return usize(count)
|
if (c.__error()?^ != c.EINTR) return .write_failed
|
||||||
}
|
|
||||||
if c.__error()?^ != c.EINTR {
|
|
||||||
return .write_failed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ EnumMap proc($E, $V type) type {
|
|||||||
match typeinfo!(E) {
|
match typeinfo!(E) {
|
||||||
.enum |info|: return struct {
|
.enum |info|: return struct {
|
||||||
present [info.fields.len]mut bool # fixme: replace with bitset
|
present [info.fields.len]mut bool # fixme: replace with bitset
|
||||||
values [info.fields.len]mut V
|
values [info.fields.len]mut V
|
||||||
}
|
}
|
||||||
else: compile_error!("EnumMap key must be an enum")
|
else: compile_error!("EnumMap key must be an enum")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/testing"
|
import "@std/testing"
|
||||||
|
|
||||||
|
@hide:file
|
||||||
TestEnum :: enum(u8) {
|
TestEnum :: enum(u8) {
|
||||||
ident = 3
|
ident = 3
|
||||||
int = 8
|
int = 8
|
||||||
@@ -8,7 +9,7 @@ TestEnum :: enum(u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handles_sparse_enum_get test {
|
handles_sparse_enum_get test {
|
||||||
names EnumMap(TestEnum, []u8) := init({
|
names EnumMap(TestEnum, []u8) :: init({
|
||||||
ident = "identifier",
|
ident = "identifier",
|
||||||
int = "integer",
|
int = "integer",
|
||||||
})
|
})
|
||||||
|
|||||||
+8
-10
@@ -11,6 +11,8 @@ Entry proc($K, $V type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! owns entry storage and retains its allocator; release storage with deinit.
|
||||||
|
#! keys and values are stored by value; any resources they reference remain caller-owned.
|
||||||
HashMap proc(
|
HashMap proc(
|
||||||
$K, $V type,
|
$K, $V type,
|
||||||
$hash_key proc(key K) usize,
|
$hash_key proc(key K) usize,
|
||||||
@@ -27,6 +29,7 @@ StringHashMap proc($V type) type {
|
|||||||
return HashMap([]u8, V, str_hash, str_eql)
|
return HashMap([]u8, V, str_hash, str_eql)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! retain allocator for later growth and cleanup; this call does not allocate.
|
||||||
init proc(
|
init proc(
|
||||||
$K, $V type,
|
$K, $V type,
|
||||||
$hash_key proc(key K) usize,
|
$hash_key proc(key K) usize,
|
||||||
@@ -40,7 +43,7 @@ init proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#! free the entries in the hash map.
|
#! free owned entry storage using the retained allocator, not resources referenced by keys or values.
|
||||||
#! note: this operation invalidates the map.
|
#! note: this operation invalidates the map.
|
||||||
deinit proc(
|
deinit proc(
|
||||||
$K, $V type,
|
$K, $V type,
|
||||||
@@ -81,21 +84,17 @@ put proc(
|
|||||||
key K,
|
key K,
|
||||||
value V,
|
value V,
|
||||||
) void ! (PutError | mem.AllocError) {
|
) void ! (PutError | mem.AllocError) {
|
||||||
# check grow
|
|
||||||
threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
|
threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
|
||||||
if (map.entries.len == 0 or map.count + 1 > threshold) {
|
if (map.entries.len == 0 or map.count + 1 > threshold) {
|
||||||
old_entries :: map.entries
|
old_entries :: map.entries
|
||||||
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
|
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
|
||||||
new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
|
new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
|
||||||
|
|
||||||
# zero new entries
|
|
||||||
for (0..new_entries.len) |i| new_entries[i].hash = 0
|
for (0..new_entries.len) |i| new_entries[i].hash = 0
|
||||||
|
|
||||||
# move old entries
|
|
||||||
for old_entries |entry| {
|
for old_entries |entry| {
|
||||||
if (entry.hash == 0) continue
|
if (entry.hash == 0) continue
|
||||||
|
|
||||||
# find an empty slot
|
|
||||||
idx := entry.hash & (new_entries.len - 1)
|
idx := entry.hash & (new_entries.len - 1)
|
||||||
while new_entries[idx].hash != 0 {
|
while new_entries[idx].hash != 0 {
|
||||||
idx = (idx + 1) & (new_entries.len - 1)
|
idx = (idx + 1) & (new_entries.len - 1)
|
||||||
@@ -108,7 +107,6 @@ put proc(
|
|||||||
mem.free(map.allocator, old_entries)
|
mem.free(map.allocator, old_entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
# put new entry
|
|
||||||
hash :: normalize(hash_key(key))
|
hash :: normalize(hash_key(key))
|
||||||
idx := hash & (map.entries.len - 1)
|
idx := hash & (map.entries.len - 1)
|
||||||
|
|
||||||
@@ -132,7 +130,7 @@ put proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
normalize proc(hash usize) usize {
|
normalize proc(hash usize) usize {
|
||||||
# mapping both 0 and 1 to 1 is safe because equality resolves
|
# mapping both 0 and 1 to 1 is safe because equality resolves
|
||||||
# collisions (since hash and key must both be equal).
|
# collisions (since hash and key must both be equal).
|
||||||
@@ -142,10 +140,10 @@ normalize proc(hash usize) usize {
|
|||||||
|
|
||||||
#! FNV-1a hash implementation.
|
#! FNV-1a hash implementation.
|
||||||
#! note: vulnerable to collision attacks.
|
#! note: vulnerable to collision attacks.
|
||||||
@hide
|
@hide:file
|
||||||
str_hash proc(key []u8) usize {
|
str_hash proc(key []u8) usize {
|
||||||
hash u32 := 2166136261 # offset basis
|
hash u32 := 2166136261 # offset basis
|
||||||
prime u32 := 16777619
|
prime u32 :: 16777619
|
||||||
|
|
||||||
for key |byte| {
|
for key |byte| {
|
||||||
product u64 :: u64(hash xor u32(byte)) * prime
|
product u64 :: u64(hash xor u32(byte)) * prime
|
||||||
@@ -155,5 +153,5 @@ str_hash proc(key []u8) usize {
|
|||||||
return usize(hash)
|
return usize(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
str_eql proc(a, b []u8) bool { return mem.eql(a, b) }
|
str_eql proc(a, b []u8) bool { return mem.eql(a, b) }
|
||||||
|
|||||||
+20
-8
@@ -19,15 +19,18 @@ CloseError :: enum {
|
|||||||
close_failed
|
close_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! open a caller-owned handle; release it with close.
|
||||||
open proc(io Io, path [;0]u8, mode FileMode) File ! OpenError {
|
open proc(io Io, path [;0]u8, mode FileMode) File ! OpenError {
|
||||||
handle Handle :: try io.vtable.open(io.context, path, mode)
|
handle Handle :: try io.vtable.open(io.context, path, mode)
|
||||||
return File{ io = io, handle = handle }
|
return File{ io = io, handle = handle }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! close the owned handle, invalidating its borrowed readers and writers.
|
||||||
close proc(file File) void ! CloseError {
|
close proc(file File) void ! CloseError {
|
||||||
try file.io.vtable.close(file.io.context, file.handle)
|
try file.io.vtable.close(file.io.context, file.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrow the file handle; the reader must not outlive the open file.
|
||||||
reader proc(file File) Reader {
|
reader proc(file File) Reader {
|
||||||
return Reader {
|
return Reader {
|
||||||
context = file.io.context,
|
context = file.io.context,
|
||||||
@@ -36,6 +39,7 @@ reader proc(file File) Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrow the file handle; the writer must not outlive the open file.
|
||||||
writer proc(file File) Writer {
|
writer proc(file File) Writer {
|
||||||
return Writer {
|
return Writer {
|
||||||
context = file.io.context,
|
context = file.io.context,
|
||||||
@@ -44,7 +48,8 @@ writer proc(file File) Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_read proc(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
|
@hide:file
|
||||||
|
system_read proc(_ ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError {
|
||||||
request := buffer.len
|
request := buffer.len
|
||||||
maximum usize :: usize(maxval!(c_long))
|
maximum usize :: usize(maxval!(c_long))
|
||||||
if (request > maximum) request = maximum
|
if (request > maximum) request = maximum
|
||||||
@@ -60,7 +65,8 @@ writer proc(file File) Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_write proc(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
|
@hide:file
|
||||||
|
system_write proc(_ ?@mut anyopaque, handle Handle, bytes []u8) usize ! WriteError {
|
||||||
request := bytes.len
|
request := bytes.len
|
||||||
maximum usize :: usize(maxval!(c_long))
|
maximum usize :: usize(maxval!(c_long))
|
||||||
if (request > maximum) request = maximum
|
if (request > maximum) request = maximum
|
||||||
@@ -76,7 +82,8 @@ writer proc(file File) Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_open proc(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError {
|
@hide:file
|
||||||
|
system_open proc(_ ?@mut anyopaque, path [;0]u8, mode FileMode) Handle ! OpenError {
|
||||||
flags := c.O_RDONLY
|
flags := c.O_RDONLY
|
||||||
match mode {
|
match mode {
|
||||||
.read_only: flags = c.O_RDONLY
|
.read_only: flags = c.O_RDONLY
|
||||||
@@ -90,23 +97,28 @@ writer proc(file File) Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_close proc(_ ?@mut anyopaque, handle Handle) void ! CloseError {
|
@hide:file
|
||||||
|
system_close proc(_ ?@mut anyopaque, handle Handle) void ! CloseError {
|
||||||
if (c.close(handle.file_desc) != 0) return .close_failed
|
if (c.close(handle.file_desc) != 0) return .close_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_stdin proc(_ ?@mut anyopaque) Handle {
|
@hide:file
|
||||||
|
system_stdin proc(_ ?@mut anyopaque) Handle {
|
||||||
return Handle{ file_desc = c_int(Stream.stdin) }
|
return Handle{ file_desc = c_int(Stream.stdin) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_stdout proc(_ ?@mut anyopaque) Handle {
|
@hide:file
|
||||||
|
system_stdout proc(_ ?@mut anyopaque) Handle {
|
||||||
return Handle{ file_desc = c_int(Stream.stdout) }
|
return Handle{ file_desc = c_int(Stream.stdout) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_stderr proc(_ ?@mut anyopaque) Handle {
|
@hide:file
|
||||||
|
system_stderr proc(_ ?@mut anyopaque) Handle {
|
||||||
return Handle{ file_desc = c_int(Stream.stderr) }
|
return Handle{ file_desc = c_int(Stream.stderr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide system_vtable IoVTable :: IoVTable {
|
@hide:file
|
||||||
|
system_vtable IoVTable :: IoVTable {
|
||||||
read = system_read,
|
read = system_read,
|
||||||
write = system_write,
|
write = system_write,
|
||||||
open = system_open,
|
open = system_open,
|
||||||
|
|||||||
+33
-14
@@ -1,4 +1,6 @@
|
|||||||
import "@ffi/c"
|
import "@ffi/c"
|
||||||
|
import "@std/mem"
|
||||||
|
# typeinfo! requires this import even without a qualified meta reference.
|
||||||
import "@std/meta"
|
import "@std/meta"
|
||||||
|
|
||||||
ReadError :: enum {
|
ReadError :: enum {
|
||||||
@@ -23,6 +25,7 @@ Stream :: enum(c_int) {
|
|||||||
stderr = c.STDERR_FILENO
|
stderr = c.STDERR_FILENO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! context and vtable are borrowed and must outlive derived readers and writers.
|
||||||
Io :: struct {
|
Io :: struct {
|
||||||
context ?@mut anyopaque
|
context ?@mut anyopaque
|
||||||
vtable @IoVTable
|
vtable @IoVTable
|
||||||
@@ -38,12 +41,14 @@ IoVTable :: struct {
|
|||||||
stderr @proc(context ?@mut anyopaque) Handle
|
stderr @proc(context ?@mut anyopaque) Handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrows its context and handle; it does not close the handle.
|
||||||
Reader :: struct {
|
Reader :: struct {
|
||||||
context ?@mut anyopaque
|
context ?@mut anyopaque
|
||||||
handle Handle
|
handle Handle
|
||||||
read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
|
read @proc(context ?@mut anyopaque, handle Handle, buffer []mut u8) usize ! ReadError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! borrows its context and handle; it does not close the handle.
|
||||||
Writer :: struct {
|
Writer :: struct {
|
||||||
context ?@mut anyopaque
|
context ?@mut anyopaque
|
||||||
handle Handle
|
handle Handle
|
||||||
@@ -101,7 +106,7 @@ stderr proc(io Io) Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print proc(output Writer, $format []u8, $Args type, args Args) void ! WriteError {
|
print proc($format []u8, $Args type, output Writer, args Args) void ! WriteError {
|
||||||
inline for parse_format(format.len, format, Args) |token| {
|
inline for parse_format(format.len, format, Args) |token| {
|
||||||
match token.kind {
|
match token.kind {
|
||||||
.unused: break
|
.unused: break
|
||||||
@@ -120,14 +125,19 @@ print proc(output Writer, $format []u8, $Args type, args Args) void ! WriteError
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
write_integer_signed proc(output Writer, value i64, base u64, uppercase bool) void ! WriteError {
|
write_integer_signed proc(
|
||||||
|
output Writer,
|
||||||
|
value i64,
|
||||||
|
base u64,
|
||||||
|
uppercase bool,
|
||||||
|
) void ! WriteError {
|
||||||
buffer [65]mut u8 := undefined
|
buffer [65]mut u8 := undefined
|
||||||
end := buffer.len
|
end := buffer.len
|
||||||
current := value
|
current := value
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
digit_value i64 :: rem!(current, i64(base))
|
digit_value i64 :: rem!(current, i64(base))
|
||||||
digit := if (digit_value < 0)
|
digit :: if (digit_value < 0)
|
||||||
u8(-digit_value)
|
u8(-digit_value)
|
||||||
else
|
else
|
||||||
u8(digit_value)
|
u8(digit_value)
|
||||||
@@ -152,7 +162,12 @@ write_integer_signed proc(output Writer, value i64, base u64, uppercase bool) vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
write_integer_unsigned proc(output Writer, value u64, base u64, uppercase bool) void ! WriteError {
|
write_integer_unsigned proc(
|
||||||
|
output Writer,
|
||||||
|
value u64,
|
||||||
|
base u64,
|
||||||
|
uppercase bool,
|
||||||
|
) void ! WriteError {
|
||||||
buffer [65]mut u8 := undefined
|
buffer [65]mut u8 := undefined
|
||||||
end := buffer.len
|
end := buffer.len
|
||||||
current := value
|
current := value
|
||||||
@@ -201,9 +216,7 @@ FormatToken :: struct {
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||||
tokens [N]mut FormatToken := undefined
|
tokens [N]mut FormatToken := undefined
|
||||||
for (usize(0))..format.len |index| {
|
mem.set(FormatToken, tokens[..], FormatToken{ kind = .unused, start = 0, end = 0, field = "" })
|
||||||
tokens[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
field_count := 0
|
field_count := 0
|
||||||
match typeinfo!(Args) {
|
match typeinfo!(Args) {
|
||||||
@@ -314,7 +327,7 @@ parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
if literal_start < format.len {
|
if literal_start < format.len {
|
||||||
tokens[token_count] = FormatToken{
|
tokens[token_count] = FormatToken{
|
||||||
kind = .literal,
|
kind = .literal,
|
||||||
start = literal_start,
|
start = literal_start,
|
||||||
end = format.len,
|
end = format.len,
|
||||||
field = "",
|
field = "",
|
||||||
}
|
}
|
||||||
@@ -351,7 +364,13 @@ scalar_or_distinct_type proc($T type) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
write_integer proc(output Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
|
write_integer proc(
|
||||||
|
$T type,
|
||||||
|
output Writer,
|
||||||
|
value T,
|
||||||
|
base u64,
|
||||||
|
uppercase bool,
|
||||||
|
) void ! WriteError {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.integer: if minval!(T) < 0 {
|
.integer: if minval!(T) < 0 {
|
||||||
try write_integer_signed(output, i64(value), base, uppercase)
|
try write_integer_signed(output, i64(value), base, uppercase)
|
||||||
@@ -369,7 +388,7 @@ write_integer proc(output Writer, $T type, value T, base u64, uppercase bool) vo
|
|||||||
|
|
||||||
# note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
|
# note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
|
||||||
@hide:file
|
@hide:file
|
||||||
write_float proc(output Writer, $T type, value T, scientific bool) void ! WriteError {
|
write_float proc($T type, output Writer, value T, scientific bool) void ! WriteError {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.float: {
|
.float: {
|
||||||
buffer [64]mut u8 := undefined
|
buffer [64]mut u8 := undefined
|
||||||
@@ -395,7 +414,7 @@ write_float proc(output Writer, $T type, value T, scientific bool) void ! WriteE
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
write_decimal proc(output Writer, $T type, value T) void ! WriteError {
|
write_decimal proc($T type, output Writer, value T) void ! WriteError {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.integer: try write_integer(output, value, 10, false)
|
.integer: try write_integer(output, value, 10, false)
|
||||||
.float: try write_float(output, value, false)
|
.float: try write_float(output, value, false)
|
||||||
@@ -409,13 +428,13 @@ write_decimal proc(output Writer, $T type, value T) void ! WriteError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
write_character proc(output Writer, $T type, value T) void ! WriteError {
|
write_character proc($T type, output Writer, value T) void ! WriteError {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.integer: {
|
.integer: {
|
||||||
if minval!(T) < 0 or maxval!(T) > 255 {
|
if minval!(T) < 0 or maxval!(T) > 255 {
|
||||||
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
||||||
}
|
}
|
||||||
buffer [1]u8 := [u8(value)]
|
buffer [1]u8 :: [u8(value)]
|
||||||
try write_all(output, buffer[..])
|
try write_all(output, buffer[..])
|
||||||
}
|
}
|
||||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
||||||
@@ -428,7 +447,7 @@ write_character proc(output Writer, $T type, value T) void ! WriteError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
write_default proc(output Writer, $T type, value T) void ! WriteError {
|
write_default proc($T type, output Writer, value T) void ! WriteError {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.bool: if value {
|
.bool: if value {
|
||||||
try write_all(output, "true")
|
try write_all(output, "true")
|
||||||
|
|||||||
+47
-21
@@ -4,6 +4,7 @@ AllocError :: enum {
|
|||||||
out_of_memory
|
out_of_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! allocator context and vtable are borrowed and must outlive allocations made through them.
|
||||||
Allocator :: struct {
|
Allocator :: struct {
|
||||||
context ?@mut anyopaque
|
context ?@mut anyopaque
|
||||||
vtable @AllocatorVTable
|
vtable @AllocatorVTable
|
||||||
@@ -15,23 +16,40 @@ AllocatorVTable :: struct {
|
|||||||
free @proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
|
free @proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
|
||||||
}
|
}
|
||||||
|
|
||||||
raw_alloc proc(allocator Allocator, size usize, alignment usize) ?*mut u8 {
|
#! returned storage belongs to the caller; release it with raw_free using the same allocator, size, and alignment.
|
||||||
|
raw_alloc proc(
|
||||||
|
allocator Allocator,
|
||||||
|
size usize,
|
||||||
|
alignment usize,
|
||||||
|
) ?*mut u8 {
|
||||||
return allocator.vtable.alloc(allocator.context, size, alignment)
|
return allocator.vtable.alloc(allocator.context, size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw_realloc proc(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
|
#! resize caller-owned storage with its original allocator and alignment.
|
||||||
|
#! on allocation failure with nonzero new_size, original storage remains caller-owned; release the result with raw_free.
|
||||||
|
raw_realloc proc(
|
||||||
|
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)
|
return allocator.vtable.realloc(allocator.context, memory, old_size, new_size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw_free proc(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
|
#! release caller-owned storage with its original allocator, size, and alignment.
|
||||||
|
raw_free proc(
|
||||||
|
allocator Allocator,
|
||||||
|
memory ?*mut u8,
|
||||||
|
size usize,
|
||||||
|
alignment usize,
|
||||||
|
) void {
|
||||||
allocator.vtable.free(allocator.context, memory, size, alignment)
|
allocator.vtable.free(allocator.context, memory, size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
eql proc($T type, left, right []T) bool {
|
eql proc($T type, left, right []T) bool {
|
||||||
if (left.len != right.len) return false
|
if (left.len != right.len) return false
|
||||||
for (0..left.len) |i| if (left[i] != right[i]) {
|
for (0..left.len) |i| if (left[i] != right[i]) return false
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,17 +60,18 @@ set proc($T type, destination []mut T, value T) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#! allocate memory for a slice of type `T` with `count` elements.
|
#! allocate memory for a slice of type `T` with `count` elements.
|
||||||
|
#! returned storage belongs to the caller; release it with free using the same allocator.
|
||||||
alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||||
if (count == 0) return empty_slice(T, 0)
|
if (count == 0) return empty_slice(T, 0)
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size :: sizeof!(T)
|
||||||
if (element_size == 0) return empty_slice(T, count)
|
if (element_size == 0) return empty_slice(T, count)
|
||||||
|
|
||||||
if count > divtrunc!(maxval!(usize), element_size) {
|
if count > divtrunc!(maxval!(usize), element_size) {
|
||||||
return .out_of_memory
|
return .out_of_memory
|
||||||
}
|
}
|
||||||
|
|
||||||
memory := raw_alloc(allocator, count * element_size, alignof!(T))
|
memory :: raw_alloc(allocator, count * element_size, alignof!(T))
|
||||||
if memory |bytes| {
|
if memory |bytes| {
|
||||||
pointer *mut T :: ptrcast!(bytes)
|
pointer *mut T :: ptrcast!(bytes)
|
||||||
return pointer[..count]
|
return pointer[..count]
|
||||||
@@ -64,17 +83,22 @@ alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
|||||||
#! reallocate memory for a slice of type `T` with `new_count` elements.
|
#! reallocate memory for a slice of type `T` with `new_count` elements.
|
||||||
#! reallocating with `new_count == 0` will free the memory and return an empty slice.
|
#! reallocating with `new_count == 0` will free the memory and return an empty slice.
|
||||||
#! note: memory must be reallocated with the same allocator that was used to allocate it.
|
#! note: memory must be reallocated with the same allocator that was used to allocate it.
|
||||||
realloc proc($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
|
#! the result belongs to the caller; release it with free using that allocator.
|
||||||
if new_count == memory.len {
|
#! on failure the original slice remains valid and caller-owned.
|
||||||
return memory
|
realloc proc(
|
||||||
}
|
$T type,
|
||||||
|
allocator Allocator,
|
||||||
|
memory []mut T,
|
||||||
|
new_count usize,
|
||||||
|
) []mut T ! AllocError {
|
||||||
|
if (new_count == memory.len) return memory
|
||||||
|
|
||||||
if new_count == 0 {
|
if new_count == 0 {
|
||||||
free(allocator, memory)
|
free(allocator, memory)
|
||||||
return empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
element_size usize :: sizeof!(T)
|
element_size :: sizeof!(T)
|
||||||
if (element_size == 0) return empty_slice(T, new_count)
|
if (element_size == 0) return empty_slice(T, new_count)
|
||||||
if (new_count > divtrunc!(maxval!(usize), element_size)) return .out_of_memory
|
if (new_count > divtrunc!(maxval!(usize), element_size)) return .out_of_memory
|
||||||
|
|
||||||
@@ -85,7 +109,7 @@ realloc proc($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
|||||||
old_size = memory.len * element_size
|
old_size = memory.len * element_size
|
||||||
}
|
}
|
||||||
|
|
||||||
resized := raw_realloc(
|
resized :: raw_realloc(
|
||||||
allocator,
|
allocator,
|
||||||
old_memory,
|
old_memory,
|
||||||
old_size,
|
old_size,
|
||||||
@@ -105,8 +129,9 @@ realloc proc($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
|||||||
#! note: memory must be freed with the same allocator that was used to allocate it.
|
#! note: memory must be freed with the same allocator that was used to allocate it.
|
||||||
free proc($T type, allocator Allocator, memory []T) void {
|
free proc($T type, allocator Allocator, memory []T) void {
|
||||||
if (memory.len == 0 or sizeof!(T) == 0) return
|
if (memory.len == 0 or sizeof!(T) == 0) return
|
||||||
raw_free(allocator, ptrcast!(
|
raw_free(
|
||||||
constcast!(memory).ptr),
|
allocator,
|
||||||
|
ptrcast!(constcast!(memory).ptr),
|
||||||
memory.len * sizeof!(T),
|
memory.len * sizeof!(T),
|
||||||
alignof!(T),
|
alignof!(T),
|
||||||
)
|
)
|
||||||
@@ -122,14 +147,15 @@ empty_slice proc($T type, count usize) []mut T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#! get an empty slice of type `T` with 0 elements.
|
#! get an empty slice of type `T` with 0 elements.
|
||||||
|
#! the view borrows shared empty storage and requires no cleanup.
|
||||||
empty proc($T type) []mut T {
|
empty proc($T type) []mut T {
|
||||||
return empty_slice(T, 0)
|
return empty_slice(T, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
empty_storage [1]mut u64 := [0]
|
empty_storage [1]mut u64 := [0]
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
malloc_alignment usize :: 16 # note: aarch64-macos libc malloc alignment assumption.
|
malloc_alignment usize :: 16 # note: aarch64-macos libc malloc alignment assumption.
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -137,7 +163,7 @@ power_of_two proc(value usize) bool {
|
|||||||
if (value == 0) return false
|
if (value == 0) return false
|
||||||
current := value
|
current := value
|
||||||
while current > 1 {
|
while current > 1 {
|
||||||
half := divtrunc!(current, 2)
|
half :: divtrunc!(current, 2)
|
||||||
if (half * 2 != current) return false
|
if (half * 2 != current) return false
|
||||||
current = half
|
current = half
|
||||||
}
|
}
|
||||||
@@ -150,7 +176,7 @@ c_alloc proc(_ ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
|
|||||||
if (alignment <= malloc_alignment) return ptrcast!(c.malloc(c_ulong(size)))
|
if (alignment <= malloc_alignment) return ptrcast!(c.malloc(c_ulong(size)))
|
||||||
|
|
||||||
memory [1]mut ?*mut anyopaque := [null]
|
memory [1]mut ?*mut anyopaque := [null]
|
||||||
status := c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
status :: c.posix_memalign((&memory).ptr, c_ulong(alignment), c_ulong(size))
|
||||||
if (status != 0) return null
|
if (status != 0) return null
|
||||||
|
|
||||||
return ptrcast!(memory[0])
|
return ptrcast!(memory[0])
|
||||||
@@ -176,7 +202,7 @@ c_realloc proc(
|
|||||||
return ptrcast!(c.realloc(old_memory, c_ulong(new_size)))
|
return ptrcast!(c.realloc(old_memory, c_ulong(new_size)))
|
||||||
}
|
}
|
||||||
|
|
||||||
new_memory := c_alloc(null, new_size, alignment)
|
new_memory :: c_alloc(null, new_size, alignment)
|
||||||
if new_memory |new_bytes| {
|
if new_memory |new_bytes| {
|
||||||
copy_size := old_size
|
copy_size := old_size
|
||||||
if (new_size < copy_size) copy_size = new_size
|
if (new_size < copy_size) copy_size = new_size
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ fills_typed_values_within_slice_boundaries test {
|
|||||||
try testing.expect_equal(u32(305419896), values[2])
|
try testing.expect_equal(u32(305419896), values[2])
|
||||||
try testing.expect_equal(u32(4), values[3])
|
try testing.expect_equal(u32(4), values[3])
|
||||||
|
|
||||||
records [2]mut Record := [Record{ tag = 0, id = 0 }, Record{ tag = 0, id = 0 }]
|
records [2]mut Record := [
|
||||||
|
Record{ tag = 0, id = 0 },
|
||||||
|
Record{ tag = 0, id = 0 },
|
||||||
|
]
|
||||||
set(records[..], Record{ tag = 3, id = maxval!(u32) })
|
set(records[..], Record{ tag = 3, id = maxval!(u32) })
|
||||||
for records |record| {
|
for records |record| {
|
||||||
try testing.expect_equal(u8(3), record.tag)
|
try testing.expect_equal(u8(3), record.tag)
|
||||||
|
|||||||
+10
-4
@@ -1,18 +1,24 @@
|
|||||||
import "@std/testing"
|
import "@std/testing"
|
||||||
|
|
||||||
|
@hide:file
|
||||||
TestTokenKind :: enum(u8) {
|
TestTokenKind :: enum(u8) {
|
||||||
ident = 3
|
ident = 3
|
||||||
int = 8
|
int = 8
|
||||||
eof = 21
|
eof = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
|
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(null))
|
||||||
|
@hide:file
|
||||||
TestArrayAlias :: alias [3]u16
|
TestArrayAlias :: alias [3]u16
|
||||||
|
@hide:file
|
||||||
TestInner :: distinct u16
|
TestInner :: distinct u16
|
||||||
|
@hide:file
|
||||||
TestOuter :: distinct TestInner
|
TestOuter :: distinct TestInner
|
||||||
|
@hide:file
|
||||||
TestOuterAlias :: alias TestOuter
|
TestOuterAlias :: alias TestOuter
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
array_info_matches proc($Array, $Child type, $len usize) bool {
|
array_info_matches proc($Array, $Child type, $len usize) bool {
|
||||||
return match typeinfo!(Array) {
|
return match typeinfo!(Array) {
|
||||||
.array |info|: info.child == Child and info.len == len
|
.array |info|: info.child == Child and info.len == len
|
||||||
@@ -20,7 +26,7 @@ array_info_matches proc($Array, $Child type, $len usize) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide
|
@hide:file
|
||||||
distinct_info_matches proc($Distinct, $Backing type) bool {
|
distinct_info_matches proc($Distinct, $Backing type) bool {
|
||||||
return match typeinfo!(Distinct) {
|
return match typeinfo!(Distinct) {
|
||||||
.distinct |backing|: backing == Backing
|
.distinct |backing|: backing == Backing
|
||||||
@@ -43,7 +49,7 @@ distinct_reflection_exposes_immediate_backing test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum_field_struct_defaults test {
|
enum_field_struct_defaults test {
|
||||||
names TestNames := {
|
names TestNames :: {
|
||||||
ident = "identifier",
|
ident = "identifier",
|
||||||
int = "integer",
|
int = "integer",
|
||||||
}
|
}
|
||||||
@@ -60,6 +66,6 @@ enum_field_struct_defaults test {
|
|||||||
|
|
||||||
if (field!(names, "eof")) |_| try testing.expect(false)
|
if (field!(names, "eof")) |_| try testing.expect(false)
|
||||||
|
|
||||||
empty TestNames := {}
|
empty TestNames :: {}
|
||||||
if (field!(empty, "ident")) |_| try testing.expect(false)
|
if (field!(empty, "ident")) |_| try testing.expect(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Pair proc($V type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#! initializes a static string map from a list of key-value pairs (constructed at compile-time).
|
#! initializes a static string map from a list of key-value pairs (constructed at compile-time).
|
||||||
|
#! returned views use compile-time storage and require no cleanup.
|
||||||
init proc($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
init proc($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
||||||
if N > usize(maxval!(u32)) {
|
if N > usize(maxval!(u32)) {
|
||||||
compile_error!("static string map has too many entries")
|
compile_error!("static string map has too many entries")
|
||||||
@@ -39,7 +40,7 @@ init proc($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if N == 0 {
|
if N == 0 {
|
||||||
len_indexes [0]u32 := undefined
|
len_indexes [0]u32 :: undefined
|
||||||
return StaticStringMap(V){
|
return StaticStringMap(V){
|
||||||
keys = keys[..],
|
keys = keys[..],
|
||||||
values = values[..],
|
values = values[..],
|
||||||
@@ -83,7 +84,7 @@ init proc($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
|||||||
get proc($V type, map @StaticStringMap(V), key []u8) ?V {
|
get proc($V type, map @StaticStringMap(V), key []u8) ?V {
|
||||||
if (map.keys.len == 0 or key.len > maxval!(u32)) return null
|
if (map.keys.len == 0 or key.len > maxval!(u32)) return null
|
||||||
|
|
||||||
length := u32(key.len)
|
length :: u32(key.len)
|
||||||
if (length < map.min_len or length > map.max_len) return null
|
if (length < map.min_len or length > map.max_len) return null
|
||||||
|
|
||||||
idx := usize(map.len_indexes[usize(length)])
|
idx := usize(map.len_indexes[usize(length)])
|
||||||
|
|||||||
Reference in New Issue
Block a user