Files
brolang/compiler/parser/parser.odin
T
2026-06-10 20:54:14 +02:00

571 lines
14 KiB
Odin

package parser
import "../ast"
import "../source"
import "../token"
import "core:fmt"
import "core:strconv"
import "core:strings"
Parser :: struct {
tokens: ^token.Stream,
diagnostics: ^source.Diagnostics,
module: ast.Module,
pkg: int,
file: int,
cursor: int,
delimiter_depth: int,
}
span_from :: proc(first, last: source.Span) -> source.Span {
return source.Span{file=first.file, start=first.start, end=last.end}
}
current :: proc(parser: ^Parser) -> token.Token {
return parser.tokens.items[min(parser.cursor, len(parser.tokens.items)-1)]
}
previous :: proc(parser: ^Parser) -> token.Token {
return parser.tokens.items[max(parser.cursor-1, 0)]
}
advance :: proc(parser: ^Parser) -> token.Token {
result := current(parser)
if result.kind != .Eof {
parser.cursor += 1
}
return result
}
allow :: proc(parser: ^Parser, kind: token.Kind) -> (token.Token, bool) {
if current(parser).kind == kind {
return advance(parser), true
}
return current(parser), false
}
skip_newlines :: proc(parser: ^Parser) {
for current(parser).kind == .Newline {
advance(parser)
}
}
add_expr :: proc(parser: ^Parser, expr: ast.Expr) -> int {
id := len(parser.module.exprs)
append(&parser.module.exprs, expr)
return id
}
invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> int {
id := source.add(parser.diagnostics, span, message)
return add_expr(parser, ast.Expr{
kind=.Invalid,
span=span,
left=ast.INVALID_ID,
right=ast.INVALID_ID,
diagnostic=id,
})
}
is_type_token :: proc(kind: token.Kind) -> bool {
#partial switch kind {
case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64, .Keyword_Void:
return true
}
return false
}
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
tok := current(parser)
#partial switch tok.kind {
case .Keyword_Int:
advance(parser)
return .Int
case .Keyword_I8:
advance(parser)
return .I8
case .Keyword_I16:
advance(parser)
return .I16
case .Keyword_I32:
advance(parser)
return .I32
case .Keyword_I64:
advance(parser)
return .I64
case .Keyword_Void:
advance(parser)
return .Void
}
source.add(parser.diagnostics, tok.span, "expected a type")
return .Invalid
}
parse_call :: proc(parser: ^Parser, qualifier: string, first, name: token.Token) -> int {
left_paren := advance(parser)
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
args: [dynamic]int
args.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
append(&args, parse_expression(parser))
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
break
}
right_paren, ok := allow(parser, .Right_Paren)
if !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after call arguments")
right_paren = left_paren
}
return add_expr(parser, ast.Expr{
kind=.Call,
span=source.Span{file=name.span.file, start=first.span.start, end=right_paren.span.end},
qualifier=qualifier,
text=name.text,
args=args[:],
left=ast.INVALID_ID,
right=ast.INVALID_ID,
diagnostic=-1,
})
}
parse_primary :: proc(parser: ^Parser) -> int {
tok := current(parser)
#partial switch tok.kind {
case .Integer:
advance(parser)
value, ok := strconv.parse_i64(tok.text)
if !ok {
return invalid_expr(parser, tok.span, "integer literal does not fit in i64")
}
return add_expr(parser, ast.Expr{
kind=.Integer,
span=tok.span,
integer=value,
left=ast.INVALID_ID,
right=ast.INVALID_ID,
diagnostic=-1,
})
case .Identifier:
first := advance(parser)
name := first
qualifier := ""
if _, ok := allow(parser, .Dot); ok {
if current(parser).kind != .Identifier {
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
}
qualifier = first.text
name = advance(parser)
}
if current(parser).kind == .Left_Paren {
return parse_call(parser, qualifier, first, name)
}
return add_expr(parser, ast.Expr{
kind=.Name,
span=span_from(first.span, name.span),
qualifier=qualifier,
text=name.text,
left=ast.INVALID_ID,
right=ast.INVALID_ID,
diagnostic=-1,
})
case .Underscore:
advance(parser)
return invalid_expr(parser, tok.span, "'_' is a write-only sink and cannot be read")
case .Left_Paren:
advance(parser)
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
skip_newlines(parser)
expr := parse_expression(parser)
skip_newlines(parser)
if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')'")
}
return expr
case .Invalid:
advance(parser)
return add_expr(parser, ast.Expr{
kind=.Invalid,
span=tok.span,
left=ast.INVALID_ID,
right=ast.INVALID_ID,
diagnostic=tok.diagnostic,
})
}
if tok.kind != .Newline && tok.kind != .Right_Brace && tok.kind != .Eof {
advance(parser)
}
return invalid_expr(parser, tok.span, "expected an expression")
}
parse_expression :: proc(parser: ^Parser) -> int {
left := parse_primary(parser)
if parser.delimiter_depth > 0 {
skip_newlines(parser)
}
for current(parser).kind == .Plus {
advance(parser)
skip_newlines(parser)
right := parse_primary(parser)
left_expr := parser.module.exprs[left]
right_expr := parser.module.exprs[right]
left = add_expr(parser, ast.Expr{
kind=.Add,
span=span_from(left_expr.span, right_expr.span),
left=left,
right=right,
diagnostic=-1,
})
if parser.delimiter_depth > 0 {
skip_newlines(parser)
}
}
return left
}
finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> int {
if current(parser).kind == .Newline {
skip_newlines(parser)
return -1
}
if current(parser).kind == .Eof || allow_closing_brace && current(parser).kind == .Right_Brace {
return -1
}
diagnostic := source.add(
parser.diagnostics,
current(parser).span,
"completed statements must be followed by a newline",
)
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
return diagnostic
}
parse_return :: proc(parser: ^Parser) -> int {
start := advance(parser)
skip_newlines(parser)
if current(parser).kind == .Underscore {
end := advance(parser)
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Return,
span=span_from(start.span, end.span),
name="_",
expr=ast.INVALID_ID,
diagnostic=-1,
})
return id
}
expr := parse_expression(parser)
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Return,
span=span_from(start.span, parser.module.exprs[expr].span),
expr=expr,
diagnostic=-1,
})
return id
}
parse_statement :: proc(parser: ^Parser) -> int {
if current(parser).kind == .Keyword_Return {
return parse_return(parser)
}
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
start_cursor := parser.cursor
name := advance(parser)
type_syntax := ast.Type_Syntax.Invalid
had_type := false
if is_type_token(current(parser).kind) {
type_syntax = parse_type(parser)
had_type = true
}
operator := current(parser)
if operator.kind == .Colon_Colon || operator.kind == .Equal {
advance(parser)
skip_newlines(parser)
expr := parse_expression(parser)
kind := ast.Stmt_Kind.Assignment
immutable := false
if operator.kind == .Colon_Colon || had_type {
kind = .Declaration
immutable = operator.kind == .Colon_Colon
}
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=kind,
span=span_from(name.span, parser.module.exprs[expr].span),
name=name.text,
type=type_syntax,
immutable=immutable,
expr=expr,
diagnostic=-1,
})
return id
}
parser.cursor = start_cursor
}
expr := parse_expression(parser)
id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Expression,
span=parser.module.exprs[expr].span,
expr=expr,
diagnostic=-1,
})
return id
}
parse_params :: proc(parser: ^Parser) -> []ast.Param {
params: [dynamic]ast.Param
params.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
names: [dynamic]token.Token
names.allocator = parser.module.allocator
for {
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected parameter name")
break
}
append(&names, advance(parser))
if is_type_token(current(parser).kind) {
break
}
if _, ok := allow(parser, .Comma); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ',' or parameter type")
break
}
skip_newlines(parser)
}
type_syntax := parse_type(parser)
for name in names {
append(&params, ast.Param{name=name.text, span=name.span, type=type_syntax})
}
delete(names)
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
break
}
return params[:]
}
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
func_token := advance(parser)
if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
}
params := parse_params(parser)
if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after parameters")
}
skip_newlines(parser)
result := parse_type(parser)
skip_newlines(parser)
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '{' before function body")
}
body: [dynamic]int
body.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
append(&body, parse_statement(parser))
if diagnostic := finish_statement(parser, true); diagnostic >= 0 {
statement_id := len(parser.module.statements)
append(&parser.module.statements, ast.Stmt{
kind=.Invalid,
span=current(parser).span,
expr=ast.INVALID_ID,
diagnostic=diagnostic,
})
append(&body, statement_id)
}
}
end := current(parser)
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after function body")
end = func_token
}
append(&parser.module.functions, ast.Function{
span=span_from(name.span, end.span),
name=name.text,
pkg=parser.pkg,
file=parser.file,
c_abi=c_abi,
params=params,
result=result,
body=body[:],
diagnostic=-1,
})
}
decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
if len(tok.text) < 2 {
return fmt.aprintf("", allocator=parser.module.allocator)
}
builder := strings.builder_make(parser.module.allocator)
defer strings.builder_destroy(&builder)
for index := 1; index < len(tok.text)-1; index += 1 {
value := tok.text[index]
if value == '\\' && index+1 < len(tok.text)-1 {
index += 1
value = tok.text[index]
}
strings.write_byte(&builder, value)
}
return fmt.aprintf("%s", strings.to_string(builder), allocator=parser.module.allocator)
}
parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) {
skip_newlines(parser)
path_token := current(parser)
if path_token.kind != .String {
source.add(parser.diagnostics, path_token.span, "expected an import path string")
if path_token.kind != .Newline && path_token.kind != .Eof {
advance(parser)
}
append(&parser.module.imports, ast.Import{
span=start.span,
alias=alias.text,
pkg=parser.pkg,
file=parser.file,
target=-1,
valid=false,
diagnostic=-1,
})
_ = finish_statement(parser)
return
}
advance(parser)
append(&parser.module.imports, ast.Import{
span=span_from(start.span, path_token.span),
alias=alias.text,
path=decode_import_path(parser, path_token),
pkg=parser.pkg,
file=parser.file,
target=-1,
valid=true,
diagnostic=-1,
})
_ = finish_statement(parser)
}
parse_top_level :: proc(parser: ^Parser) {
if current(parser).kind == .Keyword_Import {
start := advance(parser)
parse_import(parser, token.Token{}, start)
return
}
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected a top-level declaration")
for current(parser).kind != .Newline && current(parser).kind != .Eof {
advance(parser)
}
_ = finish_statement(parser)
return
}
name := advance(parser)
if current(parser).kind == .Colon_Colon {
saved := parser.cursor
advance(parser)
skip_newlines(parser)
if current(parser).kind == .Keyword_Import {
start := advance(parser)
parse_import(parser, name, start)
return
}
parser.cursor = saved
}
type_syntax := ast.Type_Syntax.Invalid
if is_type_token(current(parser).kind) {
type_syntax = parse_type(parser)
}
operator := current(parser)
if operator.kind != .Colon_Colon && operator.kind != .Equal {
source.add(parser.diagnostics, operator.span, "expected '::' or '=' after top-level name")
_ = finish_statement(parser)
return
}
advance(parser)
skip_newlines(parser)
c_abi := false
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_C {
c_abi = true
advance(parser)
skip_newlines(parser)
}
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Func {
parse_function(parser, name, c_abi)
return
}
expr := parse_expression(parser)
append(&parser.module.globals, ast.Global{
span=span_from(name.span, parser.module.exprs[expr].span),
name=name.text,
pkg=parser.pkg,
file=parser.file,
type=type_syntax,
immutable=operator.kind == .Colon_Colon,
expr=expr,
diagnostic=-1,
})
_ = finish_statement(parser)
}
parse :: proc(
stream: ^token.Stream,
diagnostics: ^source.Diagnostics,
allocator := context.allocator,
) -> ast.Module {
parser := Parser{
tokens=stream,
diagnostics=diagnostics,
module=ast.init_module(allocator),
}
skip_newlines(&parser)
for current(&parser).kind != .Eof {
parse_top_level(&parser)
skip_newlines(&parser)
}
return parser.module
}
parse_into :: proc(
stream: ^token.Stream,
diagnostics: ^source.Diagnostics,
module: ^ast.Module,
pkg, file: int,
) {
parser := Parser{
tokens=stream,
diagnostics=diagnostics,
module=module^,
pkg=pkg,
file=file,
}
skip_newlines(&parser)
for current(&parser).kind != .Eof {
parse_top_level(&parser)
skip_newlines(&parser)
}
module^ = parser.module
}