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) || p.check(tokenSuspend): 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) && !p.check(tokenSuspend) { 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, Suspend: signature.Suspend, }, nil } func (p *parser) parseFunctionSignature() (FunctionSignature, error) { suspend := p.match(tokenSuspend) 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, Suspend: suspend, }, 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) { if p.check(tokenIdent) && p.peek().lexeme == "go" { return nil, fmt.Errorf("bare go is removed; use launch inside a coroutine scope") } 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) parseMatchExpr() (Expr, 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 []MatchExprCase 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 } result, err := p.parseExpr(0) if err != nil { return nil, err } cases = append(cases, MatchExprCase{EnumName: enumName.lexeme, VariantName: variant.lexeme, Bindings: bindings, Value: result}) p.match(tokenComma) p.match(tokenSemicolon) } if _, err := p.expect(tokenRBrace, "expected '}' after match"); err != nil { return nil, err } return MatchExpr{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 tokenMatch: return p.parseMatchExpr() 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(tokenSafeDot): name := p.advance() if !selectorName(name.lexeme) { return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, name.lexeme) } expr = SafeSelectorExpr{Receiver: expr, Name: name.lexeme} case p.match(tokenDoubleBang): expr = NonNullExpr{Value: expr} case p.match(tokenQuestion): expr = TryExpr{Value: expr} 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 p.check(tokenLBrace) { expr = CallExpr{Callee: expr, TypeArgs: typeArgs} continue } 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} case IdentExpr: 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.check(tokenLBrace) { 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] }