package js import ( "bytes" "errors" "fmt" "io" "github.com/tdewolff/parse/v2" "github.com/tdewolff/parse/v2/buffer" ) type Options struct { WhileToFor bool Inline bool } // Parser is the state for the parser. type Parser struct { l *Lexer o Options err error data []byte tt TokenType prevLT bool in, await, yield, deflt, retrn bool assumeArrowFunc bool allowDirectivePrologue bool stmtLevel int exprLevel int scope *Scope } // Parse returns a JS AST tree of. func Parse(r *parse.Input, o Options) (*AST, error) { ast := &AST{} p := &Parser{ l: NewLexer(r), o: o, tt: WhitespaceToken, // trick so that next() works in: true, await: true, } if o.Inline { p.next() p.retrn = true p.allowDirectivePrologue = true p.enterScope(&ast.BlockStmt.Scope, true) for { if p.tt == ErrorToken { break } ast.BlockStmt.List = append(ast.BlockStmt.List, p.parseStmt(true)) } } else { // catch shebang in first line var shebang []byte if r.Peek(0) == '#' && r.Peek(1) == '!' { r.Move(2) p.l.consumeSingleLineComment() // consume till end-of-line shebang = r.Shift() } // parse JS module p.next() ast.BlockStmt = p.parseModule() if 0 < len(shebang) { ast.BlockStmt.List = append([]IStmt{&Comment{shebang}}, ast.BlockStmt.List...) } } if p.err == nil { p.err = p.l.Err() } else { offset := p.l.r.Offset() - len(p.data) p.err = parse.NewError(buffer.NewReader(p.l.r.Bytes()), offset, p.err.Error()) } if p.err == io.EOF { p.err = nil } return ast, p.err } //////////////////////////////////////////////////////////////// func (p *Parser) next() { p.prevLT = false p.tt, p.data = p.l.Next() for p.tt == WhitespaceToken || p.tt == LineTerminatorToken || (p.tt == CommentToken || p.tt == CommentLineTerminatorToken) && (p.exprLevel != 0 || len(p.data) < 3 || p.data[2] != '!') { if p.tt == LineTerminatorToken || p.tt == CommentLineTerminatorToken { p.prevLT = true } p.tt, p.data = p.l.Next() } } func (p *Parser) failMessage(msg string, args ...interface{}) { if p.err == nil { p.err = fmt.Errorf(msg, args...) p.tt = ErrorToken } } func (p *Parser) fail(in string, expected ...TokenType) { if p.err == nil { msg := "unexpected" if 0 < len(expected) { msg = "expected" for i, tt := range expected[:len(expected)-1] { if 0 < i { msg += "," } msg += " " + tt.String() + "" } if 2 < len(expected) { msg += ", or" } else if 1 < len(expected) { msg += " or" } msg += " " + expected[len(expected)-1].String() + " instead of" } if p.tt == ErrorToken { if p.l.Err() == io.EOF { msg += " EOF" } else if lexerErr, ok := p.l.Err().(*parse.Error); ok { msg = lexerErr.Message } else { // does not happen } } else { msg += " " + string(p.data) + "" } if in != "" { msg += " in " + in } p.err = errors.New(msg) p.tt = ErrorToken } } func (p *Parser) consume(in string, tt TokenType) bool { if p.tt != tt { p.fail(in, tt) return false } p.next() return true } func (p *Parser) enterScope(scope *Scope, isFunc bool) *Scope { // create a new scope object and add it to the parent parent := p.scope p.scope = scope *scope = Scope{ Parent: parent, } if isFunc { scope.Func = scope } else if parent != nil { scope.Func = parent.Func } return parent } func (p *Parser) exitScope(parent *Scope) { p.scope.HoistUndeclared() p.scope = parent } func (p *Parser) parseModule() (module BlockStmt) { p.enterScope(&module.Scope, true) p.allowDirectivePrologue = true for { switch p.tt { case ErrorToken: return case ImportToken: p.next() if p.tt == OpenParenToken { // could be an import call expression left := &LiteralExpr{ImportToken, []byte("import")} p.exprLevel++ suffix := p.parseExpressionSuffix(left, OpExpr, OpCall) p.exprLevel-- module.List = append(module.List, &ExprStmt{suffix}) if !p.prevLT && p.tt == SemicolonToken { p.next() } } else { importStmt := p.parseImportStmt() module.List = append(module.List, &importStmt) } case ExportToken: exportStmt := p.parseExportStmt() module.List = append(module.List, &exportStmt) default: module.List = append(module.List, p.parseStmt(true)) } } } func (p *Parser) parseStmt(allowDeclaration bool) (stmt IStmt) { p.stmtLevel++ if 1000 < p.stmtLevel { p.failMessage("too many nested statements") return nil } allowDirectivePrologue := p.allowDirectivePrologue p.allowDirectivePrologue = false switch tt := p.tt; tt { case OpenBraceToken: stmt = p.parseBlockStmt("block statement") case ConstToken, VarToken: if !allowDeclaration && tt == ConstToken { p.fail("statement") return } p.next() varDecl := p.parseVarDecl(tt, true) stmt = varDecl if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { if tt == ConstToken { p.fail("const declaration") } else { p.fail("var statement") } return } case LetToken: let := p.data p.next() if allowDeclaration && (IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken || p.tt == OpenBracketToken || p.tt == OpenBraceToken) { stmt = p.parseVarDecl(tt, false) if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { p.fail("let declaration") return } } else { // expression stmt = &ExprStmt{p.parseIdentifierExpression(OpExpr, let)} if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { p.fail("expression") return } } case IfToken: p.next() if !p.consume("if statement", OpenParenToken) { return } cond := p.parseExpression(OpExpr) if !p.consume("if statement", CloseParenToken) { return } body := p.parseStmt(false) var elseBody IStmt if p.tt == ElseToken { p.next() elseBody = p.parseStmt(false) } stmt = &IfStmt{cond, body, elseBody} case ContinueToken, BreakToken: tt := p.tt p.next() var label []byte if !p.prevLT && p.isIdentifierReference(p.tt) { label = p.data p.next() } stmt = &BranchStmt{tt, label} case WithToken: p.next() if !p.consume("with statement", OpenParenToken) { return } cond := p.parseExpression(OpExpr) if !p.consume("with statement", CloseParenToken) { return } p.scope.Func.HasWith = true stmt = &WithStmt{cond, p.parseStmt(false)} case DoToken: stmt = &DoWhileStmt{} p.next() body := p.parseStmt(false) if !p.consume("do-while statement", WhileToken) { return } if !p.consume("do-while statement", OpenParenToken) { return } stmt = &DoWhileStmt{p.parseExpression(OpExpr), body} if !p.consume("do-while statement", CloseParenToken) { return } case WhileToken: p.next() if !p.consume("while statement", OpenParenToken) { return } cond := p.parseExpression(OpExpr) if !p.consume("while statement", CloseParenToken) { return } body := p.parseStmt(false) if p.o.WhileToFor { varDecl := &VarDecl{TokenType: VarToken, Scope: p.scope, InFor: true} p.scope.Func.VarDecls = append(p.scope.Func.VarDecls, varDecl) block, ok := body.(*BlockStmt) if !ok { block = &BlockStmt{List: []IStmt{body}} } stmt = &ForStmt{varDecl, cond, nil, block} } else { stmt = &WhileStmt{cond, body} } case ForToken: p.next() await := p.await && p.tt == AwaitToken if await { p.next() } if !p.consume("for statement", OpenParenToken) { return } body := &BlockStmt{} parent := p.enterScope(&body.Scope, false) var init IExpr p.in = false if p.tt == VarToken || p.tt == LetToken || p.tt == ConstToken { tt := p.tt p.next() varDecl := p.parseVarDecl(tt, true) if p.err != nil { return } else if p.tt != SemicolonToken && (1 < len(varDecl.List) || varDecl.List[0].Default != nil) { p.fail("for statement") return } else if p.tt == SemicolonToken && varDecl.List[0].Default == nil { // all but the first item were already verified if _, ok := varDecl.List[0].Binding.(*Var); !ok { p.fail("for statement") return } } init = varDecl } else if await { init = p.parseExpression(OpLHS) } else if p.tt != SemicolonToken { init = p.parseExpression(OpExpr) } p.in = true isLHSExpr := isLHSExpr(init) if isLHSExpr && p.tt == InToken { if await { p.fail("for statement", OfToken) return } p.next() value := p.parseExpression(OpExpr) if !p.consume("for statement", CloseParenToken) { return } p.scope.MarkForStmt() if p.tt == OpenBraceToken { body.List = p.parseStmtList("") } else if p.tt != SemicolonToken { body.List = []IStmt{p.parseStmt(false)} } else { p.next() } if varDecl, ok := init.(*VarDecl); ok { varDecl.InForInOf = true } stmt = &ForInStmt{init, value, body} } else if isLHSExpr && p.tt == OfToken { p.next() value := p.parseExpression(OpAssign) if !p.consume("for statement", CloseParenToken) { return } p.scope.MarkForStmt() if p.tt == OpenBraceToken { body.List = p.parseStmtList("") } else if p.tt != SemicolonToken { body.List = []IStmt{p.parseStmt(false)} } else { p.next() } if varDecl, ok := init.(*VarDecl); ok { varDecl.InForInOf = true } stmt = &ForOfStmt{await, init, value, body} } else if p.tt == SemicolonToken { var cond, post IExpr if await { p.fail("for statement", OfToken) return } p.next() if p.tt != SemicolonToken { cond = p.parseExpression(OpExpr) } if !p.consume("for statement", SemicolonToken) { return } if p.tt != CloseParenToken { post = p.parseExpression(OpExpr) } if !p.consume("for statement", CloseParenToken) { return } p.scope.MarkForStmt() if p.tt == OpenBraceToken { body.List = p.parseStmtList("") } else if p.tt != SemicolonToken { body.List = []IStmt{p.parseStmt(false)} } else { p.next() } if init == nil { varDecl := &VarDecl{TokenType: VarToken, Scope: p.scope, InFor: true} p.scope.Func.VarDecls = append(p.scope.Func.VarDecls, varDecl) init = varDecl } else if varDecl, ok := init.(*VarDecl); ok { varDecl.InFor = true } stmt = &ForStmt{init, cond, post, body} } else if isLHSExpr { p.fail("for statement", InToken, OfToken, SemicolonToken) return } else { p.fail("for statement", SemicolonToken) return } p.exitScope(parent) case SwitchToken: p.next() if !p.consume("switch statement", OpenParenToken) { return } init := p.parseExpression(OpExpr) if !p.consume("switch statement", CloseParenToken) { return } // case block if !p.consume("switch statement", OpenBraceToken) { return } switchStmt := &SwitchStmt{Init: init} parent := p.enterScope(&switchStmt.Scope, false) for { if p.tt == ErrorToken { p.fail("switch statement") return } else if p.tt == CloseBraceToken { p.next() break } clause := p.tt var list IExpr if p.tt == CaseToken { p.next() list = p.parseExpression(OpExpr) } else if p.tt == DefaultToken { p.next() } else { p.fail("switch statement", CaseToken, DefaultToken) return } if !p.consume("switch statement", ColonToken) { return } var stmts []IStmt for p.tt != CaseToken && p.tt != DefaultToken && p.tt != CloseBraceToken && p.tt != ErrorToken { stmts = append(stmts, p.parseStmt(true)) } switchStmt.List = append(switchStmt.List, CaseClause{clause, list, stmts}) } p.exitScope(parent) stmt = switchStmt case FunctionToken: if !allowDeclaration { p.fail("statement") return } stmt = p.parseFuncDecl() case AsyncToken: // async function if !allowDeclaration { p.fail("statement") return } async := p.data p.next() if p.tt == FunctionToken && !p.prevLT { stmt = p.parseAsyncFuncDecl() } else { // expression stmt = &ExprStmt{p.parseAsyncExpression(OpExpr, async)} if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { p.fail("expression") return } } case ClassToken: if !allowDeclaration { p.fail("statement") return } stmt = p.parseClassDecl() case ThrowToken: p.next() if p.prevLT { p.failMessage("unexpected newline in throw statement") return } stmt = &ThrowStmt{p.parseExpression(OpExpr)} case TryToken: p.next() body := p.parseBlockStmt("try statement") var binding IBinding var catch, finally *BlockStmt if p.tt == CatchToken { p.next() catch = &BlockStmt{} parent := p.enterScope(&catch.Scope, false) if p.tt == OpenParenToken { p.next() binding = p.parseBinding(CatchDecl) // local to block scope of catch if !p.consume("try-catch statement", CloseParenToken) { return } } catch.List = p.parseStmtList("try-catch statement") p.exitScope(parent) } else if p.tt != FinallyToken { p.fail("try statement", CatchToken, FinallyToken) return } if p.tt == FinallyToken { p.next() finally = p.parseBlockStmt("try-finally statement") } stmt = &TryStmt{body, binding, catch, finally} case DebuggerToken: stmt = &DebuggerStmt{} p.next() case SemicolonToken: stmt = &EmptyStmt{} p.next() case CommentToken, CommentLineTerminatorToken: // bang comment stmt = &Comment{p.data} p.next() case ErrorToken: stmt = &EmptyStmt{} return default: if p.retrn && p.tt == ReturnToken { p.next() var value IExpr if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { value = p.parseExpression(OpExpr) } stmt = &ReturnStmt{value} } else if p.isIdentifierReference(p.tt) { // LabelledStatement, Expression label := p.data p.next() if p.tt == ColonToken { p.next() prevDeflt := p.deflt if p.tt == FunctionToken { p.deflt = false } stmt = &LabelledStmt{label, p.parseStmt(true)} // allows illegal async function, generator function, let, const, or class declarations p.deflt = prevDeflt } else { // expression stmt = &ExprStmt{p.parseIdentifierExpression(OpExpr, label)} if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { p.fail("expression") return } } } else { // expression stmt = &ExprStmt{p.parseExpression(OpExpr)} if !p.prevLT && p.tt != SemicolonToken && p.tt != CloseBraceToken && p.tt != ErrorToken { p.fail("expression") return } else if lit, ok := stmt.(*ExprStmt).Value.(*LiteralExpr); ok && allowDirectivePrologue && lit.TokenType == StringToken && len(lit.Data) == 12 && bytes.Equal(lit.Data[1:11], []byte("use strict")) { stmt = &DirectivePrologueStmt{lit.Data} p.allowDirectivePrologue = true } } } if !p.prevLT && p.tt == SemicolonToken { p.next() } p.stmtLevel-- return } func (p *Parser) parseStmtList(in string) (list []IStmt) { if !p.consume(in, OpenBraceToken) { return } for { if p.tt == ErrorToken { p.fail("") return } else if p.tt == CloseBraceToken { p.next() break } list = append(list, p.parseStmt(true)) } return } func (p *Parser) parseBlockStmt(in string) (blockStmt *BlockStmt) { blockStmt = &BlockStmt{} parent := p.enterScope(&blockStmt.Scope, false) blockStmt.List = p.parseStmtList(in) p.exitScope(parent) return } func (p *Parser) parseImportStmt() (importStmt ImportStmt) { // assume we're passed import if p.tt == StringToken { importStmt.Module = p.data p.next() } else { expectClause := true if IsIdentifier(p.tt) || p.tt == YieldToken { importStmt.Default = p.data p.next() expectClause = p.tt == CommaToken if expectClause { p.next() } } if expectClause && p.tt == MulToken { star := p.data p.next() if !p.consume("import statement", AsToken) { return } if !IsIdentifier(p.tt) && p.tt != YieldToken { p.fail("import statement", IdentifierToken) return } importStmt.List = []Alias{Alias{star, p.data}} p.next() } else if expectClause && p.tt == OpenBraceToken { p.next() importStmt.List = []Alias{} for IsIdentifierName(p.tt) || p.tt == StringToken { tt := p.tt var name, binding []byte = nil, p.data p.next() if p.tt == AsToken { p.next() if !IsIdentifier(p.tt) && p.tt != YieldToken { p.fail("import statement", IdentifierToken) return } name = binding binding = p.data p.next() } else if !IsIdentifier(tt) && tt != YieldToken || tt == StringToken { p.fail("import statement", IdentifierToken, StringToken) return } importStmt.List = append(importStmt.List, Alias{name, binding}) if p.tt == CommaToken { p.next() if p.tt == CloseBraceToken { importStmt.List = append(importStmt.List, Alias{}) break } } } if !p.consume("import statement", CloseBraceToken) { return } } else if expectClause && importStmt.Default != nil { p.fail("import statement", MulToken, OpenBraceToken) return } else if importStmt.Default == nil { p.fail("import statement", StringToken, IdentifierToken, MulToken, OpenBraceToken) return } if !p.consume("import statement", FromToken) { return } if p.tt != StringToken { p.fail("import statement", StringToken) return } importStmt.Module = p.data p.next() } if p.tt == SemicolonToken { p.next() } return } func (p *Parser) parseExportStmt() (exportStmt ExportStmt) { // assume we're at export p.next() prevYield, prevAwait, prevDeflt := p.yield, p.await, p.deflt p.yield, p.await, p.deflt = false, true, true if p.tt == MulToken || p.tt == OpenBraceToken { if p.tt == MulToken { star := p.data p.next() if p.tt == AsToken { p.next() if !IsIdentifierName(p.tt) && p.tt != StringToken { p.fail("export statement", IdentifierToken, StringToken) return } exportStmt.List = []Alias{Alias{star, p.data}} p.next() } else { exportStmt.List = []Alias{Alias{nil, star}} } if p.tt != FromToken { p.fail("export statement", FromToken) return } } else { p.next() for IsIdentifierName(p.tt) || p.tt == StringToken { var name, binding []byte = nil, p.data p.next() if p.tt == AsToken { p.next() if !IsIdentifierName(p.tt) && p.tt != StringToken { p.fail("export statement", IdentifierToken, StringToken) return } name = binding binding = p.data p.next() } exportStmt.List = append(exportStmt.List, Alias{name, binding}) if p.tt == CommaToken { p.next() if p.tt == CloseBraceToken { exportStmt.List = append(exportStmt.List, Alias{}) break } } } if !p.consume("export statement", CloseBraceToken) { return } } if p.tt == FromToken { p.next() if p.tt != StringToken { p.fail("export statement", StringToken) return } exportStmt.Module = p.data p.next() } } else if p.tt == VarToken || p.tt == ConstToken || p.tt == LetToken { tt := p.tt p.next() exportStmt.Decl = p.parseVarDecl(tt, false) } else if p.tt == FunctionToken { exportStmt.Decl = p.parseFuncDecl() } else if p.tt == AsyncToken { // async function p.next() if p.tt != FunctionToken || p.prevLT { p.fail("export statement", FunctionToken) return } exportStmt.Decl = p.parseAsyncFuncDecl() } else if p.tt == ClassToken { exportStmt.Decl = p.parseClassDecl() } else if p.tt == DefaultToken { exportStmt.Default = true p.next() if p.tt == FunctionToken { exportStmt.Decl = p.parseFuncDecl() } else if p.tt == AsyncToken { // async function or async arrow function async := p.data p.next() if p.tt == FunctionToken && !p.prevLT { exportStmt.Decl = p.parseAsyncFuncDecl() } else { // expression exportStmt.Decl = p.parseAsyncExpression(OpExpr, async) } } else if p.tt == ClassToken { exportStmt.Decl = p.parseClassDecl() } else { exportStmt.Decl = p.parseExpression(OpAssign) } } else { p.fail("export statement", MulToken, OpenBraceToken, VarToken, LetToken, ConstToken, FunctionToken, AsyncToken, ClassToken, DefaultToken) return } if p.tt == SemicolonToken { p.next() } p.yield, p.await, p.deflt = prevYield, prevAwait, prevDeflt return } func (p *Parser) parseVarDecl(tt TokenType, canBeHoisted bool) (varDecl *VarDecl) { // assume we're past var, let or const varDecl = &VarDecl{ TokenType: tt, Scope: p.scope, } declType := LexicalDecl if tt == VarToken { declType = VariableDecl if canBeHoisted { p.scope.Func.VarDecls = append(p.scope.Func.VarDecls, varDecl) } } for { // binding element, var declaration in for-in or for-of can never have a default var bindingElement BindingElement bindingElement.Binding = p.parseBinding(declType) if p.tt == EqToken { p.next() bindingElement.Default = p.parseExpression(OpAssign) } else if _, ok := bindingElement.Binding.(*Var); !ok && (p.in || 0 < len(varDecl.List)) { p.fail("var statement", EqToken) return } else if tt == ConstToken && (p.in || !p.in && p.tt != OfToken && p.tt != InToken) { p.fail("const statement", EqToken) } varDecl.List = append(varDecl.List, bindingElement) if p.tt == CommaToken { p.next() } else { break } } return } func (p *Parser) parseFuncParams(in string) (params Params) { // FormalParameters if !p.consume(in, OpenParenToken) { return } for p.tt != CloseParenToken && p.tt != ErrorToken { if p.tt == EllipsisToken { // binding rest element p.next() params.Rest = p.parseBinding(ArgumentDecl) p.consume(in, CloseParenToken) return } params.List = append(params.List, p.parseBindingElement(ArgumentDecl)) if p.tt != CommaToken { break } p.next() } if p.tt != CloseParenToken { p.fail(in) return } p.next() // mark undeclared vars as arguments in `function f(a=b){var b}` where the b's are different vars p.scope.MarkFuncArgs() return } func (p *Parser) parseFuncDecl() (funcDecl *FuncDecl) { return p.parseFunc(false, false) } func (p *Parser) parseAsyncFuncDecl() (funcDecl *FuncDecl) { return p.parseFunc(true, false) } func (p *Parser) parseFuncExpr() (funcDecl *FuncDecl) { return p.parseFunc(false, true) } func (p *Parser) parseAsyncFuncExpr() (funcDecl *FuncDecl) { return p.parseFunc(true, true) } func (p *Parser) parseFunc(async, expr bool) (funcDecl *FuncDecl) { // assume we're at function p.next() funcDecl = &FuncDecl{} funcDecl.Async = async funcDecl.Generator = p.tt == MulToken if funcDecl.Generator { p.next() } var ok bool var name []byte if expr && (IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken) || !expr && p.isIdentifierReference(p.tt) { name = p.data if !expr { funcDecl.Name, ok = p.scope.Declare(FunctionDecl, p.data) if !ok { p.failMessage("identifier %s has already been declared", string(p.data)) return } } p.next() } else if !expr && !p.deflt { p.fail("function declaration", IdentifierToken) return } else if p.tt != OpenParenToken { p.fail("function declaration", IdentifierToken, OpenParenToken) return } parent := p.enterScope(&funcDecl.Body.Scope, true) prevAwait, prevYield, prevRetrn := p.await, p.yield, p.retrn p.await, p.yield, p.retrn = funcDecl.Async, funcDecl.Generator, true if expr && name != nil { funcDecl.Name, _ = p.scope.Declare(ExprDecl, name) // cannot fail } funcDecl.Params = p.parseFuncParams("function declaration") prevAllowDirectivePrologue, prevExprLevel := p.allowDirectivePrologue, p.exprLevel p.allowDirectivePrologue, p.exprLevel = true, 0 funcDecl.Body.List = p.parseStmtList("function declaration") p.allowDirectivePrologue, p.exprLevel = prevAllowDirectivePrologue, prevExprLevel p.await, p.yield, p.retrn = prevAwait, prevYield, prevRetrn p.exitScope(parent) return } func (p *Parser) parseClassDecl() (classDecl *ClassDecl) { return p.parseAnyClass(false) } func (p *Parser) parseClassExpr() (classDecl *ClassDecl) { return p.parseAnyClass(true) } func (p *Parser) parseAnyClass(expr bool) (classDecl *ClassDecl) { // assume we're at class p.next() classDecl = &ClassDecl{} if IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken { if !expr { var ok bool classDecl.Name, ok = p.scope.Declare(LexicalDecl, p.data) if !ok { p.failMessage("identifier %s has already been declared", string(p.data)) return } } else { //classDecl.Name, ok = p.scope.Declare(ExprDecl, p.data) // classes do not register vars classDecl.Name = &Var{p.data, nil, 1, ExprDecl} } p.next() } else if !expr && !p.deflt { p.fail("class declaration", IdentifierToken) return } if p.tt == ExtendsToken { p.next() classDecl.Extends = p.parseExpression(OpLHS) } if !p.consume("class declaration", OpenBraceToken) { return } for { if p.tt == ErrorToken { p.fail("class declaration") return } else if p.tt == SemicolonToken { p.next() continue } else if p.tt == CloseBraceToken { p.next() break } classDecl.List = append(classDecl.List, p.parseClassElement()) } return } func (p *Parser) parseClassElement() ClassElement { method := &MethodDecl{} var data []byte // either static, async, get, or set if p.tt == StaticToken { method.Static = true data = p.data p.next() if p.tt == OpenBraceToken { prevYield, prevAwait, prevRetrn := p.yield, p.await, p.retrn p.yield, p.await, p.retrn = false, true, false elem := ClassElement{StaticBlock: p.parseBlockStmt("class static block")} p.yield, p.await, p.retrn = prevYield, prevAwait, prevRetrn return elem } } if p.tt == MulToken { method.Generator = true p.next() } else if p.tt == AsyncToken { data = p.data p.next() if !p.prevLT { method.Async = true if p.tt == MulToken { method.Generator = true data = nil p.next() } } } else if p.tt == GetToken { method.Get = true data = p.data p.next() } else if p.tt == SetToken { method.Set = true data = p.data p.next() } isField := false if data != nil && p.tt == OpenParenToken { // (static) method name is: static, async, get, or set method.Name.Literal = LiteralExpr{IdentifierToken, data} if method.Async || method.Get || method.Set { method.Async = false method.Get = false method.Set = false } else { method.Static = false } } else if data != nil && (p.tt == EqToken || p.tt == SemicolonToken || p.tt == CloseBraceToken) { // (static) field name is: static, async, get, or set method.Name.Literal = LiteralExpr{IdentifierToken, data} if !method.Async && !method.Get && !method.Set { method.Static = false } isField = true } else { if p.tt == PrivateIdentifierToken { method.Name.Literal = LiteralExpr{p.tt, p.data} p.next() } else { method.Name = p.parsePropertyName("method or field definition") } if (data == nil || method.Static) && p.tt != OpenParenToken { isField = true } } if isField { var init IExpr if p.tt == EqToken { p.next() init = p.parseExpression(OpAssign) } return ClassElement{Field: Field{Static: method.Static, Name: method.Name, Init: init}} } parent := p.enterScope(&method.Body.Scope, true) prevAwait, prevYield, prevRetrn := p.await, p.yield, p.retrn p.await, p.yield, p.retrn = method.Async, method.Generator, true method.Params = p.parseFuncParams("method definition") prevAllowDirectivePrologue, prevExprLevel := p.allowDirectivePrologue, p.exprLevel p.allowDirectivePrologue, p.exprLevel = true, 0 method.Body.List = p.parseStmtList("method function") p.allowDirectivePrologue, p.exprLevel = prevAllowDirectivePrologue, prevExprLevel p.await, p.yield, p.retrn = prevAwait, prevYield, prevRetrn p.exitScope(parent) return ClassElement{Method: method} } func (p *Parser) parsePropertyName(in string) (propertyName PropertyName) { if IsIdentifierName(p.tt) { propertyName.Literal = LiteralExpr{IdentifierToken, p.data} p.next() } else if p.tt == StringToken { // reinterpret string as identifier or number if we can, except for empty strings if isIdent := AsIdentifierName(p.data[1 : len(p.data)-1]); isIdent { propertyName.Literal = LiteralExpr{IdentifierToken, p.data[1 : len(p.data)-1]} } else if isNum := AsDecimalLiteral(p.data[1 : len(p.data)-1]); isNum { propertyName.Literal = LiteralExpr{DecimalToken, p.data[1 : len(p.data)-1]} } else { propertyName.Literal = LiteralExpr{p.tt, p.data} } p.next() } else if IsNumeric(p.tt) { propertyName.Literal = LiteralExpr{p.tt, p.data} p.next() } else if p.tt == OpenBracketToken { p.next() propertyName.Computed = p.parseExpression(OpAssign) if !p.consume(in, CloseBracketToken) { return } } else { p.fail(in, IdentifierToken, StringToken, NumericToken, OpenBracketToken) return } return } func (p *Parser) parseBindingElement(decl DeclType) (bindingElement BindingElement) { // BindingElement bindingElement.Binding = p.parseBinding(decl) if p.tt == EqToken { p.next() bindingElement.Default = p.parseExpression(OpAssign) } return } func (p *Parser) parseBinding(decl DeclType) (binding IBinding) { // BindingIdentifier, BindingPattern if p.isIdentifierReference(p.tt) { var ok bool binding, ok = p.scope.Declare(decl, p.data) if !ok { p.failMessage("identifier %s has already been declared", string(p.data)) return } p.next() } else if p.tt == OpenBracketToken { p.next() array := BindingArray{} if p.tt == CommaToken { array.List = append(array.List, BindingElement{}) } last := 0 for p.tt != CloseBracketToken { // elision for p.tt == CommaToken { p.next() if p.tt == CommaToken { array.List = append(array.List, BindingElement{}) } } // binding rest element if p.tt == EllipsisToken { p.next() array.Rest = p.parseBinding(decl) if p.tt != CloseBracketToken { p.fail("array binding pattern", CloseBracketToken) return } break } else if p.tt == CloseBracketToken { array.List = array.List[:last] break } array.List = append(array.List, p.parseBindingElement(decl)) last = len(array.List) if p.tt != CommaToken && p.tt != CloseBracketToken { p.fail("array binding pattern", CommaToken, CloseBracketToken) return } } p.next() // always CloseBracketToken binding = &array } else if p.tt == OpenBraceToken { p.next() object := BindingObject{} for p.tt != CloseBraceToken { // binding rest property if p.tt == EllipsisToken { p.next() if !p.isIdentifierReference(p.tt) { p.fail("object binding pattern", IdentifierToken) return } var ok bool object.Rest, ok = p.scope.Declare(decl, p.data) if !ok { p.failMessage("identifier %s has already been declared", string(p.data)) return } p.next() if p.tt != CloseBraceToken { p.fail("object binding pattern", CloseBraceToken) return } break } item := BindingObjectItem{} if p.isIdentifierReference(p.tt) { name := p.data item.Key = &PropertyName{LiteralExpr{IdentifierToken, p.data}, nil} p.next() if p.tt == ColonToken { // property name + : + binding element p.next() item.Value = p.parseBindingElement(decl) } else { // single name binding var ok bool item.Key.Literal.Data = parse.Copy(item.Key.Literal.Data) // copy so that renaming doesn't rename the key item.Value.Binding, ok = p.scope.Declare(decl, name) if !ok { p.failMessage("identifier %s has already been declared", string(name)) return } if p.tt == EqToken { p.next() item.Value.Default = p.parseExpression(OpAssign) } } } else { propertyName := p.parsePropertyName("object binding pattern") item.Key = &propertyName if !p.consume("object binding pattern", ColonToken) { return } item.Value = p.parseBindingElement(decl) } object.List = append(object.List, item) if p.tt == CommaToken { p.next() } else if p.tt != CloseBraceToken { p.fail("object binding pattern", CommaToken, CloseBraceToken) return } } p.next() // always CloseBracketToken binding = &object } else { p.fail("binding") return } return } func (p *Parser) parseArrayLiteral() (array ArrayExpr) { // assume we're on [ p.next() prevComma := true for { if p.tt == ErrorToken { p.fail("expression") return } else if p.tt == CloseBracketToken { p.next() break } else if p.tt == CommaToken { if prevComma { array.List = append(array.List, Element{}) } prevComma = true p.next() } else { spread := p.tt == EllipsisToken if spread { p.next() } array.List = append(array.List, Element{p.parseAssignExprOrParam(), spread}) prevComma = false if spread && p.tt != CloseBracketToken { p.assumeArrowFunc = false } } } return } func (p *Parser) parseObjectLiteral() (object ObjectExpr) { // assume we're on { p.next() for { if p.tt == ErrorToken { p.fail("object literal", CloseBraceToken) return } else if p.tt == CloseBraceToken { p.next() break } property := Property{} if p.tt == EllipsisToken { p.next() property.Spread = true property.Value = p.parseAssignExprOrParam() if _, isIdent := property.Value.(*Var); !isIdent || p.tt != CloseBraceToken { p.assumeArrowFunc = false } } else { // try to parse as MethodDefinition, otherwise fall back to PropertyName:AssignExpr or IdentifierReference var data []byte method := MethodDecl{} if p.tt == MulToken { p.next() method.Generator = true } else if p.tt == AsyncToken { data = p.data p.next() if !p.prevLT { method.Async = true if p.tt == MulToken { p.next() method.Generator = true data = nil } } else { method.Name.Literal = LiteralExpr{IdentifierToken, data} data = nil } } else if p.tt == GetToken { data = p.data p.next() method.Get = true } else if p.tt == SetToken { data = p.data p.next() method.Set = true } // PropertyName if data != nil && !method.Generator && (p.tt == EqToken || p.tt == CommaToken || p.tt == CloseBraceToken || p.tt == ColonToken || p.tt == OpenParenToken) { method.Name.Literal = LiteralExpr{IdentifierToken, data} method.Async = false method.Get = false method.Set = false } else if !method.Name.IsSet() { // did not parse async [LT] method.Name = p.parsePropertyName("object literal") if !method.Name.IsSet() { return } } if p.tt == OpenParenToken { // MethodDefinition parent := p.enterScope(&method.Body.Scope, true) prevAwait, prevYield, prevRetrn := p.await, p.yield, p.retrn p.await, p.yield, p.retrn = method.Async, method.Generator, true method.Params = p.parseFuncParams("method definition") method.Body.List = p.parseStmtList("method definition") p.await, p.yield, p.retrn = prevAwait, prevYield, prevRetrn p.exitScope(parent) property.Value = &method p.assumeArrowFunc = false } else if p.tt == ColonToken { // PropertyName : AssignmentExpression p.next() property.Name = &method.Name property.Value = p.parseAssignExprOrParam() } else if method.Name.IsComputed() || !p.isIdentifierReference(method.Name.Literal.TokenType) { p.fail("object literal", ColonToken, OpenParenToken) return } else { // IdentifierReference (= AssignmentExpression)? name := method.Name.Literal.Data method.Name.Literal.Data = parse.Copy(method.Name.Literal.Data) // copy so that renaming doesn't rename the key property.Name = &method.Name // set key explicitly so after renaming the original is still known if p.assumeArrowFunc { var ok bool property.Value, ok = p.scope.Declare(ArgumentDecl, name) if !ok { property.Value = p.scope.Use(name) p.assumeArrowFunc = false } } else { property.Value = p.scope.Use(name) } if p.tt == EqToken { p.next() prevAssumeArrowFunc := p.assumeArrowFunc p.assumeArrowFunc = false property.Init = p.parseExpression(OpAssign) p.assumeArrowFunc = prevAssumeArrowFunc } } } object.List = append(object.List, property) if p.tt == CommaToken { p.next() } else if p.tt != CloseBraceToken { p.fail("object literal") return } } return } func (p *Parser) parseTemplateLiteral(precLeft OpPrec) (template TemplateExpr) { // assume we're on 'Template' or 'TemplateStart' template.Prec = OpMember if precLeft < OpMember { template.Prec = OpCall } for p.tt == TemplateStartToken || p.tt == TemplateMiddleToken { tpl := p.data p.next() template.List = append(template.List, TemplatePart{tpl, p.parseExpression(OpExpr)}) } if p.tt != TemplateToken && p.tt != TemplateEndToken { p.fail("template literal", TemplateToken) return } template.Tail = p.data p.next() // TemplateEndToken return } func (p *Parser) parseArguments() (args Args) { // assume we're on ( p.next() args.List = make([]Arg, 0, 4) for p.tt != CloseParenToken && p.tt != ErrorToken { rest := p.tt == EllipsisToken if rest { p.next() } args.List = append(args.List, Arg{ Value: p.parseExpression(OpAssign), Rest: rest, }) if p.tt != CloseParenToken { if p.tt != CommaToken { p.fail("arguments", CommaToken, CloseParenToken) return } else { p.next() // CommaToken } } } p.consume("arguments", CloseParenToken) return } func (p *Parser) parseAsyncArrowFunc() (arrowFunc *ArrowFunc) { // expect we're at Identifier or Yield or ( arrowFunc = &ArrowFunc{} parent := p.enterScope(&arrowFunc.Body.Scope, true) prevAwait, prevYield := p.await, p.yield p.await, p.yield = true, false if IsIdentifier(p.tt) || !prevYield && p.tt == YieldToken { ref, _ := p.scope.Declare(ArgumentDecl, p.data) // cannot fail p.next() arrowFunc.Params.List = []BindingElement{{Binding: ref}} } else { arrowFunc.Params = p.parseFuncParams("arrow function") // CallExpression of 'async(params)' already handled } arrowFunc.Async = true arrowFunc.Body.List = p.parseArrowFuncBody() p.await, p.yield = prevAwait, prevYield p.exitScope(parent) return } func (p *Parser) parseIdentifierArrowFunc(v *Var) (arrowFunc *ArrowFunc) { // expect we're at => arrowFunc = &ArrowFunc{} parent := p.enterScope(&arrowFunc.Body.Scope, true) prevAwait, prevYield := p.await, p.yield p.await, p.yield = false, false if 1 < v.Uses { v.Uses-- v, _ = p.scope.Declare(ArgumentDecl, parse.Copy(v.Data)) // cannot fail } else { // if v.Uses==1 it must be undeclared and be the last added p.scope.Parent.Undeclared = p.scope.Parent.Undeclared[:len(p.scope.Parent.Undeclared)-1] v.Decl = ArgumentDecl p.scope.Declared = append(p.scope.Declared, v) } arrowFunc.Params.List = []BindingElement{{v, nil}} arrowFunc.Body.List = p.parseArrowFuncBody() p.await, p.yield = prevAwait, prevYield p.exitScope(parent) return } func (p *Parser) parseArrowFuncBody() (list []IStmt) { // expect we're at arrow if p.tt != ArrowToken { p.fail("arrow function", ArrowToken) return } else if p.prevLT { p.fail("expression") return } p.next() // mark undeclared vars as arguments in `function f(a=b){var b}` where the b's are different vars p.scope.MarkFuncArgs() if p.tt == OpenBraceToken { prevIn, prevRetrn := p.in, p.retrn p.in, p.retrn = true, true prevAllowDirectivePrologue, prevExprLevel := p.allowDirectivePrologue, p.exprLevel p.allowDirectivePrologue, p.exprLevel = true, 0 list = p.parseStmtList("arrow function") p.allowDirectivePrologue, p.exprLevel = prevAllowDirectivePrologue, prevExprLevel p.in, p.retrn = prevIn, prevRetrn } else { list = []IStmt{&ReturnStmt{p.parseExpression(OpAssign)}} } return } func (p *Parser) parseIdentifierExpression(prec OpPrec, ident []byte) IExpr { var left IExpr left = p.scope.Use(ident) return p.parseExpressionSuffix(left, prec, OpPrimary) } func (p *Parser) parseAsyncExpression(prec OpPrec, async []byte) IExpr { // IdentifierReference, AsyncFunctionExpression, AsyncGeneratorExpression // CoverCallExpressionAndAsyncArrowHead, AsyncArrowFunction // assume we're at a token after async var left IExpr precLeft := OpPrimary if !p.prevLT && p.tt == FunctionToken { // primary expression left = p.parseAsyncFuncExpr() } else if !p.prevLT && prec <= OpAssign && (p.tt == OpenParenToken || IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken) { // async arrow function expression or call expression if p.tt == AwaitToken || p.yield && p.tt == YieldToken { p.fail("arrow function") return nil } else if p.tt == OpenParenToken { return p.parseParenthesizedExpression(prec, async) } left = p.parseAsyncArrowFunc() precLeft = OpAssign } else { left = p.scope.Use(async) } // can be async(args), async => ..., or e.g. async + ... return p.parseExpressionSuffix(left, prec, precLeft) } // parseExpression parses an expression that has a precedence of prec or higher. func (p *Parser) parseExpression(prec OpPrec) IExpr { p.exprLevel++ if 1000 < p.exprLevel { p.failMessage("too many nested expressions") return nil } // reparse input if we have / or /= as the beginning of a new expression, this should be a regular expression! if p.tt == DivToken || p.tt == DivEqToken { p.tt, p.data = p.l.RegExp() if p.tt == ErrorToken { p.fail("regular expression") return nil } } var left IExpr precLeft := OpPrimary if IsIdentifier(p.tt) && p.tt != AsyncToken { left = p.scope.Use(p.data) p.next() suffix := p.parseExpressionSuffix(left, prec, precLeft) p.exprLevel-- return suffix } else if IsNumeric(p.tt) { left = &LiteralExpr{p.tt, p.data} p.next() suffix := p.parseExpressionSuffix(left, prec, precLeft) p.exprLevel-- return suffix } switch tt := p.tt; tt { case StringToken, ThisToken, NullToken, TrueToken, FalseToken, RegExpToken: left = &LiteralExpr{p.tt, p.data} p.next() case OpenBracketToken: prevIn := p.in p.in = true array := p.parseArrayLiteral() p.in = prevIn left = &array case OpenBraceToken: prevIn := p.in p.in = true object := p.parseObjectLiteral() p.in = prevIn left = &object case OpenParenToken: // parenthesized expression or arrow parameter list if OpAssign < prec { // must be a parenthesized expression p.next() prevIn := p.in p.in = true left = &GroupExpr{p.parseExpression(OpExpr)} p.in = prevIn if !p.consume("expression", CloseParenToken) { return nil } break } suffix := p.parseParenthesizedExpression(prec, nil) p.exprLevel-- return suffix case NotToken, BitNotToken, TypeofToken, VoidToken, DeleteToken: if OpUnary < prec { p.fail("expression") return nil } p.next() left = &UnaryExpr{tt, p.parseExpression(OpUnary)} precLeft = OpUnary case AddToken: if OpUnary < prec { p.fail("expression") return nil } p.next() left = &UnaryExpr{PosToken, p.parseExpression(OpUnary)} precLeft = OpUnary case SubToken: if OpUnary < prec { p.fail("expression") return nil } p.next() left = &UnaryExpr{NegToken, p.parseExpression(OpUnary)} precLeft = OpUnary case IncrToken: if OpUpdate < prec { p.fail("expression") return nil } p.next() left = &UnaryExpr{PreIncrToken, p.parseExpression(OpUnary)} precLeft = OpUnary case DecrToken: if OpUpdate < prec { p.fail("expression") return nil } p.next() left = &UnaryExpr{PreDecrToken, p.parseExpression(OpUnary)} precLeft = OpUnary case AwaitToken: // either accepted as IdentifierReference or as AwaitExpression if p.await && prec <= OpUnary { p.next() left = &UnaryExpr{tt, p.parseExpression(OpUnary)} precLeft = OpUnary } else if p.await { p.fail("expression") return nil } else { left = p.scope.Use(p.data) p.next() } case NewToken: p.next() if p.tt == DotToken { p.next() if !p.consume("new.target expression", TargetToken) { return nil } left = &NewTargetExpr{} precLeft = OpMember } else { newExpr := &NewExpr{p.parseExpression(OpNew), nil} if p.tt == OpenParenToken { args := p.parseArguments() if len(args.List) != 0 { newExpr.Args = &args } precLeft = OpMember } else { precLeft = OpNew } left = newExpr } case ImportToken: // OpMember < prec does never happen left = &LiteralExpr{p.tt, p.data} p.next() if p.tt == DotToken { p.next() if !p.consume("import.meta expression", MetaToken) { return nil } left = &ImportMetaExpr{} precLeft = OpMember } else if p.tt != OpenParenToken { p.fail("import expression", OpenParenToken) return nil } else if OpCall < prec { p.fail("expression") return nil } else { precLeft = OpCall } case SuperToken: // OpMember < prec does never happen left = &LiteralExpr{p.tt, p.data} p.next() if OpCall < prec && p.tt != DotToken && p.tt != OpenBracketToken { p.fail("super expression", OpenBracketToken, DotToken) return nil } else if p.tt != DotToken && p.tt != OpenBracketToken && p.tt != OpenParenToken { p.fail("super expression", OpenBracketToken, OpenParenToken, DotToken) return nil } if OpCall < prec { precLeft = OpMember } else { precLeft = OpCall } case YieldToken: // either accepted as IdentifierReference or as YieldExpression if p.yield && prec <= OpAssign { // YieldExpression p.next() yieldExpr := YieldExpr{} if !p.prevLT { yieldExpr.Generator = p.tt == MulToken if yieldExpr.Generator { p.next() yieldExpr.X = p.parseExpression(OpAssign) } else if p.tt != CloseBraceToken && p.tt != CloseBracketToken && p.tt != CloseParenToken && p.tt != ColonToken && p.tt != CommaToken && p.tt != SemicolonToken { yieldExpr.X = p.parseExpression(OpAssign) } } left = &yieldExpr precLeft = OpAssign } else if p.yield { p.fail("expression") return nil } else { left = p.scope.Use(p.data) p.next() } case AsyncToken: async := p.data p.next() prevIn := p.in p.in = true left = p.parseAsyncExpression(prec, async) p.in = prevIn case ClassToken: prevIn := p.in p.in = true left = p.parseClassExpr() p.in = prevIn case FunctionToken: prevIn := p.in p.in = true left = p.parseFuncExpr() p.in = prevIn case TemplateToken, TemplateStartToken: prevIn := p.in p.in = true template := p.parseTemplateLiteral(precLeft) left = &template p.in = prevIn case PrivateIdentifierToken: if OpCompare < prec || !p.in { p.fail("expression") return nil } left = &LiteralExpr{p.tt, p.data} p.next() if p.tt != InToken { p.fail("relational expression", InToken) return nil } default: p.fail("expression") return nil } suffix := p.parseExpressionSuffix(left, prec, precLeft) p.exprLevel-- return suffix } func (p *Parser) parseExpressionSuffix(left IExpr, prec, precLeft OpPrec) IExpr { for i := 0; ; i++ { if 1000 < p.exprLevel+i { p.failMessage("too many nested expressions") return nil } switch tt := p.tt; tt { case EqToken, MulEqToken, DivEqToken, ModEqToken, ExpEqToken, AddEqToken, SubEqToken, LtLtEqToken, GtGtEqToken, GtGtGtEqToken, BitAndEqToken, BitXorEqToken, BitOrEqToken, AndEqToken, OrEqToken, NullishEqToken: if OpAssign < prec { return left } else if precLeft < OpLHS { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpAssign)} precLeft = OpAssign case LtToken, LtEqToken, GtToken, GtEqToken, InToken, InstanceofToken: if OpCompare < prec || !p.in && tt == InToken { return left } else if precLeft < OpCompare { // can only fail after a yield or arrow function expression p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpShift)} precLeft = OpCompare case EqEqToken, NotEqToken, EqEqEqToken, NotEqEqToken: if OpEquals < prec { return left } else if precLeft < OpEquals { // can only fail after a yield or arrow function expression p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpCompare)} precLeft = OpEquals case AndToken: if OpAnd < prec { return left } else if precLeft < OpAnd { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpBitOr)} precLeft = OpAnd case OrToken: if OpOr < prec { return left } else if precLeft < OpOr { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpAnd)} precLeft = OpOr case NullishToken: if OpCoalesce < prec { return left } else if precLeft < OpBitOr && precLeft != OpCoalesce { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpBitOr)} precLeft = OpCoalesce case DotToken: // OpMember < prec does never happen if precLeft < OpCall { p.fail("expression") return nil } p.next() if !IsIdentifierName(p.tt) && p.tt != PrivateIdentifierToken { p.fail("dot expression", IdentifierToken) return nil } exprPrec := OpMember if precLeft < OpMember { exprPrec = OpCall } if p.tt != PrivateIdentifierToken { p.tt = IdentifierToken } left = &DotExpr{left, LiteralExpr{p.tt, p.data}, exprPrec, false} p.next() if precLeft < OpMember { precLeft = OpCall } else { precLeft = OpMember } case OpenBracketToken: // OpMember < prec does never happen if precLeft < OpCall { p.fail("expression") return nil } p.next() exprPrec := OpMember if precLeft < OpMember { exprPrec = OpCall } prevIn := p.in p.in = true left = &IndexExpr{left, p.parseExpression(OpExpr), exprPrec, false} p.in = prevIn if !p.consume("index expression", CloseBracketToken) { return nil } if precLeft < OpMember { precLeft = OpCall } else { precLeft = OpMember } case OpenParenToken: if OpCall < prec { return left } else if precLeft < OpCall { p.fail("expression") return nil } prevIn := p.in p.in = true left = &CallExpr{left, p.parseArguments(), false} precLeft = OpCall p.in = prevIn case TemplateToken, TemplateStartToken: // OpMember < prec does never happen if precLeft < OpCall { p.fail("expression") return nil } prevIn := p.in p.in = true template := p.parseTemplateLiteral(precLeft) template.Tag = left left = &template if precLeft < OpMember { precLeft = OpCall } else { precLeft = OpMember } p.in = prevIn case OptChainToken: if OpCall < prec { return left } p.next() if p.tt == OpenParenToken { left = &CallExpr{left, p.parseArguments(), true} } else if p.tt == OpenBracketToken { p.next() left = &IndexExpr{left, p.parseExpression(OpExpr), OpCall, true} if !p.consume("optional chaining expression", CloseBracketToken) { return nil } } else if p.tt == TemplateToken || p.tt == TemplateStartToken { template := p.parseTemplateLiteral(precLeft) template.Prec = OpCall template.Tag = left template.Optional = true left = &template } else if IsIdentifierName(p.tt) { left = &DotExpr{left, LiteralExpr{IdentifierToken, p.data}, OpCall, true} p.next() } else if p.tt == PrivateIdentifierToken { left = &DotExpr{left, LiteralExpr{p.tt, p.data}, OpCall, true} p.next() } else { p.fail("optional chaining expression", IdentifierToken, OpenParenToken, OpenBracketToken, TemplateToken) return nil } precLeft = OpCall case IncrToken: if p.prevLT || OpUpdate < prec { return left } else if precLeft < OpLHS { p.fail("expression") return nil } p.next() left = &UnaryExpr{PostIncrToken, left} precLeft = OpUpdate case DecrToken: if p.prevLT || OpUpdate < prec { return left } else if precLeft < OpLHS { p.fail("expression") return nil } p.next() left = &UnaryExpr{PostDecrToken, left} precLeft = OpUpdate case ExpToken: if OpExp < prec { return left } else if precLeft < OpUpdate { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpExp)} precLeft = OpExp case MulToken, DivToken, ModToken: if OpMul < prec { return left } else if precLeft < OpMul { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpExp)} precLeft = OpMul case AddToken, SubToken: if OpAdd < prec { return left } else if precLeft < OpAdd { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpMul)} precLeft = OpAdd case LtLtToken, GtGtToken, GtGtGtToken: if OpShift < prec { return left } else if precLeft < OpShift { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpAdd)} precLeft = OpShift case BitAndToken: if OpBitAnd < prec { return left } else if precLeft < OpBitAnd { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpEquals)} precLeft = OpBitAnd case BitXorToken: if OpBitXor < prec { return left } else if precLeft < OpBitXor { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpBitAnd)} precLeft = OpBitXor case BitOrToken: if OpBitOr < prec { return left } else if precLeft < OpBitOr { p.fail("expression") return nil } p.next() left = &BinaryExpr{tt, left, p.parseExpression(OpBitXor)} precLeft = OpBitOr case QuestionToken: if OpAssign < prec { return left } else if precLeft < OpCoalesce { p.fail("expression") return nil } p.next() prevIn := p.in p.in = true ifExpr := p.parseExpression(OpAssign) p.in = prevIn if !p.consume("conditional expression", ColonToken) { return nil } elseExpr := p.parseExpression(OpAssign) left = &CondExpr{left, ifExpr, elseExpr} precLeft = OpAssign case CommaToken: if OpExpr < prec { return left } p.next() if commaExpr, ok := left.(*CommaExpr); ok { commaExpr.List = append(commaExpr.List, p.parseExpression(OpAssign)) i-- // adjust expression nesting limit } else { left = &CommaExpr{[]IExpr{left, p.parseExpression(OpAssign)}} } precLeft = OpExpr case ArrowToken: // handle identifier => ..., where identifier could also be yield or await if OpAssign < prec { return left } else if precLeft < OpPrimary || p.prevLT { p.fail("expression") return nil } v, ok := left.(*Var) if !ok { p.fail("expression") return nil } left = p.parseIdentifierArrowFunc(v) precLeft = OpAssign default: return left } } } func (p *Parser) parseAssignExprOrParam() IExpr { // this could be a BindingElement or an AssignmentExpression. Here we handle BindingIdentifier with a possible Initializer, BindingPattern will be handled by parseArrayLiteral or parseObjectLiteral if p.assumeArrowFunc && p.isIdentifierReference(p.tt) { tt := p.tt data := p.data p.next() if p.tt == EqToken || p.tt == CommaToken || p.tt == CloseParenToken || p.tt == CloseBraceToken || p.tt == CloseBracketToken { var ok bool var left IExpr left, ok = p.scope.Declare(ArgumentDecl, data) if ok { p.assumeArrowFunc = false left = p.parseExpressionSuffix(left, OpAssign, OpPrimary) p.assumeArrowFunc = true return left } } p.assumeArrowFunc = false if tt == AsyncToken { return p.parseAsyncExpression(OpAssign, data) } return p.parseIdentifierExpression(OpAssign, data) } else if p.tt != OpenBracketToken && p.tt != OpenBraceToken { p.assumeArrowFunc = false } return p.parseExpression(OpAssign) } func (p *Parser) parseParenthesizedExpression(prec OpPrec, async []byte) IExpr { // parse ArrowFunc, AsyncArrowFunc, AsyncCallExpr, ParenthesizedExpr var left IExpr precLeft := OpPrimary // expect to be at ( p.next() isAsync := async != nil // prevLT is false before open parenthesis arrowFunc := &ArrowFunc{} parent := p.enterScope(&arrowFunc.Body.Scope, true) prevAssumeArrowFunc, prevIn := p.assumeArrowFunc, p.in p.assumeArrowFunc, p.in = true, true // parse an Arguments expression but assume we might be parsing an (async) arrow function or ParenthesisedExpression. If this is really an arrow function, parsing as an Arguments expression cannot fail as AssignmentExpression, ArrayLiteral, and ObjectLiteral are supersets of SingleNameBinding, ArrayBindingPattern, and ObjectBindingPattern respectively. Any identifier that would be a BindingIdentifier in case of an arrow function, will be added as such to the scope. If finally this is not an arrow function, we will demote those variables as undeclared and merge them with the parent scope. rests := 0 var args Args for p.tt != CloseParenToken && p.tt != ErrorToken { if 0 < len(args.List) && args.List[len(args.List)-1].Rest { // only last parameter can have ellipsis p.assumeArrowFunc = false if !isAsync { p.fail("arrow function", CloseParenToken) } } rest := p.tt == EllipsisToken if rest { p.next() rests++ } args.List = append(args.List, Arg{p.parseAssignExprOrParam(), rest}) if p.tt != CommaToken { break } p.next() } if p.tt != CloseParenToken { p.fail("expression") return nil } p.next() isArrowFunc := !p.prevLT && p.tt == ArrowToken && p.assumeArrowFunc hasLastRest := 0 < rests && p.assumeArrowFunc p.assumeArrowFunc, p.in = prevAssumeArrowFunc, prevIn if isArrowFunc { prevAwait, prevYield := p.await, p.yield p.await, p.yield = isAsync, false // arrow function arrowFunc.Async = isAsync arrowFunc.Params = Params{List: make([]BindingElement, 0, len(args.List)-rests)} for _, arg := range args.List { if arg.Rest { arrowFunc.Params.Rest = p.exprToBinding(arg.Value) } else { arrowFunc.Params.List = append(arrowFunc.Params.List, p.exprToBindingElement(arg.Value)) // can not fail when assumArrowFunc is set } } arrowFunc.Body.List = p.parseArrowFuncBody() p.await, p.yield = prevAwait, prevYield p.exitScope(parent) left = arrowFunc precLeft = OpAssign } else if !isAsync && (len(args.List) == 0 || hasLastRest) { p.fail("arrow function", ArrowToken) return nil } else if isAsync && OpCall < prec || !isAsync && 0 < rests { p.fail("expression") return nil } else { // for any nested FuncExpr/ArrowFunc scope, Parent will point to the temporary scope created in case this was an arrow function instead of a parenthesized expression. This is not a problem as Parent is only used for defining new variables, and we already parsed all the nested scopes so that Parent (not Func) are not relevant anymore. Anyways, the Parent will just point to an empty scope, whose Parent/Func will point to valid scopes. This should not be a big deal. // Here we move all declared ArgumentDecls (in case of an arrow function) to its parent scope as undeclared variables (identifiers used in a parenthesized expression). p.exitScope(parent) arrowFunc.Body.Scope.UndeclareScope() if isAsync { // call expression left = p.scope.Use(async) left = &CallExpr{left, args, false} precLeft = OpCall } else { // parenthesized expression if 1 < len(args.List) { commaExpr := &CommaExpr{} for _, arg := range args.List { commaExpr.List = append(commaExpr.List, arg.Value) } left = &GroupExpr{commaExpr} } else { left = &GroupExpr{args.List[0].Value} } } } return p.parseExpressionSuffix(left, prec, precLeft) } // exprToBindingElement and exprToBinding convert a CoverParenthesizedExpressionAndArrowParameterList into FormalParameters. // Any unbound variables of the parameters (Initializer, ComputedPropertyName) are kept in the parent scope func (p *Parser) exprToBindingElement(expr IExpr) (bindingElement BindingElement) { if assign, ok := expr.(*BinaryExpr); ok && assign.Op == EqToken { bindingElement.Binding = p.exprToBinding(assign.X) bindingElement.Default = assign.Y } else { bindingElement.Binding = p.exprToBinding(expr) } return } func (p *Parser) exprToBinding(expr IExpr) (binding IBinding) { if expr == nil { // no-op } else if v, ok := expr.(*Var); ok { binding = v } else if array, ok := expr.(*ArrayExpr); ok { bindingArray := BindingArray{} for _, item := range array.List { if item.Spread { // can only BindingIdentifier or BindingPattern bindingArray.Rest = p.exprToBinding(item.Value) break } var bindingElement BindingElement bindingElement = p.exprToBindingElement(item.Value) bindingArray.List = append(bindingArray.List, bindingElement) } binding = &bindingArray } else if object, ok := expr.(*ObjectExpr); ok { bindingObject := BindingObject{} for _, item := range object.List { if item.Spread { // can only be BindingIdentifier bindingObject.Rest = item.Value.(*Var) break } bindingElement := p.exprToBindingElement(item.Value) if v, ok := item.Value.(*Var); item.Name == nil || (ok && item.Name.IsIdent(v.Data)) { // IdentifierReference : Initializer bindingElement.Default = item.Init } bindingObject.List = append(bindingObject.List, BindingObjectItem{Key: item.Name, Value: bindingElement}) } binding = &bindingObject } else { p.failMessage("invalid parameters in arrow function") } return } func (p *Parser) isIdentifierReference(tt TokenType) bool { return IsIdentifier(tt) || !p.yield && tt == YieldToken || !p.await && tt == AwaitToken }