package js import ( "errors" "fmt" "io" "github.com/tdewolff/parse/v2" "github.com/tdewolff/parse/v2/buffer" ) var evalBytes = []byte("eval") // Parser is the state for the parser. type Parser struct { l *Lexer err error data []byte tt TokenType prevLT bool inFor bool async, generator bool assumeArrowFunc bool allowDirectivePrologue bool stmtLevel int exprLevel int scope *Scope } // Parse returns a JS AST tree of. func Parse(r *parse.Input) (*AST, error) { p := &Parser{ l: NewLexer(r), tt: WhitespaceToken, // trick so that next() works } ast := &AST{} p.tt, p.data = p.l.Next() for p.tt == CommentToken || p.tt == CommentLineTerminatorToken { ast.Comments = append(ast.Comments, p.data) p.tt, p.data = p.l.Next() if p.tt == WhitespaceToken || p.tt == LineTerminatorToken { p.tt, p.data = p.l.Next() } } if p.tt == WhitespaceToken || p.tt == LineTerminatorToken { p.next() } // prevLT may be wrong but that is not a problem ast.BlockStmt = p.parseModule() 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 { 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 } // TODO: refactor //type ScopeState struct { // scope *Scope // async bool // generator bool // assumeArrowFunc bool //} 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, Func: nil, Declared: VarArray{}, Undeclared: VarArray{}, NumVarDecls: 0, NumArguments: 0, HasWith: false, } 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: 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 } switch tt := p.tt; tt { case OpenBraceToken: blockStmt := p.parseBlockStmt("block statement") stmt = &blockStmt case ConstToken, VarToken: if !allowDeclaration && tt == ConstToken { p.fail("statement") return } p.next() varDecl := p.parseVarDecl(tt) 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) { varDecl := p.parseVarDecl(tt) stmt = &varDecl 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 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} 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 } stmt = &WhileStmt{cond, p.parseStmt(false)} case ForToken: p.next() await := p.async && 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.inFor = true if p.tt == VarToken || p.tt == LetToken || p.tt == ConstToken { tt := p.tt p.next() varDecl := p.parseVarDecl(tt) 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 p.tt != SemicolonToken { init = p.parseExpression(OpExpr) } p.inFor = false 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.MarkForInit() if p.tt == OpenBraceToken { body.List = p.parseStmtList("") } else if p.tt != SemicolonToken { body.List = []IStmt{p.parseStmt(false)} } stmt = &ForStmt{init, cond, post, body} } else if 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.MarkForInit() if p.tt == OpenBraceToken { body.List = p.parseStmtList("") } else if p.tt != SemicolonToken { body.List = []IStmt{p.parseStmt(false)} } stmt = &ForInStmt{init, value, body} } else if p.tt == OfToken { p.next() value := p.parseExpression(OpAssign) if !p.consume("for statement", CloseParenToken) { return } p.scope.MarkForInit() if p.tt == OpenBraceToken { body.List = p.parseStmtList("") } else if p.tt != SemicolonToken { body.List = []IStmt{p.parseStmt(false)} } stmt = &ForOfStmt{await, init, value, body} } else { p.fail("for statement", InToken, OfToken, 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 } clauses := []CaseClause{} 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)) } clauses = append(clauses, CaseClause{clause, list, stmts}) } stmt = &SwitchStmt{init, clauses} case FunctionToken: if !allowDeclaration { p.fail("statement") return } funcDecl := p.parseFuncDecl() stmt = &funcDecl case AsyncToken: // async function if !allowDeclaration { p.fail("statement") return } async := p.data p.next() if p.tt == FunctionToken && !p.prevLT { funcDecl := p.parseAsyncFuncDecl() stmt = &funcDecl } 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 } classDecl := p.parseClassDecl() stmt = &classDecl case ThrowToken: p.next() var value IExpr if !p.prevLT { value = p.parseExpression(OpExpr) } stmt = &ThrowStmt{value} 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() blockStmt := p.parseBlockStmt("try-finally statement") finally = &blockStmt } stmt = &TryStmt{body, binding, catch, finally} case DebuggerToken: p.next() stmt = &DebuggerStmt{} case SemicolonToken, ErrorToken: stmt = &EmptyStmt{} default: if p.isIdentifierReference(p.tt) { // labelled statement or expression label := p.data p.next() if p.tt == ColonToken { p.next() stmt = &LabelledStmt{label, p.parseStmt(true)} // allows illegal async function, generator function, let, const, or class declarations } 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 } if p.allowDirectivePrologue { if lit, ok := stmt.(*ExprStmt).Value.(*LiteralExpr); ok && lit.TokenType == StringToken { stmt = &DirectivePrologueStmt{lit.Data} } else { p.allowDirectivePrologue = false } } } } if 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) { parent := p.enterScope(&blockStmt.Scope, false) blockStmt.List = p.parseStmtList(in) p.exitScope(parent) return } func (p *Parser) parseImportStmt() (importStmt ImportStmt) { // assume we're at import p.next() if p.tt == StringToken { importStmt.Module = p.data p.next() } else { if IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken { importStmt.Default = p.data p.next() if p.tt == CommaToken { p.next() } } if p.tt == MulToken { star := p.data p.next() if !p.consume("import statement", AsToken) { return } if !IsIdentifier(p.tt) && p.tt != YieldToken && p.tt != AwaitToken { p.fail("import statement", IdentifierToken) return } importStmt.List = []Alias{Alias{star, p.data}} p.next() } else if p.tt == OpenBraceToken { p.next() for IsIdentifierName(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.tt != AwaitToken { p.fail("import statement", IdentifierToken) return } name = binding binding = p.data p.next() } 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 } } if importStmt.Default == nil && len(importStmt.List) == 0 { 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() 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.fail("export statement", IdentifierToken) 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) { var name, binding []byte = nil, p.data p.next() if p.tt == AsToken { p.next() if !IsIdentifierName(p.tt) { p.fail("export statement", IdentifierToken) 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() varDecl := p.parseVarDecl(tt) exportStmt.Decl = &varDecl } else if p.tt == FunctionToken { funcDecl := p.parseFuncDecl() exportStmt.Decl = &funcDecl } else if p.tt == AsyncToken { // async function p.next() if p.tt != FunctionToken || p.prevLT { p.fail("export statement", FunctionToken) return } funcDecl := p.parseAsyncFuncDecl() exportStmt.Decl = &funcDecl } else if p.tt == ClassToken { classDecl := p.parseClassDecl() exportStmt.Decl = &classDecl } else if p.tt == DefaultToken { exportStmt.Default = true p.next() if p.tt == FunctionToken { funcDecl := p.parseFuncExpr() exportStmt.Decl = &funcDecl } else if p.tt == AsyncToken { // async function or async arrow function async := p.data p.next() if p.tt == FunctionToken && !p.prevLT { funcDecl := p.parseAsyncFuncExpr() exportStmt.Decl = &funcDecl } else { // expression exportStmt.Decl = p.parseAsyncExpression(OpExpr, async) } } else if p.tt == ClassToken { classDecl := p.parseClassExpr() exportStmt.Decl = &classDecl } 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() } return } func (p *Parser) parseVarDecl(tt TokenType) (varDecl VarDecl) { // assume we're past var, let or const varDecl.TokenType = tt declType := LexicalDecl if tt == VarToken { declType = VariableDecl p.scope.Func.NumVarDecls++ } for { // binding element, var declaration in for-in or for-of can never have a default var bindingElement BindingElement parentInFor := p.inFor p.inFor = false bindingElement.Binding = p.parseBinding(declType) p.inFor = parentInFor if p.tt == EqToken { p.next() bindingElement.Default = p.parseExpression(OpAssign) } else if _, ok := bindingElement.Binding.(*Var); !ok && (!p.inFor || 0 < len(varDecl.List)) { p.fail("var statement", EqToken) return } varDecl.List = append(varDecl.List, bindingElement) if p.tt == CommaToken { p.next() } else { break } } return } func (p *Parser) parseFuncParams(in string) (params Params) { 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.MarkArguments() return } func (p *Parser) parseFuncDecl() (funcDecl FuncDecl) { return p.parseAnyFunc(false, false) } func (p *Parser) parseAsyncFuncDecl() (funcDecl FuncDecl) { return p.parseAnyFunc(true, false) } func (p *Parser) parseFuncExpr() (funcDecl FuncDecl) { return p.parseAnyFunc(false, true) } func (p *Parser) parseAsyncFuncExpr() (funcDecl FuncDecl) { return p.parseAnyFunc(true, true) } func (p *Parser) parseAnyFunc(async, inExpr bool) (funcDecl FuncDecl) { // assume we're at function p.next() funcDecl.Async = async funcDecl.Generator = p.tt == MulToken if funcDecl.Generator { p.next() } var ok bool var name []byte if inExpr && (IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken) || !inExpr && p.isIdentifierReference(p.tt) { name = p.data if !inExpr { 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 !inExpr { 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) parentAsync, parentGenerator := p.async, p.generator p.async, p.generator = funcDecl.Async, funcDecl.Generator if inExpr && name != nil { funcDecl.Name, _ = p.scope.Declare(ExprDecl, name) // cannot fail } funcDecl.Params = p.parseFuncParams("function declaration") p.allowDirectivePrologue = true funcDecl.Body.List = p.parseStmtList("function declaration") p.async, p.generator = parentAsync, parentGenerator 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(inExpr bool) (classDecl ClassDecl) { // assume we're at class p.next() if IsIdentifier(p.tt) || p.tt == YieldToken || p.tt == AwaitToken { if !inExpr { 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 !inExpr { 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.Methods = append(classDecl.Methods, p.parseMethod()) } return } func (p *Parser) parseMethod() (method MethodDecl) { var data []byte if p.tt == StaticToken { method.Static = true data = p.data p.next() } 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() } if data != nil && p.tt == OpenParenToken { 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 { method.Name = p.parsePropertyName("method definition") } parent := p.enterScope(&method.Body.Scope, true) parentAsync, parentGenerator := p.async, p.generator p.async, p.generator = method.Async, method.Generator method.Params = p.parseFuncParams("method definition") p.allowDirectivePrologue = true method.Body.List = p.parseStmtList("method definition") p.async, p.generator = parentAsync, parentGenerator p.exitScope(parent) return } 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) { // binding element 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) { // binding identifier or binding pattern if IsIdentifier(p.tt) || !p.generator && p.tt == YieldToken || !p.async && p.tt == AwaitToken { 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.parseAssignmentExpression(), 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.parseAssignmentExpression() 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) parentAsync, parentGenerator := p.async, p.generator p.async, p.generator = method.Async, method.Generator method.Params = p.parseFuncParams("method definition") method.Body.List = p.parseStmtList("method definition") p.async, p.generator = parentAsync, parentGenerator 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.parseAssignmentExpression() } 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 { property.Value, _ = p.scope.Declare(ArgumentDecl, name) // cannot fail } else { property.Value = p.scope.Use(name) } if p.tt == EqToken { p.next() parentAssumeArrowFunc := p.assumeArrowFunc p.assumeArrowFunc = false property.Init = p.parseExpression(OpAssign) p.assumeArrowFunc = parentAssumeArrowFunc } } } 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 { rest := p.tt == EllipsisToken if rest { p.next() } if p.tt == CloseParenToken || p.tt == ErrorToken { break } args.List = append(args.List, Arg{ Value: p.parseExpression(OpAssign), Rest: rest, }) if p.tt == CommaToken { p.next() } } p.consume("arguments", CloseParenToken) return } func (p *Parser) parseAsyncArrowFunc() (arrowFunc ArrowFunc) { // expect we're at Identifier or Yield or ( parent := p.enterScope(&arrowFunc.Body.Scope, true) parentAsync, parentGenerator := p.async, p.generator p.async, p.generator = true, false if IsIdentifier(p.tt) || !p.generator && p.tt == YieldToken { ref, _ := p.scope.Declare(ArgumentDecl, p.data) p.next() arrowFunc.Params.List = []BindingElement{{Binding: ref}} } else { arrowFunc.Params = p.parseFuncParams("arrow function") } arrowFunc.Async = true arrowFunc.Body.List = p.parseArrowFuncBody() p.async, p.generator = parentAsync, parentGenerator p.exitScope(parent) return } func (p *Parser) parseIdentifierArrowFunc(v *Var) (arrowFunc ArrowFunc) { // expect we're at => parent := p.enterScope(&arrowFunc.Body.Scope, true) parentAsync, parentGenerator := p.async, p.generator p.async, p.generator = false, false if 1 < v.Uses { v.Uses-- v, _ = p.scope.Declare(ArgumentDecl, 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.async, p.generator = parentAsync, parentGenerator 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.MarkArguments() if p.tt == OpenBraceToken { parentInFor := p.inFor p.inFor = false p.allowDirectivePrologue = true list = p.parseStmtList("arrow function") p.inFor = parentInFor } 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 { // assume we're at a token after async var left IExpr precLeft := OpPrimary if !p.prevLT && p.tt == FunctionToken { // primary expression funcDecl := p.parseAsyncFuncExpr() left = &funcDecl } else if !p.prevLT && prec <= OpAssign && (p.tt == OpenParenToken || IsIdentifier(p.tt) || !p.generator && p.tt == YieldToken || p.tt == AwaitToken) { // async arrow function expression if p.tt == AwaitToken { p.fail("arrow function") return nil } arrowFunc := p.parseAsyncArrowFunc() left = &arrowFunc precLeft = OpAssign } else { left = p.scope.Use(async) } left = p.parseExpressionSuffix(left, prec, precLeft) return left } // 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: parentInFor := p.inFor p.inFor = false array := p.parseArrayLiteral() left = &array p.inFor = parentInFor case OpenBraceToken: parentInFor := p.inFor p.inFor = false object := p.parseObjectLiteral() left = &object p.inFor = parentInFor case OpenParenToken: // parenthesized expression or arrow parameter list if OpAssign < prec { // must be a parenthesized expression p.next() parentInFor := p.inFor p.inFor = false left = &GroupExpr{p.parseExpression(OpExpr)} p.inFor = parentInFor if !p.consume("expression", CloseParenToken) { return nil } break } suffix := p.parseParenthesizedExpressionOrArrowFunc(prec) 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.async && prec <= OpUnary { p.next() left = &UnaryExpr{tt, p.parseExpression(OpUnary)} precLeft = OpUnary } else if p.async { 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.generator && 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.generator { p.fail("expression") return nil } else { left = p.scope.Use(p.data) p.next() } case AsyncToken: async := p.data p.next() left = p.parseAsyncExpression(prec, async) case ClassToken: parentInFor := p.inFor p.inFor = false classDecl := p.parseClassExpr() left = &classDecl p.inFor = parentInFor case FunctionToken: parentInFor := p.inFor p.inFor = false funcDecl := p.parseFuncExpr() left = &funcDecl p.inFor = parentInFor case TemplateToken, TemplateStartToken: parentInFor := p.inFor p.inFor = false template := p.parseTemplateLiteral(precLeft) left = &template p.inFor = parentInFor 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 { switch tt := p.tt; tt { case EqToken, MulEqToken, DivEqToken, ModEqToken, ExpEqToken, AddEqToken, SubEqToken, LtLtEqToken, GtGtEqToken, GtGtGtEqToken, BitAndEqToken, BitXorEqToken, BitOrEqToken: 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.inFor && 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.fail("dot expression", IdentifierToken) return nil } exprPrec := OpMember if precLeft < OpMember { exprPrec = OpCall } left = &DotExpr{left, LiteralExpr{IdentifierToken, p.data}, exprPrec} 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 } parentInFor := p.inFor p.inFor = false left = &IndexExpr{left, p.parseExpression(OpExpr), exprPrec} p.inFor = parentInFor 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 } parentInFor := p.inFor p.inFor = false left = &CallExpr{left, p.parseArguments()} precLeft = OpCall p.inFor = parentInFor case TemplateToken, TemplateStartToken: // OpMember < prec does never happen if precLeft < OpCall { p.fail("expression") return nil } parentInFor := p.inFor p.inFor = false template := p.parseTemplateLiteral(precLeft) template.Tag = left left = &template if precLeft < OpMember { precLeft = OpCall } else { precLeft = OpMember } p.inFor = parentInFor case OptChainToken: if OpCall < prec { return left } p.next() if p.tt == OpenParenToken { left = &OptChainExpr{left, &CallExpr{nil, p.parseArguments()}} } else if p.tt == OpenBracketToken { p.next() left = &OptChainExpr{left, &IndexExpr{nil, p.parseExpression(OpExpr), OpCall}} if !p.consume("optional chaining expression", CloseBracketToken) { return nil } } else if p.tt == TemplateToken || p.tt == TemplateStartToken { template := p.parseTemplateLiteral(precLeft) left = &OptChainExpr{left, &template} } else if IsIdentifierName(p.tt) { left = &OptChainExpr{left, &LiteralExpr{IdentifierToken, p.data}} 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() ifExpr := p.parseExpression(OpAssign) 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() left = &BinaryExpr{tt, 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.fail("expression") return nil } v, ok := left.(*Var) if !ok { p.fail("expression") return nil } arrowFunc := p.parseIdentifierArrowFunc(v) left = &arrowFunc precLeft = OpAssign default: return left } } } func (p *Parser) parseAssignmentExpression() 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 left IExpr left, _ = p.scope.Declare(ArgumentDecl, data) // cannot fail 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) parseParenthesizedExpressionOrArrowFunc(prec OpPrec) IExpr { var left IExpr precLeft := OpPrimary // expect to be at ( p.next() arrowFunc := ArrowFunc{} parent := p.enterScope(&arrowFunc.Body.Scope, true) parentAssumeArrowFunc, parentInFor := p.assumeArrowFunc, p.inFor p.assumeArrowFunc, p.inFor = true, false // parse a parenthesized expression but assume we might be parsing an arrow function. If this is really an arrow function, parsing as a parenthesized 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. If finally this is not an arrow function, we will demote those variables an undeclared and merge them with the parent scope. var list []IExpr var rest IExpr for p.tt != CloseParenToken && p.tt != ErrorToken { if p.tt == EllipsisToken && p.assumeArrowFunc { p.next() if p.isIdentifierReference(p.tt) { rest, _ = p.scope.Declare(ArgumentDecl, p.data) // cannot fail p.next() } else if p.tt == OpenBracketToken { array := p.parseArrayLiteral() rest = &array } else if p.tt == OpenBraceToken { object := p.parseObjectLiteral() rest = &object } else { p.fail("arrow function") return nil } break } list = append(list, p.parseAssignmentExpression()) if p.tt != CommaToken { break } p.next() } if p.tt != CloseParenToken { p.fail("expression") return nil } p.next() isArrowFunc := p.tt == ArrowToken && p.assumeArrowFunc p.assumeArrowFunc, p.inFor = parentAssumeArrowFunc, parentInFor if isArrowFunc { parentAsync, parentGenerator := p.async, p.generator p.async, p.generator = false, false // arrow function arrowFunc.Params = Params{List: make([]BindingElement, len(list))} for i, item := range list { arrowFunc.Params.List[i] = p.exprToBindingElement(item) // can not fail when assumArrowFunc is set } arrowFunc.Params.Rest = p.exprToBinding(rest) arrowFunc.Body.List = p.parseArrowFuncBody() p.async, p.generator = parentAsync, parentGenerator p.exitScope(parent) left = &arrowFunc precLeft = OpAssign } else if len(list) == 0 || rest != nil { p.fail("arrow function", ArrowToken) return nil } else { p.exitScope(parent) // 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). arrowFunc.Body.Scope.UndeclareScope() // parenthesized expression left = list[0] for _, item := range list[1:] { left = &BinaryExpr{CommaToken, left, item} } left = &GroupExpr{left} } return p.parseExpressionSuffix(left, prec, precLeft) } // exprToBinding converts a CoverParenthesizedExpressionAndArrowParameterList into FormalParameters // Any unbound variables of the parameters (Initializer, ComputedPropertyName) are kept in the parent scope func (p *Parser) exprToBinding(expr IExpr) (binding IBinding) { 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 } var bindingElement BindingElement bindingElement.Binding = p.exprToBinding(item.Value) if item.Init != nil { bindingElement.Default = item.Init } bindingObject.List = append(bindingObject.List, BindingObjectItem{Key: item.Name, Value: bindingElement}) } binding = &bindingObject } return } 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) isIdentifierReference(tt TokenType) bool { return IsIdentifier(tt) || tt == YieldToken && !p.generator || tt == AwaitToken && !p.async }