refactor: apply project style guidelines

This commit is contained in:
2026-10-05 20:18:53 +02:00
parent 6ba23f579a
commit 2648991d04
32 changed files with 495 additions and 266 deletions
+35 -7
View File
@@ -1,4 +1,4 @@
#! GRAMMAR:
#! grammar:
#! * type ..... -> identifier
#! * assignment -> :: | :=
#! * literal .. -> int | float | string
@@ -99,11 +99,13 @@ Storage :: struct {
spans arraylist.ArrayList(Span)
}
#! owns node storage, retains its allocator, and borrows source until finish or deinit_builder.
Builder :: struct {
storage Storage
source []u8
}
#! owns node storage and borrows source; source must remain valid until deinit.
Tree :: struct {
storage Storage
source []u8
@@ -154,6 +156,7 @@ InvalidView :: struct {
diagnostic DiagnosticId
}
#! retains allocator for node storage and borrows source; release with deinit_builder.
init_builder proc(allocator mem.Allocator, source []u8) Builder {
return Builder{
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 {
arraylist.deinit(&builder.storage.nodes)
arraylist.deinit(&builder.storage.extra)
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 {
tree :: Tree{ storage = builder.storage, source = builder.source, root = root }
allocator :: builder.storage.nodes.allocator
@@ -183,6 +188,7 @@ finish proc(builder @mut Builder, root NodeId) Tree {
return tree
}
#! releases owned node storage with its retained allocator, without freeing source.
deinit proc(tree @mut Tree) void {
arraylist.deinit(&tree.storage.nodes)
arraylist.deinit(&tree.storage.extra)
@@ -324,21 +330,33 @@ add_package proc(builder @mut Builder, span Span, declarations []NodeId) NodeId
}, 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{
kind = .invalid_expr,
main = NodeData{ diagnostic = diagnostic },
}, 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{
kind = .invalid_stmt,
main = NodeData{ diagnostic = diagnostic },
}, 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{
kind = .invalid_decl,
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])
}
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 {
node :: get_node(&tree.storage, id)
debug.assert(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 }
debug.assert(
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 {
debug.assert(span.start <= span.end and usize(span.end) <= tree.source.len)
return tree.source[usize(span.start)..usize(span.end)]
+1 -1
View File
@@ -15,7 +15,7 @@ exposes_typed_declaration_views test {
.constant,
value,
)
declarations [1]NodeId := [declaration]
declarations [1]NodeId :: [declaration]
package :: try add_package(&builder, Span{ start = 0, end = 11 }, declarations[..])
tree := finish(&builder, package)
defer deinit(&tree)
+4
View File
@@ -2,18 +2,22 @@ import "@std/mem"
import "@std/arraylist"
import "@source/ast"
#! owns output bytes and retains its allocator; release with deinit.
Renderer :: struct {
bytes arraylist.ArrayList(u8)
}
#! retains allocator for rendered output; the caller owns the returned Renderer.
init proc(allocator mem.Allocator) Renderer {
return Renderer{ bytes = arraylist.init(allocator) }
}
#! releases output bytes with the retained allocator.
deinit proc(renderer @mut Renderer) void {
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 {
arraylist.clear(&renderer.bytes)
try append_text(renderer, "package\n")
+1 -1
View File
@@ -14,7 +14,7 @@ renders_semantic_debug_view test {
.constant,
value,
)
declarations [1]ast.NodeId := [declaration]
declarations [1]ast.NodeId :: [declaration]
package :: try ast.add_package(&builder, ast.Span{ start = 0, end = 11 }, declarations[..])
tree := ast.finish(&builder, package)
defer ast.deinit(&tree)
+4
View File
@@ -23,18 +23,22 @@ Diagnostic :: struct {
span Span
}
#! owns diagnostic storage and retains its allocator; release with deinit.
Store :: struct { list arraylist.ArrayList(Diagnostic) }
CapacityError :: enum { diagnostic_limit_reached }
#! retains allocator for added diagnostics; the caller owns the returned Store.
init proc(allocator mem.Allocator) Store {
return Store{ list = arraylist.init(allocator) }
}
#! releases diagnostic storage with the retained allocator.
deinit proc(store @mut Store) void {
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) {
if (store.list.items.len >= usize(NO_DIAGNOSTIC)) return .diagnostic_limit_reached
id :: DiagnosticId(store.list.items.len)
+55 -32
View File
@@ -5,6 +5,7 @@ import "@std/arraylist"
import "@source/diagnostics"
import "@source/strpool"
@hide:file
ScanError :: struct {
code ErrorCode
end usize
@@ -17,7 +18,7 @@ ErrorCode :: enum {
}
SourceError :: enum { source_too_large }
@hide:file
keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "proc", .proc },
{ "return", .return },
@@ -27,18 +28,25 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
{ "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 {
tokens arraylist.ArrayList(Token)
}
#! retains allocator for scanned tokens; the caller owns the returned State.
init proc(allocator mem.Allocator) State {
return State{ tokens = arraylist.init(allocator) }
}
#! releases token storage with the retained allocator, without releasing strpool.strings.
deinit proc(state @mut State) void {
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(
state @mut State,
program []u8,
@@ -49,24 +57,22 @@ scan proc(
while cursor < program.len {
char :: program[cursor]
# whitespace
if char == '\n' {
try add_token(state, Token{ span = make_span(cursor, cursor + 1), kind = .newline })
cursor += 1
continue
} else if is_whitespace(char) {
}
if is_whitespace(char) {
cursor += 1
continue
}
# comments
if char == '#' {
while (cursor < program.len and program[cursor] != '\n') cursor += 1
if (cursor < program.len) cursor += 1
continue
}
# identifiers and keywords
if is_alpha(char) or char == '_' {
start :: cursor
result :: scan_ident(start, program)
@@ -79,7 +85,6 @@ scan proc(
continue
}
# numeric literals
if is_digit(char) {
start :: cursor
result :: scan_number(start, program) catch |err| {
@@ -102,7 +107,6 @@ scan proc(
continue
}
# string literals
if char == '"' {
start :: cursor
result :: scan_string(start, program) catch |err| {
@@ -124,15 +128,20 @@ scan proc(
continue
}
# immutable decl, mutable decl, or single colon
if char == ':' {
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
continue
}
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
continue
}
@@ -141,10 +150,12 @@ scan proc(
continue
}
# compound arithmetic assignment
if char == '+' {
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
continue
}
@@ -154,7 +165,10 @@ scan proc(
}
if char == '-' {
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
continue
}
@@ -164,7 +178,10 @@ scan proc(
}
if char == '*' {
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
continue
}
@@ -174,7 +191,10 @@ scan proc(
}
if char == '/' {
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
continue
}
@@ -183,7 +203,6 @@ scan proc(
continue
}
# single character tokens
kind ?TokenKind :: match char {
'=': .equal
',': .comma
@@ -201,7 +220,6 @@ scan proc(
continue
}
# invalid character
id :: try diagnostics.add(
diagnostic_store,
.invalid_character,
@@ -218,15 +236,16 @@ scan proc(
try add_token(state, Token{ span = make_span(cursor, cursor), kind = .eof })
}
@hide:file
ScanIdentResult :: struct {
end usize
kind TokenKind
str_id strpool.StringId
}
@hide:file
scan_ident proc(start usize, program []u8) ScanIdentResult {
cursor := start + 1
# scan whole identifier
while (cursor < program.len and (
is_alpha(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)
kind :: strmap.get(&keywords, program[start..cursor]) orelse .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
strpool.NO_STR
return ScanIdentResult{ end = cursor, kind = kind, str_id = str_id }
}
@hide:file
ScanNumResult :: struct {
end usize
has_decimal bool
}
@hide:file
scan_number proc(start usize, program []u8) ScanNumResult ! ScanError {
cursor := start
has_decimal := false
# scan integer part
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
# check for decimal
if cursor < program.len and program[cursor] == '.' {
has_decimal = true
cursor += 1
}
# assert non-terminating decimal
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
return ScanNumResult{ end = cursor, has_decimal = has_decimal }
}
@hide:file
ScanStrResult :: struct { end usize }
@hide:file
scan_string proc(start usize, program []u8) ScanStrResult ! ScanError {
cursor := start + 1 # skip first `"`
# scan entire string
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
}
# assert string terminal
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 `"`
}
@hide
@hide:file
is_whitespace proc(char u8) bool {
return char == ' ' or char == '\t' or char == '\n' or char == '\r'
}
@hide
@hide:file
is_alpha proc(char u8) bool {
return match char {
'a'..='z', 'A'..='Z': true
@@ -295,7 +318,7 @@ is_alpha proc(char u8) bool {
}
}
@hide
@hide:file
is_digit proc(char u8) bool {
return match char {
'0'..='9': true
@@ -316,7 +339,7 @@ make_span proc(start, end usize) diagnostics.Span {
return diagnostics.Span{ start = u32(start), end = u32(end) }
}
@hide
@hide:file
add_token proc(state @mut State, token Token) void ! mem.AllocError {
_ = token_id(state.tokens.items.len)
try arraylist.append(&state.tokens, token)
+6 -3
View File
@@ -4,8 +4,8 @@ import "@source/diagnostics"
import "@source/strpool"
reports_invalid_input_and_keeps_scanning test {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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.eof, state.tokens.items[7].kind)
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(7), state.tokens.items[1].span.end)
}
+10 -10
View File
@@ -57,22 +57,22 @@ TokenKind :: enum {
invalid
}
render_token proc(tok Token, program []u8) void {
match tok.kind {
render_token proc(token Token, program []u8) void {
match token.kind {
.ident: {
if (strpool.get_str(&strpool.STRINGS, tok.data.str_id)) |str| {
debug.print("{}({})\n", {tok.kind, str})
if (strpool.get_str(&strpool.strings, token.data.str_id)) |str| {
debug.print("{}({})\n", {token.kind, str})
} 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:
debug.print("{}({})\n", {tok.kind, program[tok.span.start..tok.span.end]})
else: debug.print("{}\n", {tok.kind})
debug.print("{}({})\n", {token.kind, program[token.span.start..token.span.end]})
else: debug.print("{}\n", {token.kind})
}
}
token_id proc(idx uint) TokenId {
debug.assert(u64(idx) < u64(NO_TOKEN))
return TokenId(idx)
token_id proc(index uint) TokenId {
debug.assert(u64(index) < u64(NO_TOKEN))
return TokenId(index)
}
+5 -4
View File
@@ -24,6 +24,7 @@ test import "@source/ast/renderer"
test import "@source/parser"
test import "@source/sema"
@hide:file
program ::
`# literals
`x float :: 123.9
@@ -42,8 +43,8 @@ program ::
`empty proc() void {}
main proc() void! {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
@@ -53,14 +54,14 @@ main proc() void! {
try lexer.scan(&scan_state, program, &diagnostic_store)
tree := try parser.parse(
mem.c_allocator,
scan_state.tokens.items,
program,
&diagnostic_store,
mem.c_allocator,
)
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)
renderer := ast_renderer.init(mem.c_allocator)
+29 -16
View File
@@ -29,11 +29,14 @@ State :: struct {
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(
allocator mem.Allocator,
tokens []lexer.Token,
source []u8,
diagnostic_store @mut diagnostics.Store,
allocator mem.Allocator,
) ast.Tree ! (mem.AllocError | diagnostics.CapacityError) {
state := State{
builder = ast.init_builder(allocator, source),
@@ -55,7 +58,7 @@ parse_package proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.
skip_newlines(state)
while !next_is(state, .eof) {
start :: state.next_token
declaration := parse_decl(state) catch |err| {
declaration :: parse_decl(state) catch |err| {
match err {
.unexpected_token: {
recovered :: try recover_decl(state, start)
@@ -79,9 +82,7 @@ parse_package proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.
@hide:file
parse_decl proc(state @mut State) ast.NodeId ! ParseError {
if next_is(state, .ident) and next_n_is(state, 1, .proc) {
return try parse_func_decl(state)
}
if (next_is(state, .ident) and next_n_is(state, 1, .proc)) return try parse_func_decl(state)
return try parse_statement_decl(state)
}
@@ -142,7 +143,7 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
skip_newlines(state)
while !next_is(state, .close_curly) and !next_is(state, .eof) {
start :: state.next_token
statement := parse_statement(state) catch |err| {
statement :: parse_statement(state) catch |err| {
match err {
.unexpected_token: {
recovered :: try recover_statement(state, start)
@@ -184,7 +185,7 @@ parse_statement proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
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)
else
null
@@ -266,8 +267,7 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
defer arraylist.deinit(&arguments)
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)
try arraylist.append(&arguments, argument)
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)
return try ast.add_call(
&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,
arguments.items,
)
@@ -293,7 +296,11 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
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{
start = token.span.start,
end = ast.builder_span(&state.builder, expression).end,
@@ -321,9 +328,9 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
else
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) {
state.next_token += 1
}
if (token.kind != .newline and token.kind != .comma and
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)
}
@@ -331,14 +338,20 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
}
@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)
span :: synchronize(state, start, false)
return try ast.add_invalid_decl(&state.builder, span, diagnostic)
}
@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)
span :: synchronize(state, start, true)
return try ast.add_invalid_stmt(&state.builder, span, diagnostic)
+56 -30
View File
@@ -7,8 +7,8 @@ import "@source/lexer"
import "@source/ast"
parses_expression_precedence_through_tree_views test {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source :: "value :: 1 + 2 * 3\n"
@@ -16,7 +16,7 @@ parses_expression_precedence_through_tree_views test {
defer lexer.deinit(&scan_state)
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)
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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source :: "value :: a + ((b - c)) * 2 / 3\n"
@@ -43,7 +43,7 @@ parses_grouped_expression_precedence test {
defer lexer.deinit(&scan_state)
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)
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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -77,7 +77,7 @@ parses_function_structure_through_tree_views test {
defer lexer.deinit(&scan_state)
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)
declaration :: ast.package_decl(&tree, 0)
@@ -98,8 +98,8 @@ parses_function_structure_through_tree_views test {
}
recovers_invalid_declaration_and_preserves_sibling test {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source :: "@ broken\nvalid :: 1\n"
@@ -107,7 +107,7 @@ recovers_invalid_declaration_and_preserves_sibling test {
defer lexer.deinit(&scan_state)
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)
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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -138,7 +138,7 @@ recovers_malformed_groups_and_preserves_following_statements test {
defer lexer.deinit(&scan_state)
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)
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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -174,7 +174,7 @@ parses_call_statements_and_postfix_precedence test {
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
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)
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)
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("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))
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))
factory :: ast.call_expr(&tree, call).callee
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)
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("1", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, nested, 0))))
try testing.expect_equal(
"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))
try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator)
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)))
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("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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -235,7 +253,7 @@ recovers_malformed_calls_and_preserves_siblings test {
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
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)
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
@@ -245,16 +263,24 @@ recovers_malformed_calls_and_preserves_siblings test {
for ([0, 2]) |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(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))
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))
try testing.expect_equal("after", ast.source_text(&tree, after.name))
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(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))
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
try testing.expect_equal(0, ast.call_arg_count(&tree, tail.initializer))
+10 -4
View File
@@ -35,6 +35,8 @@ Storage :: struct {
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 }
@hide:file
@@ -51,10 +53,13 @@ State :: struct {
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(
allocator mem.Allocator,
tree @ast.Tree,
diagnostic_store @mut diagnostics.Store,
allocator mem.Allocator,
) Resolution ! (mem.AllocError | diagnostics.CapacityError) {
bindings :: try mem.alloc(Binding, allocator, ast.node_count(tree))
errdefer mem.free(allocator, bindings)
@@ -90,6 +95,7 @@ resolve proc(
return Resolution{ storage = Storage{ bindings = bindings, allocator = allocator } }
}
#! releases binding storage with the allocator retained by resolve.
deinit proc(resolution @mut Resolution) void {
mem.free(resolution.storage.allocator, resolution.storage.bindings)
}
@@ -141,7 +147,7 @@ predeclare proc(
value := Binding{ kind = .declaration, data = BindingData{ declaration = id } }
if lookup(scope.parent, name) |_| {
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 } }
}
try put_name(scope, name, value)
@@ -156,7 +162,7 @@ resolve_function proc(
) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
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)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
@@ -212,7 +218,7 @@ resolve_expr proc(
scope @Scope,
id ast.NodeId,
) 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
tree :: state.tree
match ast.expr_kind(tree, id) {
+78 -27
View File
@@ -8,8 +8,8 @@ import "@source/parser"
import "@source/ast"
resolves_forward_references_recursion_and_builtin_signatures test {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -26,9 +26,9 @@ resolves_forward_references_recursion_and_builtin_signatures test {
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
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)
resolution := try resolve(&tree, &diagnostic_store, mem.c_allocator)
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
defer deinit(&resolution)
first :: ast.package_decl(&tree, 0)
@@ -85,10 +85,22 @@ resolves_forward_references_recursion_and_builtin_signatures test {
} else {
return .expectation_failed
}
try testing.expect_equal(BindingKind.builtin, binding(&resolution, ast.param(&tree, q).type).kind)
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)
try testing.expect_equal(
BindingKind.builtin,
binding(&resolution, ast.param(&tree, q).type).kind,
)
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]
for return_types |id| {
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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -121,10 +133,10 @@ rejects_shadowing_and_duplicates_without_cascades test {
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
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)
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)
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_int :: ast.block_statement(&tree, view.body, 3)
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| {
invalid_binding :: binding(&resolution, id)
try testing.expect_equal(BindingKind.invalid, invalid_binding.kind)
if (diagnostics.get(&diagnostic_store, invalid_binding.data.diagnostic)) |diagnostic| {
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.end, diagnostic.span.end)
} else {
return .expectation_failed
}
}
try testing.expect(binding(&resolution, first_outer).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)
try testing.expect(
binding(&resolution, first_outer).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))
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))
try testing.expect_equal(BindingKind.declaration, binding(&resolution, p_ref).kind)
try testing.expect_equal(p, binding(&resolution, p_ref).data.declaration)
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(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))
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(outer, binding(&resolution, view.return_type).data.declaration)
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 {
strpool.STRINGS = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.STRINGS)
strpool.strings = strpool.init(mem.c_allocator)
defer strpool.deinit(&strpool.strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
@@ -205,7 +247,7 @@ preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
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)
syntax_count :: diagnostics.count(&diagnostic_store)
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))
invalid_statement :: ast.block_statement(&tree, view.body, 4)
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)
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)
b :: ast.function_param(&tree, function, 1)
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)
bad :: ast.binary_expr(&tree, ast.statement_decl(&tree, ast.block_statement(&tree, view.body, 1)).initializer)
try testing.expect_equal(
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]
for unknown_nodes |id| {
invalid_binding :: binding(&resolution, id)
@@ -243,7 +291,10 @@ preserves_syntax_diagnostics_and_resolves_shared_unknown_types_once test {
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)
later :: ast.block_statement(&tree, view.body, 3)
valid_ref :: ast.statement_decl(&tree, valid).initializer
+9 -6
View File
@@ -8,15 +8,17 @@ StringId :: alias u32
NO_STR :: maxval!(StringId)
# cross-cutting concern, hence global singleton (owned by main.hon)
STRINGS StringPool := undefined
#! global pool owned by main.hon; initialize before scanning and release with deinit.
strings StringPool := undefined
#! retains its allocator and owns interned copies; release all storage with deinit.
StringPool :: struct {
ids hashmap.StringHashMap(StringId) # string → id
strings arraylist.ArrayList([]u8) # id → owned string
allocator mem.Allocator
}
#! retains allocator for interned copies and indexes; the caller owns the pool.
init proc(allocator mem.Allocator) StringPool {
return StringPool{
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 {
hashmap.deinit(&pool.ids)
for pool.strings.items |str| {
mem.free(pool.allocator, str)
}
for (pool.strings.items) |str| mem.free(pool.allocator, str)
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) {
if hashmap.get(&pool.ids, str) |id| return id
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)
errdefer mem.free(pool.allocator, owned_str)
@@ -56,6 +58,7 @@ intern proc(pool @mut StringPool, str []u8) StringId ! (mem.AllocError | InternE
return id
}
#! returns pool-owned text borrowed until deinit; the caller must not free it.
get_str proc(pool @StringPool, id StringId) ?[]u8 {
if (usize(id) >= pool.strings.items.len) return null
return pool.strings.items[usize(id)]
+1 -1
View File
@@ -2,7 +2,7 @@ import "@std/mem"
import "@std/testing"
handles_interning test {
pool StringPool := init(mem.c_allocator)
pool := init(mem.c_allocator)
defer deinit(&pool)
input [5]mut u8 := ['h', 'e', 'l', 'l', 'o']