feat: support enums, distinct types, unions and matching
This commit is contained in:
+289
-13
@@ -30,6 +30,7 @@ State :: struct {
|
||||
next_token usize
|
||||
paren_depth usize = 0
|
||||
allow_record_literals bool = true
|
||||
match_arm_body bool = false
|
||||
}
|
||||
|
||||
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
|
||||
@@ -156,7 +157,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
while true {
|
||||
token :: try consume(state, .ident)
|
||||
try arraylist.append(&names, token)
|
||||
if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct)) break
|
||||
if (starts_type(current(state).kind)) break
|
||||
_ = try consume(state, .comma)
|
||||
}
|
||||
|
||||
@@ -237,6 +238,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
and (
|
||||
next_n_is(state, 1, .ident)
|
||||
or next_n_is(state, 1, .struct)
|
||||
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
||||
or next_n_is(state, 1, .distinct)
|
||||
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
|
||||
or next_n_is(state, 1, .double_colon)
|
||||
or next_n_is(state, 1, .colon_equal)
|
||||
@@ -250,7 +253,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.for: return try parse_for(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct: {
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
|
||||
.enum, .union, .distinct, .alias, .dot, .match: {
|
||||
expression :: try parse_expr(state)
|
||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||
return try ast.add_expression_stmt(state.builder, expression)
|
||||
@@ -264,6 +268,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .return)
|
||||
if (
|
||||
next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .eof)
|
||||
or (state.match_arm_body and next_is(state, .comma))
|
||||
or (allow_else and next_is(state, .else))
|
||||
) {
|
||||
return try ast.add_return_stmt(state.builder, keyword.span, null)
|
||||
@@ -272,6 +277,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
if (
|
||||
!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) {
|
||||
return .unexpected_token
|
||||
}
|
||||
@@ -297,6 +303,7 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
||||
and !next_is(state, .close_curly)
|
||||
and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) return .unexpected_token
|
||||
|
||||
if (keyword.kind == .break)
|
||||
@@ -314,6 +321,7 @@ parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.
|
||||
and !next_is(state, .close_curly)
|
||||
and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) {
|
||||
return .unexpected_token
|
||||
}
|
||||
@@ -569,7 +577,7 @@ parse_arm proc(state @mut State, allow_else bool) 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) or next_is(state, .open_bracket) or next_is(state, .struct))
|
||||
type_id ?ast.NodeId :: if (starts_type(current(state).kind))
|
||||
try parse_type(state)
|
||||
else
|
||||
null
|
||||
@@ -622,11 +630,14 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
)
|
||||
}
|
||||
if next_is(state, .struct) return try parse_record_type(state)
|
||||
if next_is(state, .enum) return try parse_enum_type(state)
|
||||
if next_is(state, .union) return try parse_union_type(state)
|
||||
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
||||
token :: try consume(state, .ident)
|
||||
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
while next_is(state, .dot) {
|
||||
state.next_token += 1
|
||||
member :: try consume(state, .ident)
|
||||
member :: try consume_member(state, false)
|
||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||
type = try ast.add_selection(state.builder, ast.Span{
|
||||
start = token.span.start,
|
||||
@@ -637,6 +648,261 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
return type
|
||||
}
|
||||
|
||||
@hide:file
|
||||
starts_type proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .ident or kind == .open_bracket or kind == .struct
|
||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
consume_member proc(state @mut State, numeric bool) lexer.Token ! SyntaxError {
|
||||
token :: current(state)
|
||||
if (!member_name(token.kind) and !(numeric and token.kind == .int)) return .unexpected_token
|
||||
state.next_token += 1
|
||||
return token
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_type_wrapper proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: current(state)
|
||||
state.next_token += 1
|
||||
operand :: try parse_type(state)
|
||||
return try ast.add_type_operand(state.builder, ast.Span{
|
||||
start = keyword.span.start,
|
||||
end = ast.builder_span(state.builder, operand).end,
|
||||
source = keyword.span.source,
|
||||
}, operand, keyword.kind == .alias)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_enum_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .enum)
|
||||
backing ?ast.NodeId := null
|
||||
if next_is(state, .open_paren) {
|
||||
state.next_token += 1
|
||||
backing = try parse_type(state)
|
||||
_ = try consume(state, .close_paren)
|
||||
}
|
||||
skip_newlines(state)
|
||||
_ = try consume(state, .open_curly)
|
||||
members arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&members)
|
||||
skip_newlines(state)
|
||||
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal) or next_n_is(state, 1, .proc))
|
||||
) return try invalid_record(state, keyword.span.start, false)
|
||||
start :: state.next_token
|
||||
member :: parse_enum_member(state) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
yield try ast.add_field(state.builder, span, null, null, invalid)
|
||||
}
|
||||
try arraylist.append(&members, member)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) state.next_token += 1
|
||||
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||
skip_newlines(state)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
return try ast.add_enum_type(state.builder, ast.Span{
|
||||
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||
}, backing, members.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_enum_member proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: try consume_member(state, false)
|
||||
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
value ?ast.NodeId := null
|
||||
if next_is(state, .equal) {
|
||||
state.next_token += 1
|
||||
value = try parse_expr(state)
|
||||
}
|
||||
end :: if (value) |id| ast.builder_span(state.builder, id).end else token.span.end
|
||||
return try ast.add_field(state.builder, ast.Span{
|
||||
start = token.span.start,
|
||||
end = end,
|
||||
source = token.span.source,
|
||||
}, name, null, value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_union_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .union)
|
||||
tag ?ast.NodeId := null
|
||||
auto_tag := false
|
||||
if next_is(state, .open_paren) {
|
||||
state.next_token += 1
|
||||
if next_is(state, .enum) {
|
||||
state.next_token += 1
|
||||
auto_tag = true
|
||||
} else {
|
||||
tag = try parse_type(state)
|
||||
}
|
||||
_ = try consume(state, .close_paren)
|
||||
}
|
||||
skip_newlines(state)
|
||||
_ = try consume(state, .open_curly)
|
||||
fields arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&fields)
|
||||
skip_newlines(state)
|
||||
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal) or next_n_is(state, 1, .proc))
|
||||
) return try invalid_record(state, keyword.span.start, false)
|
||||
start :: state.next_token
|
||||
field :: parse_union_field(state, auto_tag or tag != null) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
yield try ast.add_field(state.builder, span, null, invalid, null)
|
||||
}
|
||||
try arraylist.append(&fields, field)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) state.next_token += 1
|
||||
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||
skip_newlines(state)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
return try ast.add_union_type(state.builder, ast.Span{
|
||||
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||
}, tag, auto_tag, fields.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_union_field proc(state @mut State, tagged bool) ast.NodeId ! ParseError {
|
||||
token :: if (tagged) try consume_member(state, false) else try consume(state, .ident)
|
||||
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
type :: try parse_type(state)
|
||||
return try ast.add_field(state.builder, ast.Span{
|
||||
start = token.span.start, end = ast.builder_span(state.builder, type).end, source = token.span.source,
|
||||
}, name, type, null)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_match proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .match)
|
||||
skip_newlines(state)
|
||||
subject :: try parse_guard_expr(state)
|
||||
skip_newlines(state)
|
||||
_ = try consume(state, .open_curly)
|
||||
previous_depth :: state.paren_depth
|
||||
state.paren_depth = 0
|
||||
defer state.paren_depth = previous_depth
|
||||
arms arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&arms)
|
||||
skip_newlines(state)
|
||||
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal) or next_n_is(state, 1, .proc))
|
||||
) return try invalid_record(state, keyword.span.start, false)
|
||||
start :: state.next_token
|
||||
arm :: parse_match_arm(state) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
}
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
span :: recover_record_field(state, start)
|
||||
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||
yield try ast.add_match_arm(state.builder, span, &[], null, invalid, true, false)
|
||||
}
|
||||
try arraylist.append(&arms, arm)
|
||||
if next_is(state, .close_curly) break
|
||||
if next_is(state, .comma) state.next_token += 1
|
||||
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||
skip_newlines(state)
|
||||
}
|
||||
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||
close :: try consume(state, .close_curly)
|
||||
return try ast.add_match(state.builder, ast.Span{
|
||||
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||
}, subject, arms.items)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
start :: current(state).span
|
||||
patterns arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&patterns)
|
||||
catch_all :: next_is(state, .else)
|
||||
capture ?ast.NodeId := null
|
||||
if (catch_all) state.next_token += 1
|
||||
else {
|
||||
while true {
|
||||
try arraylist.append(&patterns, try parse_guard_expr(state))
|
||||
if (!next_is(state, .comma)) break
|
||||
state.next_token += 1
|
||||
skip_newlines(state)
|
||||
}
|
||||
if next_is(state, .pipe) {
|
||||
state.next_token += 1
|
||||
# pointer payload captures are outside the current runtime representation.
|
||||
if (next_is(state, .at) or next_is(state, .star)) return .unexpected_token
|
||||
token :: try consume(state, .ident)
|
||||
if (
|
||||
token.span.end != token.span.start + 1
|
||||
or state.builder.source[usize(token.span.start)] != '_'
|
||||
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
_ = try consume(state, .pipe)
|
||||
}
|
||||
}
|
||||
_ = try consume(state, .colon)
|
||||
skip_newlines(state)
|
||||
previous :: state.match_arm_body
|
||||
state.match_arm_body = true
|
||||
defer state.match_arm_body = previous
|
||||
statement_body :: match current(state).kind {
|
||||
.open_curly, .return, .break, .continue, .if, .while, .for: true
|
||||
else: false
|
||||
}
|
||||
if (statement_body or named_binding_starts(state)) {
|
||||
body :: try parse_arm(state, false)
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, false, catch_all)
|
||||
}
|
||||
value :: try parse_expr(state)
|
||||
if next_is(state, .equal) {
|
||||
statement :: try parse_assignment(state, value, false)
|
||||
statements [1]ast.NodeId :: [statement]
|
||||
body :: try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||
}, patterns.items, capture, body, false, catch_all)
|
||||
}
|
||||
return try ast.add_match_arm(state.builder, ast.Span{
|
||||
start = start.start, end = ast.builder_span(state.builder, value).end, source = start.source,
|
||||
}, patterns.items, capture, value, true, catch_all)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .struct)
|
||||
@@ -711,15 +977,16 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
|
||||
name ?ast.NodeId := null
|
||||
type ?ast.NodeId := null
|
||||
value ?ast.NodeId := null
|
||||
named :: next_is(state, .ident) and (
|
||||
named :: member_name(current(state).kind) and (
|
||||
(!declaration and next_n_is(state, 1, .equal))
|
||||
or (declaration and (
|
||||
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
|
||||
or next_n_is(state, 1, .struct)
|
||||
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .enum)
|
||||
or next_n_is(state, 1, .union) or next_n_is(state, 1, .distinct)
|
||||
))
|
||||
)
|
||||
if (named) {
|
||||
token :: try consume(state, .ident)
|
||||
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
|
||||
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
}
|
||||
if (declaration) {
|
||||
@@ -836,7 +1103,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
}
|
||||
if (token.kind == .dot) {
|
||||
state.next_token += 1
|
||||
member :: try consume_either(state, &[.ident, .int])
|
||||
member :: try consume_member(state, true)
|
||||
member_id :: if (member.kind == .int)
|
||||
try ast.add_literal(state.builder, .int, member.span)
|
||||
else
|
||||
@@ -999,7 +1266,7 @@ array_starts_type proc(state @State) bool {
|
||||
if (depth == 0) {
|
||||
if (i + 1 >= state.tokens.len) return false
|
||||
next :: state.tokens[i + 1].kind
|
||||
return next == .ident or next == .mut or next == .open_bracket or next == .struct
|
||||
return starts_type(next) or next == .mut
|
||||
}
|
||||
}
|
||||
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
||||
@@ -1073,6 +1340,18 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: current(state)
|
||||
match token.kind {
|
||||
.struct: return try parse_record_type(state)
|
||||
.enum: return try parse_enum_type(state)
|
||||
.union: return try parse_union_type(state)
|
||||
.distinct, .alias: return try parse_type_wrapper(state)
|
||||
.match: return try parse_match(state)
|
||||
.dot: {
|
||||
state.next_token += 1
|
||||
member :: try consume_member(state, false)
|
||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||
return try ast.add_enum_literal(state.builder, ast.Span{
|
||||
start = token.span.start, end = member.span.end, source = token.span.source,
|
||||
}, member_id)
|
||||
}
|
||||
.open_curly: {
|
||||
if (state.allow_record_literals) return try parse_record_literal(state, null, token.span.start)
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
@@ -1126,10 +1405,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||
}
|
||||
else: {
|
||||
diagnostic :: if (token.data.diagnostic != diagnostics.no_diagnostic)
|
||||
token.data.diagnostic
|
||||
else
|
||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
diagnostic :: try unexpected_diagnostic(state)
|
||||
|
||||
if !is_expression_recovery_boundary(token.kind) {
|
||||
state.next_token += 1
|
||||
|
||||
@@ -940,7 +940,6 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
|
||||
try testing.expect_equal(6, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
||||
@@ -952,6 +951,11 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator)
|
||||
second_and :: ast.binary_expr(&tree, second_or.left)
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right))
|
||||
missing :: ast.invalid(&tree, second_and.right)
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, missing.diagnostic)?
|
||||
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
|
||||
try testing.expect_equal(missing.span.start, diagnostic.span.start)
|
||||
try testing.expect_equal(missing.span.end, diagnostic.span.end)
|
||||
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right))
|
||||
third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2))
|
||||
try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer))
|
||||
@@ -1036,13 +1040,17 @@ rejects_control_tails_and_preserves_following_declarations test {
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect_equal(9, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(6, ast.package_decl_count(&tree))
|
||||
for (usize(0)..usize(2)) |index| try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, index)))
|
||||
for (usize(2)..usize(4)) |index| {
|
||||
declaration :: ast.package_decl(&tree, index)
|
||||
try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, declaration))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.statement_decl(&tree, declaration).initializer))
|
||||
invalid :: ast.invalid(&tree, ast.statement_decl(&tree, declaration).initializer)
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, invalid.diagnostic)?
|
||||
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
|
||||
try testing.expect_equal(invalid.span.start, diagnostic.span.start)
|
||||
try testing.expect_equal(invalid.span.end, diagnostic.span.end)
|
||||
}
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
||||
try testing.expect_equal(2, ast.block_statement_count(&tree, body))
|
||||
@@ -1588,3 +1596,117 @@ rolls_back_shared_nodes_extra_spans_and_diagnostics_on_allocation_failure test {
|
||||
}
|
||||
try testing.expect(failures > 0)
|
||||
}
|
||||
|
||||
parses_enum_union_wrappers_and_keyword_variants test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Tag :: enum(i32) { if = -2, else = 3 # preserve the member boundary
|
||||
` return
|
||||
`}
|
||||
`Payload :: union(Tag) { if i32, else void, return struct { value i32 } }
|
||||
`Auto :: union(enum) { value i32, empty void }
|
||||
`Raw :: union { integer i32, real f64 }
|
||||
`Id :: distinct i32
|
||||
`Other :: alias Id
|
||||
`value Payload :: Payload{ if = 4 }
|
||||
`context Payload :: .return{value = 5}
|
||||
`empty Payload :: .else
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
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))
|
||||
try testing.expect_equal(9, ast.package_decl_count(&tree))
|
||||
enumeration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
try testing.expect(ast.enum_type(&tree, enumeration).backing != null)
|
||||
try testing.expect_equal(3, ast.enum_member_count(&tree, enumeration))
|
||||
member :: ast.field(&tree, ast.enum_member(&tree, enumeration, 0))
|
||||
try testing.expect_equal("if", strpool.get_str(&strings, ast.name_id(&tree, member.name?))?)
|
||||
try testing.expect_equal(ast.ExprKind.unary, ast.expr_kind(&tree, member.value?))
|
||||
tagged :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
|
||||
automatic :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer
|
||||
raw :: ast.statement_decl(&tree, ast.package_decl(&tree, 3)).initializer
|
||||
try testing.expect(ast.union_type(&tree, tagged).tag != null)
|
||||
try testing.expect(ast.union_type(&tree, automatic).auto_tag)
|
||||
try testing.expect(!ast.union_type(&tree, raw).auto_tag)
|
||||
try testing.expect_equal(2, ast.record_field_count(&tree, raw))
|
||||
distinct_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 4)).initializer
|
||||
alias_value :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.distinct_type, ast.expr_kind(&tree, distinct_type))
|
||||
try testing.expect_equal(ast.ExprKind.alias_expr, ast.expr_kind(&tree, alias_value))
|
||||
contextual :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
|
||||
variant :: ast.record_literal(&tree, contextual).type?
|
||||
try testing.expect_equal(ast.ExprKind.enum_literal, ast.expr_kind(&tree, variant))
|
||||
try testing.expect_equal("return", strpool.get_str(&strings, ast.name_id(&tree, ast.enum_literal(&tree, variant)))?)
|
||||
}
|
||||
|
||||
parses_match_patterns_payload_captures_and_statement_arms test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`choose proc(value int) int {
|
||||
` return match value {
|
||||
` 0, 1..=3: 10,
|
||||
` 4: 20 # preserve the arm boundary
|
||||
` else: 30
|
||||
` }
|
||||
`}
|
||||
`use proc(value int) void {
|
||||
` match value {
|
||||
` .if, .return |payload|: { local :: payload }
|
||||
` .else: return,
|
||||
` else: {}
|
||||
` }
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
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))
|
||||
choose :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
value_match :: ast.return_stmt(&tree, ast.block_statement(&tree, choose.body, 0)).value?
|
||||
try testing.expect_equal(3, ast.match_arm_count(&tree, value_match))
|
||||
first :: ast.match_arm(&tree, value_match, 0)
|
||||
try testing.expect_equal(2, ast.match_pattern_count(&tree, first))
|
||||
try testing.expect_equal(ast.ExprKind.range, ast.expr_kind(&tree, ast.match_pattern(&tree, first, 1)))
|
||||
try testing.expect(ast.match_arm_view(&tree, first).expression)
|
||||
use :: ast.function_decl(&tree, ast.package_decl(&tree, 1))
|
||||
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, use.body, 0))
|
||||
captured :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
|
||||
try testing.expect(captured.capture != null)
|
||||
try testing.expect(!captured.expression)
|
||||
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 2)).catch_all)
|
||||
}
|
||||
|
||||
rejects_pointer_match_captures_and_preserves_siblings test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`broken :: enum { first
|
||||
`after :: 1
|
||||
`use proc(value int) void {
|
||||
` match value {
|
||||
` .item |@payload|: payload
|
||||
` else: {}
|
||||
` }
|
||||
`}
|
||||
`last :: 2
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||
try testing.expect_equal(4, ast.package_decl_count(&tree))
|
||||
try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
|
||||
try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user