refactor: apply project style guidelines
This commit is contained in:
+35
-7
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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']
|
||||
|
||||
Reference in New Issue
Block a user