gotlin/internal/lang/parser.go

1429 lines
34 KiB
Go

package lang
import (
"fmt"
"strconv"
"strings"
)
type parser struct {
tokens []token
pos int
}
func Parse(input string) (*Program, error) {
tokens, err := lex(input)
if err != nil {
return nil, err
}
p := &parser{tokens: tokens}
return p.parseProgram()
}
func (p *parser) parseProgram() (*Program, error) {
prog := &Program{}
for !p.check(tokenEOF) {
switch {
case p.match(tokenPackage):
if prog.PackagePath != "" {
return nil, fmt.Errorf("duplicate package declaration")
}
packagePath, err := p.parsePackageDecl()
if err != nil {
return nil, err
}
prog.PackagePath = packagePath
case p.match(tokenImport):
imp, err := p.parseImport()
if err != nil {
return nil, err
}
prog.Imports = append(prog.Imports, imp)
case p.check(tokenInterface):
decl, err := p.parseInterface()
if err != nil {
return nil, err
}
prog.Interfaces = append(prog.Interfaces, decl)
case p.check(tokenEnum):
decl, err := p.parseEnum()
if err != nil {
return nil, err
}
prog.Enums = append(prog.Enums, decl)
case p.check(tokenClass) || p.check(tokenData):
decl, err := p.parseClass()
if err != nil {
return nil, err
}
prog.Classes = append(prog.Classes, decl)
case p.match(tokenAt):
annotation, err := p.expect(tokenIdent, "expected annotation name")
if err != nil {
return nil, err
}
if annotation.lexeme == "embed" {
if _, err := p.expect(tokenLParen, "expected '(' after embed"); err != nil {
return nil, err
}
path, err := p.expect(tokenString, "expected embed path")
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after embed path"); err != nil {
return nil, err
}
if _, err := p.expect(tokenVal, "expected 'val' after embed annotation"); err != nil {
return nil, err
}
name, err := p.expect(tokenIdent, "expected embedded value name")
if err != nil {
return nil, err
}
if _, err := p.expect(tokenColon, "expected ':' after embedded value name"); err != nil {
return nil, err
}
typ, err := p.parseTypeRef()
if err != nil {
return nil, err
}
prog.Embeds = append(prog.Embeds, EmbedDecl{Path: strings.Trim(path.lexeme, "\""), Name: name.lexeme, Type: typ})
continue
}
switch annotation.lexeme {
case "jsonNaming":
if _, err := p.expect(tokenLParen, "expected '(' after jsonNaming"); err != nil {
return nil, err
}
policy, err := p.expect(tokenIdent, "expected JSON naming policy")
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after JSON naming policy"); err != nil {
return nil, err
}
if !p.check(tokenData) {
return nil, fmt.Errorf("jsonNaming is only valid on data classes")
}
decl, err := p.parseClass()
if err != nil {
return nil, err
}
decl.JSONNaming = policy.lexeme
prog.Classes = append(prog.Classes, decl)
case "table":
table, err := p.parseStringAnnotationArgument("table")
if err != nil {
return nil, err
}
if !validSQLName(table) {
return nil, fmt.Errorf("invalid SQL table name %q", table)
}
if !p.check(tokenData) {
return nil, fmt.Errorf("table is only valid on data classes")
}
decl, err := p.parseClass()
if err != nil {
return nil, err
}
decl.Table = table
prog.Classes = append(prog.Classes, decl)
default:
return nil, fmt.Errorf("unsupported annotation %q", annotation.lexeme)
}
case p.check(tokenWorker):
decl, err := p.parseWorker()
if err != nil {
return nil, err
}
prog.Workers = append(prog.Workers, decl)
case p.check(tokenFun):
fn, err := p.parseFunction()
if err != nil {
return nil, err
}
prog.Functions = append(prog.Functions, fn)
default:
tok := p.peek()
return nil, fmt.Errorf("expected top-level declaration at %d, found %q", tok.pos, tok.lexeme)
}
}
return prog, nil
}
func (p *parser) parseEnum() (EnumDecl, error) {
if _, err := p.expect(tokenEnum, "expected 'enum'"); err != nil {
return EnumDecl{}, err
}
name, err := p.expect(tokenIdent, "expected enum name")
if err != nil {
return EnumDecl{}, err
}
if _, err := p.expect(tokenLBrace, "expected '{' after enum name"); err != nil {
return EnumDecl{}, err
}
var variants []EnumVariant
seen := map[string]bool{}
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
variant, err := p.expect(tokenIdent, "expected enum variant")
if err != nil {
return EnumDecl{}, err
}
if seen[variant.lexeme] {
return EnumDecl{}, fmt.Errorf("duplicate enum variant %s", variant.lexeme)
}
seen[variant.lexeme] = true
var payload []string
if p.match(tokenLParen) {
if !p.check(tokenRParen) {
for {
typ, err := p.parseTypeRef()
if err != nil {
return EnumDecl{}, err
}
payload = append(payload, typ)
if !p.match(tokenComma) {
break
}
}
}
if _, err := p.expect(tokenRParen, "expected ')' after enum payload"); err != nil {
return EnumDecl{}, err
}
}
stringValue := ""
if p.match(tokenAssign) {
value, err := p.expect(tokenString, "expected string enum value")
if err != nil {
return EnumDecl{}, err
}
stringValue = strings.Trim(value.lexeme, "\"")
}
variants = append(variants, EnumVariant{Name: variant.lexeme, PayloadTypes: payload, StringValue: stringValue})
p.match(tokenComma)
p.match(tokenSemicolon)
}
if _, err := p.expect(tokenRBrace, "expected '}' after enum"); err != nil {
return EnumDecl{}, err
}
return EnumDecl{Name: name.lexeme, Variants: variants}, nil
}
func (p *parser) parsePackageDecl() (string, error) {
path, err := p.parseImportPath()
if err != nil {
return "", err
}
p.match(tokenSemicolon)
return path, nil
}
func (p *parser) parseImport() (ImportDecl, error) {
if p.check(tokenIdent) && p.peekN(1).kind == tokenString {
alias := p.advance().lexeme
path, err := p.expect(tokenString, "expected import path")
if err != nil {
return ImportDecl{}, err
}
p.match(tokenSemicolon)
return ImportDecl{Alias: alias, Path: path.lexeme}, nil
}
if p.check(tokenString) {
path, err := p.expect(tokenString, "expected import path")
if err != nil {
return ImportDecl{}, err
}
p.match(tokenSemicolon)
return ImportDecl{Path: path.lexeme}, nil
}
firstPath, err := p.parseImportPath()
if err != nil {
return ImportDecl{}, err
}
alias := ""
path := firstPath
if importPathContinues(p.peek()) {
alias = firstPath
path, err = p.parseImportPath()
if err != nil {
return ImportDecl{}, err
}
}
if err := validateBareImportPath(path); err != nil {
return ImportDecl{}, err
}
p.match(tokenSemicolon)
return ImportDecl{Alias: alias, Path: path}, nil
}
func (p *parser) parseInterface() (InterfaceDecl, error) {
if _, err := p.expect(tokenInterface, "expected 'interface'"); err != nil {
return InterfaceDecl{}, err
}
name, err := p.expect(tokenIdent, "expected interface name")
if err != nil {
return InterfaceDecl{}, err
}
if _, err := p.expect(tokenLBrace, "expected '{'"); err != nil {
return InterfaceDecl{}, err
}
var methods []FunctionSignature
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
method, err := p.parseFunctionSignature()
if err != nil {
return InterfaceDecl{}, err
}
methods = append(methods, method)
p.match(tokenSemicolon)
}
if _, err := p.expect(tokenRBrace, "expected '}'"); err != nil {
return InterfaceDecl{}, err
}
return InterfaceDecl{Name: name.lexeme, Methods: methods}, nil
}
func (p *parser) parseClass() (ClassDecl, error) {
data := p.match(tokenData)
if _, err := p.expect(tokenClass, "expected 'class'"); err != nil {
return ClassDecl{}, err
}
name, err := p.expect(tokenIdent, "expected class name")
if err != nil {
return ClassDecl{}, err
}
var fields []FieldDecl
if p.match(tokenLParen) {
fields, err = p.parseClassFields()
if err != nil {
return ClassDecl{}, err
}
if _, err := p.expect(tokenRParen, "expected ')'"); err != nil {
return ClassDecl{}, err
}
}
parents, err := p.parseClassParents()
if err != nil {
return ClassDecl{}, err
}
if !p.match(tokenLBrace) {
return ClassDecl{Name: name.lexeme, Data: data, Fields: fields, Parents: parents, Methods: nil}, nil
}
var methods []FunctionDecl
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
p.match(tokenOverride)
if !p.check(tokenFun) {
tok := p.peek()
return ClassDecl{}, fmt.Errorf("expected class member at %d, found %q", tok.pos, tok.lexeme)
}
method, err := p.parseFunction()
if err != nil {
return ClassDecl{}, err
}
methods = append(methods, method)
p.match(tokenSemicolon)
}
if _, err := p.expect(tokenRBrace, "expected '}'"); err != nil {
return ClassDecl{}, err
}
return ClassDecl{Name: name.lexeme, Data: data, Fields: fields, Parents: parents, Methods: methods}, nil
}
func (p *parser) parseWorker() (WorkerDecl, error) {
if _, err := p.expect(tokenWorker, "expected 'worker'"); err != nil {
return WorkerDecl{}, err
}
name, err := p.expect(tokenIdent, "expected worker name")
if err != nil {
return WorkerDecl{}, err
}
if _, err := p.expect(tokenLBrace, "expected '{'"); err != nil {
return WorkerDecl{}, err
}
var fields []WorkerFieldDecl
var methods []FunctionDecl
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
switch {
case p.match(tokenVal), p.match(tokenVar):
mutable := p.tokens[p.pos-1].kind == tokenVar
fieldName, err := p.expect(tokenIdent, "expected field name")
if err != nil {
return WorkerDecl{}, err
}
typ := ""
if p.match(tokenColon) {
parsed, err := p.parseTypeRef()
if err != nil {
return WorkerDecl{}, err
}
typ = parsed
}
if _, err := p.expect(tokenAssign, "expected '=' after field declaration"); err != nil {
return WorkerDecl{}, err
}
value, err := p.parseExpr(0)
if err != nil {
return WorkerDecl{}, err
}
fields = append(fields, WorkerFieldDecl{
Mutable: mutable,
Name: fieldName.lexeme,
Type: typ,
Value: value,
})
p.match(tokenSemicolon)
case p.check(tokenFun):
method, err := p.parseFunction()
if err != nil {
return WorkerDecl{}, err
}
methods = append(methods, method)
p.match(tokenSemicolon)
default:
tok := p.peek()
return WorkerDecl{}, fmt.Errorf("expected worker member at %d, found %q", tok.pos, tok.lexeme)
}
}
if _, err := p.expect(tokenRBrace, "expected '}'"); err != nil {
return WorkerDecl{}, err
}
return WorkerDecl{Name: name.lexeme, Fields: fields, Methods: methods}, nil
}
func (p *parser) parseClassParents() ([]string, error) {
if !p.match(tokenColon) {
return nil, nil
}
var parents []string
for {
parent, err := p.parseTypeRef()
if err != nil {
return nil, err
}
parents = append(parents, parent)
if !p.match(tokenComma) {
return parents, nil
}
}
}
func (p *parser) parseClassFields() ([]FieldDecl, error) {
var fields []FieldDecl
if p.check(tokenRParen) {
return fields, nil
}
for {
column := ""
id := false
generated := false
for p.match(tokenAt) {
annotation, err := p.expect(tokenIdent, "expected field annotation name")
if err != nil {
return nil, err
}
switch annotation.lexeme {
case "id":
if id {
return nil, fmt.Errorf("duplicate id annotation")
}
id = true
case "generated":
if generated {
return nil, fmt.Errorf("duplicate generated annotation")
}
generated = true
case "column":
if column != "" {
return nil, fmt.Errorf("duplicate column annotation")
}
column, err = p.parseStringAnnotationArgument("column")
if err != nil {
return nil, err
}
if !validSQLName(column) {
return nil, fmt.Errorf("invalid SQL column name %q", column)
}
default:
return nil, fmt.Errorf("unsupported field annotation %q", annotation.lexeme)
}
}
private := p.match(tokenPrivate)
mutable := false
switch {
case p.match(tokenVal):
mutable = false
case p.match(tokenVar):
mutable = true
default:
tok := p.peek()
return nil, fmt.Errorf("expected 'val' or 'var' in class constructor at %d, found %q", tok.pos, tok.lexeme)
}
name, err := p.expect(tokenIdent, "expected field name")
if err != nil {
return nil, err
}
if _, err := p.expect(tokenColon, "expected ':' after field name"); err != nil {
return nil, err
}
typ, err := p.parseTypeRef()
if err != nil {
return nil, err
}
fields = append(fields, FieldDecl{Mutable: mutable, Private: private, Name: name.lexeme, Type: typ, Column: column, ID: id, Generated: generated})
if !p.match(tokenComma) {
return fields, nil
}
}
}
func (p *parser) parseStringAnnotationArgument(name string) (string, error) {
if _, err := p.expect(tokenLParen, "expected '(' after "+name); err != nil {
return "", err
}
value, err := p.expect(tokenString, "expected string argument for "+name)
if err != nil {
return "", err
}
if _, err := p.expect(tokenRParen, "expected ')' after "+name+" argument"); err != nil {
return "", err
}
decoded, err := strconv.Unquote(value.lexeme)
if err != nil {
return "", fmt.Errorf("invalid string argument for %s: %w", name, err)
}
return decoded, nil
}
func validSQLName(name string) bool {
if name == "" {
return false
}
for i, r := range name {
if i == 0 {
if !isIdentStart(r) {
return false
}
continue
}
if !isIdentPart(r) {
return false
}
}
return true
}
func (p *parser) parseFunction() (FunctionDecl, error) {
signature, err := p.parseFunctionSignature()
if err != nil {
return FunctionDecl{}, err
}
body, err := p.parseBlock()
if err != nil {
return FunctionDecl{}, err
}
return FunctionDecl{
Name: signature.Name,
Params: signature.Params,
ReturnType: signature.ReturnType,
Body: body,
}, nil
}
func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
if _, err := p.expect(tokenFun, "expected 'fun'"); err != nil {
return FunctionSignature{}, err
}
name, err := p.expect(tokenIdent, "expected function name")
if err != nil {
return FunctionSignature{}, err
}
if _, err := p.expect(tokenLParen, "expected '('"); err != nil {
return FunctionSignature{}, err
}
params, err := p.parseParams()
if err != nil {
return FunctionSignature{}, err
}
if _, err := p.expect(tokenRParen, "expected ')'"); err != nil {
return FunctionSignature{}, err
}
returnType := "Unit"
if p.match(tokenColon) {
typ, err := p.parseTypeRef()
if err != nil {
return FunctionSignature{}, err
}
returnType = typ
}
return FunctionSignature{
Name: name.lexeme,
Params: params,
ReturnType: returnType,
}, nil
}
func (p *parser) parseParams() ([]Param, error) {
var params []Param
if p.check(tokenRParen) {
return params, nil
}
for {
name, err := p.expect(tokenIdent, "expected parameter name")
if err != nil {
return nil, err
}
if _, err := p.expect(tokenColon, "expected ':' after parameter name"); err != nil {
return nil, err
}
typ, err := p.parseTypeRef()
if err != nil {
return nil, err
}
params = append(params, Param{Name: name.lexeme, Type: typ})
if !p.match(tokenComma) {
return params, nil
}
}
}
func (p *parser) parseBlock() ([]Stmt, error) {
if _, err := p.expect(tokenLBrace, "expected '{'"); err != nil {
return nil, err
}
var stmts []Stmt
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
stmt, err := p.parseStmt()
if err != nil {
return nil, err
}
stmts = append(stmts, stmt)
p.match(tokenSemicolon)
}
if _, err := p.expect(tokenRBrace, "expected '}'"); err != nil {
return nil, err
}
return stmts, nil
}
func (p *parser) parseStmt() (Stmt, error) {
switch {
case p.match(tokenVal):
return p.parseVarDecl(false)
case p.match(tokenVar):
return p.parseVarDecl(true)
case p.match(tokenReturn):
if p.check(tokenSemicolon) || p.check(tokenRBrace) {
return ReturnStmt{}, nil
}
expr, err := p.parseExpr(0)
if err != nil {
return nil, err
}
return ReturnStmt{Value: expr}, nil
case p.match(tokenThrow):
expr, err := p.parseExpr(0)
if err != nil {
return nil, err
}
return ThrowStmt{Value: expr}, nil
case p.match(tokenGo):
var expr Expr
var err error
if p.check(tokenLBrace) {
body, err := p.parseBlock()
if err != nil {
return nil, err
}
expr = CallExpr{Callee: LambdaExpr{Body: body}}
} else {
expr, err = p.parseExpr(0)
if err != nil {
return nil, err
}
}
if _, ok := expr.(CallExpr); !ok {
return nil, fmt.Errorf("'go' expects a function call expression")
}
return GoStmt{Value: expr}, nil
case p.match(tokenDefer):
expr, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, ok := expr.(CallExpr); !ok {
return nil, fmt.Errorf("'defer' expects a function call expression")
}
return DeferStmt{Value: expr}, nil
case p.match(tokenIf):
return p.parseIf()
case p.match(tokenWhile):
return p.parseWhile()
case p.match(tokenFor):
return p.parseForEach()
case p.match(tokenSelect):
return p.parseSelect()
case p.match(tokenMatch):
return p.parseMatch()
case p.match(tokenTry):
return p.parseTryCatch()
case p.check(tokenIdent) && (p.peekN(1).kind == tokenAssign || p.peekN(1).kind == tokenComma || p.peekN(1).kind == tokenPlusAssign):
if p.peekN(1).kind == tokenPlusAssign {
name := p.advance()
if _, err := p.expect(tokenPlusAssign, "expected '+=' in assignment"); err != nil {
return nil, err
}
value, err := p.parseExpr(0)
if err != nil {
return nil, err
}
return AddAssignStmt{Name: name.lexeme, Pos: name.pos, Value: value}, nil
}
names, err := p.parseNameList()
if err != nil {
return nil, err
}
if _, err := p.expect(tokenAssign, "expected '=' in assignment"); err != nil {
return nil, err
}
value, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if len(names) == 1 {
return AssignStmt{Name: names[0].lexeme, Pos: names[0].pos, Value: value}, nil
}
assign := MultiAssignStmt{
Names: make([]string, 0, len(names)),
Positions: make([]int, 0, len(names)),
Value: value,
}
for _, name := range names {
assign.Names = append(assign.Names, name.lexeme)
assign.Positions = append(assign.Positions, name.pos)
}
return assign, nil
default:
expr, err := p.parseExpr(0)
if err != nil {
return nil, err
}
return ExprStmt{Value: expr}, nil
}
}
func (p *parser) parseMatch() (Stmt, error) {
if _, err := p.expect(tokenLParen, "expected '(' after match"); err != nil {
return nil, err
}
value, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after match value"); err != nil {
return nil, err
}
if _, err := p.expect(tokenLBrace, "expected '{' after match value"); err != nil {
return nil, err
}
var cases []MatchCase
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
enumName, err := p.expect(tokenIdent, "expected enum name in match case")
if err != nil {
return nil, err
}
if _, err := p.expect(tokenDoubleColon, "expected '::' in match case"); err != nil {
return nil, err
}
variant, err := p.expect(tokenIdent, "expected variant name")
if err != nil {
return nil, err
}
var bindings []string
if p.match(tokenLParen) {
if !p.check(tokenRParen) {
for {
binding, err := p.expect(tokenIdent, "expected variant binding")
if err != nil {
return nil, err
}
bindings = append(bindings, binding.lexeme)
if !p.match(tokenComma) {
break
}
}
}
if _, err := p.expect(tokenRParen, "expected ')' after variant bindings"); err != nil {
return nil, err
}
}
if _, err := p.expect(tokenArrow, "expected '->' after match pattern"); err != nil {
return nil, err
}
body, err := p.parseBlock()
if err != nil {
return nil, err
}
cases = append(cases, MatchCase{EnumName: enumName.lexeme, VariantName: variant.lexeme, Bindings: bindings, Body: body})
p.match(tokenComma)
p.match(tokenSemicolon)
}
if _, err := p.expect(tokenRBrace, "expected '}' after match"); err != nil {
return nil, err
}
return MatchStmt{Value: value, Cases: cases}, nil
}
func (p *parser) parseSelect() (Stmt, error) {
if _, err := p.expect(tokenLBrace, "expected '{' after select"); err != nil {
return nil, err
}
var cases []SelectCase
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
source, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, err := p.expect(tokenArrow, "expected '->' in select case"); err != nil {
return nil, err
}
var body []Stmt
if p.check(tokenLBrace) {
body, err = p.parseBlock()
if err != nil {
return nil, err
}
} else {
stmt, err := p.parseStmt()
if err != nil {
return nil, err
}
body = []Stmt{stmt}
p.match(tokenSemicolon)
}
cases = append(cases, SelectCase{Source: source, Body: body})
}
if _, err := p.expect(tokenRBrace, "expected '}' after select"); err != nil {
return nil, err
}
return SelectStmt{Cases: cases}, nil
}
func (p *parser) parseTryCatch() (Stmt, error) {
tryBody, err := p.parseBlock()
if err != nil {
return nil, err
}
if _, err := p.expect(tokenCatch, "expected 'catch' after try block"); err != nil {
return nil, err
}
if _, err := p.expect(tokenLParen, "expected '(' after catch"); err != nil {
return nil, err
}
name, err := p.expect(tokenIdent, "expected catch variable name")
if err != nil {
return nil, err
}
catchType := ""
if p.match(tokenColon) {
typ, err := p.parseTypeRef()
if err != nil {
return nil, err
}
catchType = typ
}
if _, err := p.expect(tokenRParen, "expected ')' after catch clause"); err != nil {
return nil, err
}
catchBody, err := p.parseBlock()
if err != nil {
return nil, err
}
return TryCatchStmt{
TryBody: tryBody,
CatchName: name.lexeme,
CatchType: catchType,
CatchBody: catchBody,
}, nil
}
func (p *parser) parseVarDecl(mutable bool) (Stmt, error) {
names, err := p.parseNameList()
if err != nil {
return nil, err
}
name := names[0].lexeme
var typ string
if p.match(tokenColon) {
if len(names) > 1 {
return nil, fmt.Errorf("multi-variable declaration does not support explicit type annotations")
}
typeRef, err := p.parseTypeRef()
if err != nil {
return nil, err
}
typ = typeRef
}
if _, err := p.expect(tokenAssign, "expected '=' in declaration"); err != nil {
return nil, err
}
value, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if len(names) == 1 {
return VarDecl{Mutable: mutable, Name: name, Type: typ, Value: value}, nil
}
decl := MultiVarDecl{Mutable: mutable, Names: make([]string, 0, len(names)), Value: value}
for _, name := range names {
decl.Names = append(decl.Names, name.lexeme)
}
return decl, nil
}
func (p *parser) parseIf() (Stmt, error) {
if _, err := p.expect(tokenLParen, "expected '(' after if"); err != nil {
return nil, err
}
cond, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after condition"); err != nil {
return nil, err
}
thenBlock, err := p.parseBlock()
if err != nil {
return nil, err
}
var elseBlock []Stmt
if p.match(tokenElse) {
elseBlock, err = p.parseBlock()
if err != nil {
return nil, err
}
}
return IfStmt{Cond: cond, Then: thenBlock, Else: elseBlock}, nil
}
func (p *parser) parseWhile() (Stmt, error) {
if _, err := p.expect(tokenLParen, "expected '(' after while"); err != nil {
return nil, err
}
cond, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after condition"); err != nil {
return nil, err
}
body, err := p.parseBlock()
if err != nil {
return nil, err
}
return WhileStmt{Cond: cond, Body: body}, nil
}
func (p *parser) parseExpr(minPrec int) (Expr, error) {
left, err := p.parsePrefix()
if err != nil {
return nil, err
}
for {
op := p.peek()
prec, ok := infixPrecedence(op.kind)
if !ok || prec < minPrec {
return left, nil
}
p.advance()
right, err := p.parseExpr(prec + 1)
if err != nil {
return nil, err
}
left = BinaryExpr{Left: left, Op: op.lexeme, Right: right}
}
}
func (p *parser) parsePrefix() (Expr, error) {
tok := p.advance()
switch tok.kind {
case tokenIdent:
if p.match(tokenDoubleColon) {
variant, err := p.expect(tokenIdent, "expected enum variant")
if err != nil {
return nil, err
}
var values []Expr
if p.match(tokenLParen) {
if !p.check(tokenRParen) {
for {
value, err := p.parseExpr(0)
if err != nil {
return nil, err
}
values = append(values, value)
if !p.match(tokenComma) {
break
}
}
}
if _, err := p.expect(tokenRParen, "expected ')' after variant values"); err != nil {
return nil, err
}
}
return p.parsePostfix(EnumVariantExpr{EnumName: tok.lexeme, VariantName: variant.lexeme, Values: values})
}
return p.parsePostfix(IdentExpr{Name: tok.lexeme})
case tokenInt:
return IntExpr{Value: tok.lexeme}, nil
case tokenFloat:
return FloatExpr{Value: tok.lexeme}, nil
case tokenString:
return StringExpr{Value: tok.lexeme}, nil
case tokenTrue:
return BoolExpr{Value: true}, nil
case tokenFalse:
return BoolExpr{Value: false}, nil
case tokenNull:
return NullExpr{}, nil
case tokenLParen:
expr, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')'"); err != nil {
return nil, err
}
return p.parsePostfix(expr)
case tokenLBrace:
return p.parseLambdaExpr()
case tokenBang, tokenMinus, tokenAmp, tokenStar:
value, err := p.parseExpr(7)
if err != nil {
return nil, err
}
return UnaryExpr{Op: tok.lexeme, Value: value}, nil
default:
return nil, fmt.Errorf("unexpected token %q at %d", tok.lexeme, tok.pos)
}
}
func (p *parser) parseNameList() ([]token, error) {
name, err := p.expect(tokenIdent, "expected variable name")
if err != nil {
return nil, err
}
names := []token{name}
for p.match(tokenComma) {
next, err := p.expect(tokenIdent, "expected variable name")
if err != nil {
return nil, err
}
names = append(names, next)
}
return names, nil
}
func (p *parser) parseLambdaExpr() (Expr, error) {
params, implicitIt, err := p.parseLambdaParams()
if err != nil {
return nil, err
}
var body []Stmt
for !p.check(tokenRBrace) && !p.check(tokenEOF) {
stmt, err := p.parseStmt()
if err != nil {
return nil, err
}
body = append(body, stmt)
p.match(tokenSemicolon)
}
if _, err := p.expect(tokenRBrace, "expected '}' after lambda body"); err != nil {
return nil, err
}
return LambdaExpr{Params: params, ImplicitIt: implicitIt, Body: body}, nil
}
func (p *parser) parseLambdaParams() ([]Param, bool, error) {
if p.match(tokenArrow) {
return nil, false, nil
}
saved := p.pos
var params []Param
for p.check(tokenIdent) {
name := p.advance()
param := Param{Name: name.lexeme}
if p.match(tokenColon) {
typ, err := p.parseTypeRef()
if err != nil {
return nil, false, err
}
param.Type = typ
}
params = append(params, param)
if !p.match(tokenComma) {
break
}
}
if len(params) == 0 {
return nil, true, nil
}
if p.match(tokenArrow) {
return params, false, nil
}
if allUntypedLambdaParams(params) {
p.pos = saved
return nil, true, nil
}
return nil, false, fmt.Errorf("lambda parameters must either all specify types or all omit them")
}
func (p *parser) parsePostfix(expr Expr) (Expr, error) {
for {
switch {
case p.match(tokenDot):
name := p.advance()
if !selectorName(name.lexeme) {
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, name.lexeme)
}
expr = SelectorExpr{Receiver: expr, Name: name.lexeme}
case p.match(tokenLBracket):
index, err := p.parseExpr(0)
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRBracket, "expected ']' after index"); err != nil {
return nil, err
}
expr = IndexExpr{Receiver: expr, Index: index}
case p.check(tokenLt):
typeArgs, hasTypeArgs, err := p.tryParseCallTypeArgs()
if err != nil {
return nil, err
}
if !hasTypeArgs {
return expr, nil
}
if _, err := p.expect(tokenLParen, "expected '(' after generic type arguments"); err != nil {
return nil, err
}
args, namedArgs, err := p.parseCallArguments()
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after arguments"); err != nil {
return nil, err
}
expr = CallExpr{Callee: expr, Args: args, NamedArgs: namedArgs, TypeArgs: typeArgs}
case p.match(tokenLParen):
args, namedArgs, err := p.parseCallArguments()
if err != nil {
return nil, err
}
if _, err := p.expect(tokenRParen, "expected ')' after arguments"); err != nil {
return nil, err
}
expr = CallExpr{Callee: expr, Args: args, NamedArgs: namedArgs}
case p.check(tokenLBrace):
var call CallExpr
switch current := expr.(type) {
case CallExpr:
call = current
case SelectorExpr:
call = CallExpr{Callee: current}
default:
return expr, nil
}
lambda, err := p.parsePrefix()
if err != nil {
return nil, err
}
call.Args = append(call.Args, lambda)
expr = call
default:
return expr, nil
}
}
}
func selectorName(value string) bool {
runes := []rune(value)
if len(runes) == 0 || !isIdentStart(runes[0]) {
return false
}
for _, r := range runes[1:] {
if !isIdentPart(r) {
return false
}
}
return true
}
func (p *parser) parseCallArguments() ([]Expr, []NamedArg, error) {
var args []Expr
var named []NamedArg
if p.check(tokenRParen) {
return args, named, nil
}
for {
if p.check(tokenIdent) && p.peekN(1).kind == tokenAssign {
if len(args) > 0 {
return nil, nil, fmt.Errorf("cannot mix positional and named arguments")
}
name := p.advance().lexeme
p.advance()
value, err := p.parseExpr(0)
if err != nil {
return nil, nil, err
}
named = append(named, NamedArg{Name: name, Value: value})
} else {
if len(named) > 0 {
return nil, nil, fmt.Errorf("cannot mix named and positional arguments")
}
value, err := p.parseExpr(0)
if err != nil {
return nil, nil, err
}
args = append(args, value)
}
if !p.match(tokenComma) {
break
}
}
return args, named, nil
}
func (p *parser) tryParseCallTypeArgs() ([]string, bool, error) {
if !p.check(tokenLt) {
return nil, false, nil
}
saved := p.pos
p.advance()
var typeArgs []string
for {
arg, err := p.parseTypeRef()
if err != nil {
p.pos = saved
return nil, false, nil
}
typeArgs = append(typeArgs, arg)
if !p.match(tokenComma) {
break
}
}
if !p.match(tokenGt) {
p.pos = saved
return nil, false, nil
}
if !p.check(tokenLParen) {
p.pos = saved
return nil, false, nil
}
return typeArgs, true, nil
}
func (p *parser) parseTypeRef() (string, error) {
if p.match(tokenLParen) {
var params []string
if !p.check(tokenRParen) {
for {
paramType, err := p.parseTypeRef()
if err != nil {
return "", err
}
params = append(params, paramType)
if !p.match(tokenComma) {
break
}
}
}
if _, err := p.expect(tokenRParen, "expected ')' in function type"); err != nil {
return "", err
}
if _, err := p.expect(tokenArrow, "expected '->' in function type"); err != nil {
return "", err
}
ret, err := p.parseTypeRef()
if err != nil {
return "", err
}
return "(" + strings.Join(params, ", ") + ") -> " + ret, nil
}
var b string
for p.match(tokenStar) {
b += "*"
}
name, err := p.expect(tokenIdent, "expected type name")
if err != nil {
return "", err
}
b += name.lexeme
for p.match(tokenDot) {
part, err := p.expect(tokenIdent, "expected selector name in type")
if err != nil {
return "", err
}
b += "." + part.lexeme
}
if p.match(tokenLt) {
var args []string
for {
arg, err := p.parseTypeRef()
if err != nil {
return "", err
}
args = append(args, arg)
if !p.match(tokenComma) {
break
}
}
if _, err := p.expect(tokenGt, "expected '>' in generic type"); err != nil {
return "", err
}
b += "<" + strings.Join(args, ", ") + ">"
}
if p.match(tokenQuestion) {
b += "?"
}
return b, nil
}
func (p *parser) parseImportPath() (string, error) {
if !importPathStarts(p.peek()) {
tok := p.peek()
return "", fmt.Errorf("expected import path at %d, found %q", tok.pos, tok.lexeme)
}
var b strings.Builder
b.WriteString(p.advance().lexeme)
for {
if !(p.check(tokenDot) || p.check(tokenMinus)) {
break
}
sep := p.advance().lexeme
part, err := p.expect(tokenIdent, "expected import path segment")
if err != nil {
return "", err
}
b.WriteString(sep)
b.WriteString(part.lexeme)
}
return b.String(), nil
}
func importPathStarts(tok token) bool {
return tok.kind == tokenIdent
}
func importPathContinues(tok token) bool {
switch tok.kind {
case tokenIdent:
return true
default:
return false
}
}
func validateBareImportPath(path string) error {
if strings.HasPrefix(path, `"`) {
return nil
}
return nil
}
func allUntypedLambdaParams(params []Param) bool {
if len(params) == 0 {
return false
}
for _, param := range params {
if param.Type != "" {
return false
}
}
return true
}
func infixPrecedence(kind tokenKind) (int, bool) {
switch kind {
case tokenOr:
return 1, true
case tokenAnd:
return 2, true
case tokenEq, tokenNeq:
return 3, true
case tokenLt, tokenLte, tokenGt, tokenGte:
return 4, true
case tokenPlus, tokenMinus:
return 5, true
case tokenStar, tokenSlash, tokenPercent:
return 6, true
default:
return 0, false
}
}
func (p *parser) expect(kind tokenKind, msg string) (token, error) {
if !p.check(kind) {
tok := p.peek()
return token{}, fmt.Errorf("%s at %d, found %q", msg, tok.pos, tok.lexeme)
}
return p.advance(), nil
}
func (p *parser) match(kind tokenKind) bool {
if p.check(kind) {
p.advance()
return true
}
return false
}
func (p *parser) check(kind tokenKind) bool {
return p.peek().kind == kind
}
func (p *parser) advance() token {
tok := p.tokens[p.pos]
if p.pos < len(p.tokens)-1 {
p.pos++
}
return tok
}
func (p *parser) peek() token {
return p.tokens[p.pos]
}
func (p *parser) peekN(offset int) token {
idx := p.pos + offset
if idx >= len(p.tokens) {
return p.tokens[len(p.tokens)-1]
}
return p.tokens[idx]
}