402 lines
11 KiB
Plaintext
402 lines
11 KiB
Plaintext
import "@std"
|
|
import "@std/mem"
|
|
import "@std/arraylist"
|
|
import "@std/enums/enummap"
|
|
import "@std/debug"
|
|
|
|
import "@source/lexer"
|
|
import "@source/ast"
|
|
|
|
# todo: move error stuff into a separate file
|
|
|
|
Diagnostic :: struct {
|
|
code ErrorCode
|
|
node NodeId
|
|
}
|
|
|
|
ParseError :: alias ErrorCode | mem.AllocError
|
|
|
|
ErrorCode :: enum {
|
|
unexpected_token,
|
|
}
|
|
|
|
ErrorDetails :: struct {
|
|
name []u8
|
|
message []u8
|
|
}
|
|
|
|
error_msg_map std.EnumMap(ErrorCode, ErrorDetails) :: enummap.init({
|
|
unexpected_token = ErrorDetails {
|
|
name = "P0",
|
|
message = "unexpected token",
|
|
},
|
|
})
|
|
|
|
@hide:file Token :: alias lexer.Token
|
|
@hide:file TokenId :: alias lexer.TokenId
|
|
@hide:file TokenKind :: alias lexer.TokenKind
|
|
@hide:file Node :: alias ast.Node
|
|
@hide:file NodeId :: alias ast.NodeId
|
|
@hide:file ExtraId :: alias ast.ExtraId
|
|
@hide:file NodeData :: alias ast.NodeData
|
|
@hide:file NO_NODE :: alias ast.NO_NODE
|
|
@hide:file NO_EXTRA :: alias ast.NO_EXTRA
|
|
@hide:file token_id :: alias lexer.token_id
|
|
|
|
BindingPowerScalar :: distinct u32
|
|
|
|
BindingPower :: struct {
|
|
left BindingPowerScalar
|
|
right BindingPowerScalar
|
|
}
|
|
|
|
prefix_binding_power proc(token_kind TokenKind) ?BindingPowerScalar {
|
|
return match token_kind {
|
|
.minus: BindingPowerScalar(30)
|
|
else: null
|
|
}
|
|
}
|
|
|
|
infix_binding_power proc(token_kind TokenKind) ?BindingPower {
|
|
return match token_kind {
|
|
.plus, .minus: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
|
.star, .slash: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
|
else: null
|
|
}
|
|
}
|
|
|
|
postfix_binding_power proc(token_kind TokenKind) ?BindingPowerScalar {
|
|
return match token_kind {
|
|
.open_paren, .open_bracket: BindingPowerScalar(40)
|
|
else: null
|
|
}
|
|
}
|
|
|
|
State :: struct {
|
|
nodes std.ArrayList(Node)
|
|
|
|
#! array of raw u32's indexing nodes, tokens, etc.
|
|
extra std.ArrayList(u32)
|
|
|
|
tokens []Token = &[]
|
|
next_token usize = 0
|
|
}
|
|
|
|
init proc(allocator mem.Allocator) State {
|
|
return State{
|
|
nodes = arraylist.init(allocator),
|
|
extra = arraylist.init(allocator),
|
|
}
|
|
}
|
|
|
|
deinit proc(state @mut State) void {
|
|
arraylist.deinit(&state.nodes)
|
|
arraylist.deinit(&state.extra)
|
|
}
|
|
|
|
parse proc(state @mut State, tokens []Token) NodeId ! ParseError {
|
|
state.tokens = tokens
|
|
return try parse_pkg(state)
|
|
}
|
|
|
|
parse_pkg proc(state @mut State) NodeId ! ParseError {
|
|
if (state.next_token < state.tokens.len
|
|
and state.tokens[state.next_token].kind == .eof
|
|
) return NO_NODE
|
|
|
|
stmt_ids std.ArrayList(NodeId) := arraylist.init(state.nodes.allocator)
|
|
defer arraylist.deinit(&stmt_ids)
|
|
|
|
while (state.next_token < state.tokens.len
|
|
and state.tokens[state.next_token].kind != .eof
|
|
) {
|
|
stmt_id :: try parse_decl(state)
|
|
try arraylist.append(&stmt_ids, stmt_id)
|
|
}
|
|
|
|
pkg_start :: ast.extra_id(state.extra.items.len)
|
|
for (stmt_ids.items) |stmt_id| _ = try add_extra(state, u32(stmt_id))
|
|
pkg_end :: ast.extra_id(state.extra.items.len)
|
|
|
|
return try add_node(state, Node{
|
|
kind = .package,
|
|
data0 = NodeData{ extra_id = pkg_start },
|
|
data1 = NodeData{ extra_id = pkg_end },
|
|
})
|
|
}
|
|
|
|
parse_decl proc(state @mut State) NodeId ! ParseError {
|
|
if next_n_is(state, 1, .proc)
|
|
return try parse_func_decl(state)
|
|
else
|
|
return try parse_stmt_decl(state)
|
|
}
|
|
|
|
parse_func_decl proc(state @mut State) NodeId ! ParseError {
|
|
ident_tok_id :: try consume(state, .ident)
|
|
_ = try consume(state, .proc)
|
|
_ = try consume(state, .open_paren)
|
|
|
|
param_decls std.ArrayList(NodeId) := arraylist.init(state.nodes.allocator)
|
|
defer arraylist.deinit(¶m_decls)
|
|
|
|
ident_ids std.ArrayList(TokenId) := arraylist.init(state.nodes.allocator)
|
|
defer arraylist.deinit(&ident_ids)
|
|
|
|
while !next_is(state, .close_paren) {
|
|
arraylist.clear(&ident_ids)
|
|
|
|
# collect param group
|
|
while true {
|
|
param_tok_id :: try consume(state, .ident)
|
|
try arraylist.append(&ident_ids, param_tok_id)
|
|
|
|
if next_is(state, .ident) break
|
|
_ = try consume(state, .comma)
|
|
}
|
|
|
|
# collect type
|
|
try expect(state, .ident)
|
|
type_id :: try parse_primary(state)
|
|
for (ident_ids.items) |id| {
|
|
param_decl :: try add_node(state, Node{
|
|
kind = .param_decl,
|
|
main_token = id,
|
|
data0 = NodeData{ node_id = type_id },
|
|
})
|
|
try arraylist.append(¶m_decls, param_decl)
|
|
}
|
|
|
|
if next_is(state, .comma) _ = try consume(state, .comma)
|
|
}
|
|
|
|
_ = try consume(state, .close_paren)
|
|
|
|
try expect(state, .ident)
|
|
return_type_id :: try parse_primary(state)
|
|
block_id :: try parse_block(state)
|
|
|
|
while next_is(state, .newline) _ = try consume(state, .newline)
|
|
|
|
extra_id :: try add_extra(state, u32(return_type_id))
|
|
_ = try add_extra(state, u32(param_decls.items.len))
|
|
for (param_decls.items) |param| _ = try add_extra(state, u32(param))
|
|
|
|
return try add_node(state, Node{
|
|
kind = .func_decl,
|
|
main_token = ident_tok_id,
|
|
data0 = NodeData{ node_id = block_id },
|
|
data1 = NodeData{ extra_id = extra_id },
|
|
})
|
|
}
|
|
|
|
parse_block proc(state @mut State) NodeId ! ParseError {
|
|
stmt_ids std.ArrayList(NodeId) := arraylist.init(state.nodes.allocator)
|
|
defer arraylist.deinit(&stmt_ids)
|
|
|
|
_ = try consume(state, .open_curly)
|
|
while next_is(state, .newline) _ = try consume(state, .newline)
|
|
|
|
while !next_is(state, .close_curly) {
|
|
stmt_id :: try parse_stmt(state)
|
|
try arraylist.append(&stmt_ids, stmt_id)
|
|
while next_is(state, .newline) _ = try consume(state, .newline)
|
|
}
|
|
|
|
_ = try consume(state, .close_curly)
|
|
|
|
block_start :: ast.extra_id(state.extra.items.len)
|
|
for (stmt_ids.items) |id| _ = try add_extra(state, u32(id))
|
|
block_end :: ast.extra_id(state.extra.items.len)
|
|
|
|
return try add_node(state, Node{
|
|
kind = .block,
|
|
data0 = NodeData{ extra_id = block_start },
|
|
data1 = NodeData{ extra_id = block_end },
|
|
})
|
|
}
|
|
|
|
parse_stmt proc(state @mut State) NodeId ! ParseError {
|
|
if next_is(state, .ident) return try parse_stmt_decl(state)
|
|
return .unexpected_token
|
|
}
|
|
|
|
parse_stmt_decl proc(state @mut State) NodeId ! ParseError {
|
|
# expect identifier
|
|
ident_tok_id :: try consume(state, .ident)
|
|
|
|
# check for type
|
|
# todo: make a parse_type proc for this
|
|
type_id :: if (next_is(state, .ident))
|
|
try parse_primary(state)
|
|
else
|
|
NO_NODE
|
|
|
|
# expect `::` or `:=` (immutable or mutable assignment)
|
|
mutability_tok_id :: try consume_either(state, &[.double_colon, .colon_equal])
|
|
|
|
# expect expression
|
|
expr_id :: try parse_expr(state)
|
|
|
|
# expect statement terminator (newline)
|
|
while (next_is(state, .newline)) _ = try consume(state, .newline)
|
|
|
|
extra_start_id :: try add_extra(state, u32(type_id))
|
|
_ = try add_extra(state, u32(expr_id))
|
|
|
|
return try add_node(state, Node{
|
|
kind = .stmt_decl,
|
|
main_token = ident_tok_id,
|
|
data0 = NodeData{ extra_id = extra_start_id },
|
|
data1 = NodeData{ token_id = mutability_tok_id },
|
|
})
|
|
}
|
|
|
|
parse_expr proc(state @mut State) NodeId ! mem.AllocError {
|
|
return try parse_expr_bp(state, BindingPowerScalar(0))
|
|
}
|
|
|
|
parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) NodeId ! mem.AllocError {
|
|
# parse left-hand side
|
|
kind := state.tokens[state.next_token].kind
|
|
|
|
lhs := if (prefix_binding_power(kind)) |right_bp| {
|
|
operator :: token_id(state.next_token)
|
|
state.next_token += 1
|
|
|
|
operand :: try parse_expr_bp(state, right_bp)
|
|
|
|
yield try add_node(state, Node{
|
|
kind = .expr_unary,
|
|
main_token = operator,
|
|
data0 = NodeData{ node_id = operand },
|
|
})
|
|
} else {
|
|
yield try parse_primary(state)
|
|
}
|
|
|
|
# parse right-hand side
|
|
while true {
|
|
kind = state.tokens[state.next_token].kind
|
|
|
|
# try postfix (higher precedence)
|
|
if postfix_binding_power(kind) |left_bp| {
|
|
if (left_bp < min_bp) break
|
|
|
|
lhs = match state.tokens[state.next_token].kind {
|
|
.open_paren: debug.unimplemented() # todo: parse call
|
|
.open_bracket: debug.unimplemented() # todo: parse index
|
|
else: unreachable
|
|
}
|
|
continue
|
|
}
|
|
|
|
# try infex (lower precedence)
|
|
if infix_binding_power(kind) |bp| {
|
|
if (bp.left < min_bp) break
|
|
|
|
operator :: token_id(state.next_token)
|
|
state.next_token += 1
|
|
|
|
rhs :: try parse_expr_bp(state, bp.right)
|
|
lhs = try add_node(state, Node{
|
|
kind = .expr_binary,
|
|
main_token = operator,
|
|
data0 = NodeData{ node_id = lhs },
|
|
data1 = NodeData{ node_id = rhs },
|
|
})
|
|
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
return lhs
|
|
}
|
|
|
|
parse_primary proc(state @mut State) NodeId ! mem.AllocError {
|
|
start_idx :: state.next_token
|
|
start_tok_id :: TokenId(start_idx)
|
|
state.next_token += 1
|
|
return match state.tokens[start_idx].kind {
|
|
.int: try add_node(state, Node{
|
|
kind = .literal_int,
|
|
main_token = start_tok_id,
|
|
})
|
|
.float: try add_node(state, Node{
|
|
kind = .literal_float,
|
|
main_token = start_tok_id,
|
|
})
|
|
.string: try add_node(state, Node{
|
|
kind = .literal_string,
|
|
main_token = start_tok_id,
|
|
})
|
|
.ident: try add_node(state, Node{
|
|
kind = .expr_identifier,
|
|
main_token = start_tok_id,
|
|
})
|
|
else: try add_node(state, Node{
|
|
kind = .invalid,
|
|
main_token = start_tok_id,
|
|
})
|
|
}
|
|
}
|
|
|
|
@hide
|
|
next_is proc(state @mut State, token_kind TokenKind) bool {
|
|
return state.tokens[state.next_token].kind == token_kind
|
|
}
|
|
|
|
@hide
|
|
next_is_either proc(state @mut State, token_kinds []TokenKind) bool {
|
|
for (token_kinds) |kind| if next_is(state, kind) return true
|
|
return false
|
|
}
|
|
|
|
@hide
|
|
next_n_is proc(state @mut State, n usize, token_kind TokenKind) bool {
|
|
return state.tokens[state.next_token + n].kind == token_kind
|
|
}
|
|
|
|
@hide
|
|
expect proc(state @mut State, token_kind TokenKind) void ! ErrorCode {
|
|
if (state.tokens[state.next_token].kind != token_kind) return .unexpected_token
|
|
}
|
|
|
|
@hide
|
|
expect_either proc(state @mut State, token_kinds []TokenKind) void ! ErrorCode {
|
|
for (token_kinds) |kind| if (state.tokens[state.next_token].kind == kind) return
|
|
return .unexpected_token
|
|
}
|
|
|
|
@hide
|
|
consume proc(state @mut State, token_kind TokenKind) TokenId ! ErrorCode {
|
|
try expect(state, token_kind)
|
|
id :: TokenId(state.next_token)
|
|
state.next_token += 1
|
|
return id
|
|
}
|
|
|
|
@hide
|
|
consume_either proc(state @mut State, token_kinds []TokenKind) TokenId ! ErrorCode {
|
|
try expect_either(state, token_kinds)
|
|
id :: TokenId(state.next_token)
|
|
state.next_token += 1
|
|
return id
|
|
}
|
|
|
|
@hide
|
|
add_node proc(state @mut State, node Node) NodeId ! mem.AllocError {
|
|
id :: ast.node_id(state.nodes.items.len)
|
|
try arraylist.append(&state.nodes, node)
|
|
return id
|
|
}
|
|
|
|
@hide
|
|
add_extra proc(state @mut State, data u32) ExtraId ! mem.AllocError {
|
|
id :: ast.extra_id(state.extra.items.len)
|
|
try arraylist.append(&state.extra, data)
|
|
return id
|
|
}
|