1417 lines
34 KiB
Go
1417 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}
|
|
program, err := p.parseProgram()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := hydrateTypeRefs(program); err != nil {
|
|
return nil, err
|
|
}
|
|
return program, nil
|
|
}
|
|
|
|
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(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
|
|
}
|
|
typeParams, err := p.parseTypeParameters()
|
|
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, TypeParams: typeParams, 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, TypeParams: typeParams, Data: data, Fields: fields, Parents: parents, 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,
|
|
TypeParams: signature.TypeParams,
|
|
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
|
|
}
|
|
typeParams, err := p.parseTypeParameters()
|
|
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,
|
|
TypeParams: typeParams,
|
|
Params: params,
|
|
ReturnType: returnType,
|
|
}, nil
|
|
}
|
|
|
|
func (p *parser) parseTypeParameters() ([]string, error) {
|
|
if !p.match(tokenLt) {
|
|
return nil, nil
|
|
}
|
|
var params []string
|
|
seen := map[string]bool{}
|
|
for {
|
|
name, err := p.expect(tokenIdent, "expected type parameter")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if seen[name.lexeme] {
|
|
return nil, fmt.Errorf("duplicate type parameter %s", name.lexeme)
|
|
}
|
|
seen[name.lexeme] = true
|
|
params = append(params, name.lexeme)
|
|
if !p.match(tokenComma) {
|
|
break
|
|
}
|
|
}
|
|
if _, err := p.expect(tokenGt, "expected '>' after type parameters"); err != nil {
|
|
return nil, err
|
|
}
|
|
return params, 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
|
|
}
|
|
ref, err := ParseTypeRef(typ)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params = append(params, Param{Name: name.lexeme, Type: typ, TypeRef: ref})
|
|
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")
|
|
}
|
|
if p.check(tokenIdent) && p.peek().lexeme == "select" {
|
|
return nil, fmt.Errorf("select is removed; use structured coroutines and channel read()")
|
|
}
|
|
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(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(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(tokenDot, "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(tokenDot, "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) 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, Pos: names[0].pos, 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:
|
|
return p.parsePostfix(IdentExpr{Name: tok.lexeme, Pos: tok.pos})
|
|
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
|
|
param.TypeRef, err = ParseTypeRef(typ)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
}
|
|
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, NamePos: name.pos}
|
|
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, NamePos: name.pos}
|
|
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]
|
|
}
|