605 lines
15 KiB
Odin
605 lines
15 KiB
Odin
package parser
|
|
|
|
import "../ast"
|
|
import "../source"
|
|
import "../symbol"
|
|
import "../token"
|
|
import "core:fmt"
|
|
import "core:strconv"
|
|
import "core:strings"
|
|
|
|
Parser :: struct {
|
|
tokens: ^token.Stream,
|
|
source_file: ^source.Source,
|
|
diagnostics: ^source.Diagnostics,
|
|
module: ast.Module,
|
|
pkg: int,
|
|
file: int,
|
|
cursor: int,
|
|
delimiter_depth: int,
|
|
}
|
|
|
|
token_text :: proc(parser: ^Parser, tok: token.Token) -> string {
|
|
if tok.span.start < 0 || tok.span.end < tok.span.start || tok.span.end > len(parser.source_file.text) {
|
|
return ""
|
|
}
|
|
return parser.source_file.text[tok.span.start:tok.span.end]
|
|
}
|
|
|
|
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: symbol.Id, 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,
|
|
name=name.symbol,
|
|
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(token_text(parser, tok))
|
|
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 := symbol.INVALID
|
|
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.symbol
|
|
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,
|
|
name=name.symbol,
|
|
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=end.symbol,
|
|
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.symbol,
|
|
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(¶ms, ast.Param{name=name.symbol, 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)
|
|
end := previous(parser)
|
|
ended_by_newline := current(parser).kind == .Newline
|
|
if current(parser).kind == .Newline {
|
|
skip_newlines(parser)
|
|
}
|
|
if current(parser).kind != .Left_Brace {
|
|
if !ended_by_newline && current(parser).kind != .Eof {
|
|
_ = finish_statement(parser)
|
|
}
|
|
append(&parser.module.functions, ast.Function{
|
|
span=span_from(name.span, end.span),
|
|
name=name.symbol,
|
|
pkg=parser.pkg,
|
|
file=parser.file,
|
|
c_abi=c_abi,
|
|
has_body=false,
|
|
params=params,
|
|
result=result,
|
|
diagnostic=-1,
|
|
})
|
|
return
|
|
}
|
|
advance(parser)
|
|
|
|
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.symbol,
|
|
pkg=parser.pkg,
|
|
file=parser.file,
|
|
c_abi=c_abi,
|
|
has_body=true,
|
|
params=params,
|
|
result=result,
|
|
body=body[:],
|
|
diagnostic=-1,
|
|
})
|
|
}
|
|
|
|
decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
|
|
text := token_text(parser, tok)
|
|
if len(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(text)-1; index += 1 {
|
|
value := text[index]
|
|
if value == '\\' && index+1 < len(text)-1 {
|
|
index += 1
|
|
value = 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.symbol,
|
|
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.symbol,
|
|
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.symbol,
|
|
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,
|
|
source_file: ^source.Source,
|
|
diagnostics: ^source.Diagnostics,
|
|
allocator := context.allocator,
|
|
) -> ast.Module {
|
|
parser := Parser{
|
|
tokens=stream,
|
|
source_file=source_file,
|
|
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,
|
|
source_file: ^source.Source,
|
|
diagnostics: ^source.Diagnostics,
|
|
module: ^ast.Module,
|
|
pkg, file: int,
|
|
) {
|
|
parser := Parser{
|
|
tokens=stream,
|
|
source_file=source_file,
|
|
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
|
|
}
|