1079 lines
26 KiB
Go
1079 lines
26 KiB
Go
package parser
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/dop251/goja/ast"
|
|
"github.com/dop251/goja/file"
|
|
"github.com/dop251/goja/token"
|
|
"github.com/go-sourcemap/sourcemap"
|
|
)
|
|
|
|
func (self *_parser) parseBlockStatement() *ast.BlockStatement {
|
|
node := &ast.BlockStatement{}
|
|
node.LeftBrace = self.expect(token.LEFT_BRACE)
|
|
node.List = self.parseStatementList()
|
|
node.RightBrace = self.expect(token.RIGHT_BRACE)
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseEmptyStatement() ast.Statement {
|
|
idx := self.expect(token.SEMICOLON)
|
|
return &ast.EmptyStatement{Semicolon: idx}
|
|
}
|
|
|
|
func (self *_parser) parseStatementList() (list []ast.Statement) {
|
|
for self.token != token.RIGHT_BRACE && self.token != token.EOF {
|
|
self.scope.allowLet = true
|
|
list = append(list, self.parseStatement())
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (self *_parser) parseStatement() ast.Statement {
|
|
|
|
if self.token == token.EOF {
|
|
self.errorUnexpectedToken(self.token)
|
|
return &ast.BadStatement{From: self.idx, To: self.idx + 1}
|
|
}
|
|
|
|
switch self.token {
|
|
case token.SEMICOLON:
|
|
return self.parseEmptyStatement()
|
|
case token.LEFT_BRACE:
|
|
return self.parseBlockStatement()
|
|
case token.IF:
|
|
return self.parseIfStatement()
|
|
case token.DO:
|
|
return self.parseDoWhileStatement()
|
|
case token.WHILE:
|
|
return self.parseWhileStatement()
|
|
case token.FOR:
|
|
return self.parseForOrForInStatement()
|
|
case token.BREAK:
|
|
return self.parseBreakStatement()
|
|
case token.CONTINUE:
|
|
return self.parseContinueStatement()
|
|
case token.DEBUGGER:
|
|
return self.parseDebuggerStatement()
|
|
case token.WITH:
|
|
return self.parseWithStatement()
|
|
case token.VAR:
|
|
return self.parseVariableStatement()
|
|
case token.LET:
|
|
tok := self.peek()
|
|
if tok == token.LEFT_BRACKET || self.scope.allowLet && (token.IsId(tok) || tok == token.LEFT_BRACE) {
|
|
return self.parseLexicalDeclaration(self.token)
|
|
}
|
|
self.insertSemicolon = true
|
|
case token.CONST:
|
|
return self.parseLexicalDeclaration(self.token)
|
|
case token.ASYNC:
|
|
if f := self.parseMaybeAsyncFunction(true); f != nil {
|
|
return &ast.FunctionDeclaration{
|
|
Function: f,
|
|
}
|
|
}
|
|
case token.FUNCTION:
|
|
return &ast.FunctionDeclaration{
|
|
Function: self.parseFunction(true, false, self.idx),
|
|
}
|
|
case token.CLASS:
|
|
return &ast.ClassDeclaration{
|
|
Class: self.parseClass(true),
|
|
}
|
|
case token.SWITCH:
|
|
return self.parseSwitchStatement()
|
|
case token.RETURN:
|
|
return self.parseReturnStatement()
|
|
case token.THROW:
|
|
return self.parseThrowStatement()
|
|
case token.TRY:
|
|
return self.parseTryStatement()
|
|
}
|
|
|
|
expression := self.parseExpression()
|
|
|
|
if identifier, isIdentifier := expression.(*ast.Identifier); isIdentifier && self.token == token.COLON {
|
|
// LabelledStatement
|
|
colon := self.idx
|
|
self.next() // :
|
|
label := identifier.Name
|
|
for _, value := range self.scope.labels {
|
|
if label == value {
|
|
self.error(identifier.Idx0(), "Label '%s' already exists", label)
|
|
}
|
|
}
|
|
self.scope.labels = append(self.scope.labels, label) // Push the label
|
|
self.scope.allowLet = false
|
|
statement := self.parseStatement()
|
|
self.scope.labels = self.scope.labels[:len(self.scope.labels)-1] // Pop the label
|
|
return &ast.LabelledStatement{
|
|
Label: identifier,
|
|
Colon: colon,
|
|
Statement: statement,
|
|
}
|
|
}
|
|
|
|
self.optionalSemicolon()
|
|
|
|
return &ast.ExpressionStatement{
|
|
Expression: expression,
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseTryStatement() ast.Statement {
|
|
|
|
node := &ast.TryStatement{
|
|
Try: self.expect(token.TRY),
|
|
Body: self.parseBlockStatement(),
|
|
}
|
|
|
|
if self.token == token.CATCH {
|
|
catch := self.idx
|
|
self.next()
|
|
var parameter ast.BindingTarget
|
|
if self.token == token.LEFT_PARENTHESIS {
|
|
self.next()
|
|
parameter = self.parseBindingTarget()
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
}
|
|
node.Catch = &ast.CatchStatement{
|
|
Catch: catch,
|
|
Parameter: parameter,
|
|
Body: self.parseBlockStatement(),
|
|
}
|
|
}
|
|
|
|
if self.token == token.FINALLY {
|
|
self.next()
|
|
node.Finally = self.parseBlockStatement()
|
|
}
|
|
|
|
if node.Catch == nil && node.Finally == nil {
|
|
self.error(node.Try, "Missing catch or finally after try")
|
|
return &ast.BadStatement{From: node.Try, To: node.Body.Idx1()}
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseFunctionParameterList() *ast.ParameterList {
|
|
opening := self.expect(token.LEFT_PARENTHESIS)
|
|
var list []*ast.Binding
|
|
var rest ast.Expression
|
|
if !self.scope.inFuncParams {
|
|
self.scope.inFuncParams = true
|
|
defer func() {
|
|
self.scope.inFuncParams = false
|
|
}()
|
|
}
|
|
for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF {
|
|
if self.token == token.ELLIPSIS {
|
|
self.next()
|
|
rest = self.reinterpretAsDestructBindingTarget(self.parseAssignmentExpression())
|
|
break
|
|
}
|
|
self.parseVariableDeclaration(&list)
|
|
if self.token != token.RIGHT_PARENTHESIS {
|
|
self.expect(token.COMMA)
|
|
}
|
|
}
|
|
closing := self.expect(token.RIGHT_PARENTHESIS)
|
|
|
|
return &ast.ParameterList{
|
|
Opening: opening,
|
|
List: list,
|
|
Rest: rest,
|
|
Closing: closing,
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseMaybeAsyncFunction(declaration bool) *ast.FunctionLiteral {
|
|
if self.peek() == token.FUNCTION {
|
|
idx := self.idx
|
|
self.next()
|
|
return self.parseFunction(declaration, true, idx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *_parser) parseFunction(declaration, async bool, start file.Idx) *ast.FunctionLiteral {
|
|
|
|
node := &ast.FunctionLiteral{
|
|
Function: start,
|
|
Async: async,
|
|
}
|
|
self.expect(token.FUNCTION)
|
|
|
|
if self.token == token.MULTIPLY {
|
|
node.Generator = true
|
|
self.next()
|
|
}
|
|
|
|
if !declaration {
|
|
if async != self.scope.allowAwait {
|
|
self.scope.allowAwait = async
|
|
defer func() {
|
|
self.scope.allowAwait = !async
|
|
}()
|
|
}
|
|
if node.Generator != self.scope.allowYield {
|
|
self.scope.allowYield = node.Generator
|
|
defer func() {
|
|
self.scope.allowYield = !node.Generator
|
|
}()
|
|
}
|
|
}
|
|
|
|
self.tokenToBindingId()
|
|
var name *ast.Identifier
|
|
if self.token == token.IDENTIFIER {
|
|
name = self.parseIdentifier()
|
|
} else if declaration {
|
|
// Use expect error handling
|
|
self.expect(token.IDENTIFIER)
|
|
}
|
|
node.Name = name
|
|
|
|
if declaration {
|
|
if async != self.scope.allowAwait {
|
|
self.scope.allowAwait = async
|
|
defer func() {
|
|
self.scope.allowAwait = !async
|
|
}()
|
|
}
|
|
if node.Generator != self.scope.allowYield {
|
|
self.scope.allowYield = node.Generator
|
|
defer func() {
|
|
self.scope.allowYield = !node.Generator
|
|
}()
|
|
}
|
|
}
|
|
|
|
node.ParameterList = self.parseFunctionParameterList()
|
|
node.Body, node.DeclarationList = self.parseFunctionBlock(async, async, self.scope.allowYield)
|
|
node.Source = self.slice(node.Idx0(), node.Idx1())
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseFunctionBlock(async, allowAwait, allowYield bool) (body *ast.BlockStatement, declarationList []*ast.VariableDeclaration) {
|
|
self.openScope()
|
|
self.scope.inFunction = true
|
|
self.scope.inAsync = async
|
|
self.scope.allowAwait = allowAwait
|
|
self.scope.allowYield = allowYield
|
|
defer self.closeScope()
|
|
body = self.parseBlockStatement()
|
|
declarationList = self.scope.declarationList
|
|
return
|
|
}
|
|
|
|
func (self *_parser) parseArrowFunctionBody(async bool) (ast.ConciseBody, []*ast.VariableDeclaration) {
|
|
if self.token == token.LEFT_BRACE {
|
|
return self.parseFunctionBlock(async, async, false)
|
|
}
|
|
if async != self.scope.inAsync || async != self.scope.allowAwait {
|
|
inAsync := self.scope.inAsync
|
|
allowAwait := self.scope.allowAwait
|
|
self.scope.inAsync = async
|
|
self.scope.allowAwait = async
|
|
allowYield := self.scope.allowYield
|
|
self.scope.allowYield = false
|
|
defer func() {
|
|
self.scope.inAsync = inAsync
|
|
self.scope.allowAwait = allowAwait
|
|
self.scope.allowYield = allowYield
|
|
}()
|
|
}
|
|
|
|
return &ast.ExpressionBody{
|
|
Expression: self.parseAssignmentExpression(),
|
|
}, nil
|
|
}
|
|
|
|
func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral {
|
|
if !self.scope.allowLet && self.token == token.CLASS {
|
|
self.errorUnexpectedToken(token.CLASS)
|
|
}
|
|
|
|
node := &ast.ClassLiteral{
|
|
Class: self.expect(token.CLASS),
|
|
}
|
|
|
|
self.tokenToBindingId()
|
|
var name *ast.Identifier
|
|
if self.token == token.IDENTIFIER {
|
|
name = self.parseIdentifier()
|
|
} else if declaration {
|
|
// Use expect error handling
|
|
self.expect(token.IDENTIFIER)
|
|
}
|
|
|
|
node.Name = name
|
|
|
|
if self.token != token.LEFT_BRACE {
|
|
self.expect(token.EXTENDS)
|
|
node.SuperClass = self.parseLeftHandSideExpressionAllowCall()
|
|
}
|
|
|
|
self.expect(token.LEFT_BRACE)
|
|
|
|
for self.token != token.RIGHT_BRACE && self.token != token.EOF {
|
|
if self.token == token.SEMICOLON {
|
|
self.next()
|
|
continue
|
|
}
|
|
start := self.idx
|
|
static := false
|
|
if self.token == token.STATIC {
|
|
switch self.peek() {
|
|
case token.ASSIGN, token.SEMICOLON, token.RIGHT_BRACE, token.LEFT_PARENTHESIS:
|
|
// treat as identifier
|
|
default:
|
|
self.next()
|
|
if self.token == token.LEFT_BRACE {
|
|
b := &ast.ClassStaticBlock{
|
|
Static: start,
|
|
}
|
|
b.Block, b.DeclarationList = self.parseFunctionBlock(false, true, false)
|
|
b.Source = self.slice(b.Block.LeftBrace, b.Block.Idx1())
|
|
node.Body = append(node.Body, b)
|
|
continue
|
|
}
|
|
static = true
|
|
}
|
|
}
|
|
|
|
var kind ast.PropertyKind
|
|
var async bool
|
|
methodBodyStart := self.idx
|
|
if self.literal == "get" || self.literal == "set" {
|
|
if tok := self.peek(); tok != token.SEMICOLON && tok != token.LEFT_PARENTHESIS {
|
|
if self.literal == "get" {
|
|
kind = ast.PropertyKindGet
|
|
} else {
|
|
kind = ast.PropertyKindSet
|
|
}
|
|
self.next()
|
|
}
|
|
} else if self.token == token.ASYNC {
|
|
if tok := self.peek(); tok != token.SEMICOLON && tok != token.LEFT_PARENTHESIS {
|
|
async = true
|
|
kind = ast.PropertyKindMethod
|
|
self.next()
|
|
}
|
|
}
|
|
generator := false
|
|
if self.token == token.MULTIPLY && (kind == "" || kind == ast.PropertyKindMethod) {
|
|
generator = true
|
|
kind = ast.PropertyKindMethod
|
|
self.next()
|
|
}
|
|
|
|
_, keyName, value, tkn := self.parseObjectPropertyKey()
|
|
if value == nil {
|
|
continue
|
|
}
|
|
computed := tkn == token.ILLEGAL
|
|
_, private := value.(*ast.PrivateIdentifier)
|
|
|
|
if static && !private && keyName == "prototype" {
|
|
self.error(value.Idx0(), "Classes may not have a static property named 'prototype'")
|
|
}
|
|
|
|
if kind == "" && self.token == token.LEFT_PARENTHESIS {
|
|
kind = ast.PropertyKindMethod
|
|
}
|
|
|
|
if kind != "" {
|
|
// method
|
|
if keyName == "constructor" && !computed {
|
|
if !static {
|
|
if kind != ast.PropertyKindMethod {
|
|
self.error(value.Idx0(), "Class constructor may not be an accessor")
|
|
} else if async {
|
|
self.error(value.Idx0(), "Class constructor may not be an async method")
|
|
} else if generator {
|
|
self.error(value.Idx0(), "Class constructor may not be a generator")
|
|
}
|
|
} else if private {
|
|
self.error(value.Idx0(), "Class constructor may not be a private method")
|
|
}
|
|
}
|
|
md := &ast.MethodDefinition{
|
|
Idx: start,
|
|
Key: value,
|
|
Kind: kind,
|
|
Body: self.parseMethodDefinition(methodBodyStart, kind, generator, async),
|
|
Static: static,
|
|
Computed: computed,
|
|
}
|
|
node.Body = append(node.Body, md)
|
|
} else {
|
|
// field
|
|
isCtor := !computed && keyName == "constructor"
|
|
if !isCtor {
|
|
if name, ok := value.(*ast.PrivateIdentifier); ok {
|
|
isCtor = name.Name == "constructor"
|
|
}
|
|
}
|
|
if isCtor {
|
|
self.error(value.Idx0(), "Classes may not have a field named 'constructor'")
|
|
}
|
|
var initializer ast.Expression
|
|
if self.token == token.ASSIGN {
|
|
self.next()
|
|
initializer = self.parseExpression()
|
|
}
|
|
|
|
if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE {
|
|
self.errorUnexpectedToken(self.token)
|
|
break
|
|
}
|
|
node.Body = append(node.Body, &ast.FieldDefinition{
|
|
Idx: start,
|
|
Key: value,
|
|
Initializer: initializer,
|
|
Static: static,
|
|
Computed: computed,
|
|
})
|
|
}
|
|
}
|
|
|
|
node.RightBrace = self.expect(token.RIGHT_BRACE)
|
|
node.Source = self.slice(node.Class, node.RightBrace+1)
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseDebuggerStatement() ast.Statement {
|
|
idx := self.expect(token.DEBUGGER)
|
|
|
|
node := &ast.DebuggerStatement{
|
|
Debugger: idx,
|
|
}
|
|
|
|
self.semicolon()
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseReturnStatement() ast.Statement {
|
|
idx := self.expect(token.RETURN)
|
|
|
|
if !self.scope.inFunction {
|
|
self.error(idx, "Illegal return statement")
|
|
self.nextStatement()
|
|
return &ast.BadStatement{From: idx, To: self.idx}
|
|
}
|
|
|
|
node := &ast.ReturnStatement{
|
|
Return: idx,
|
|
}
|
|
|
|
if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE && self.token != token.EOF {
|
|
node.Argument = self.parseExpression()
|
|
}
|
|
|
|
self.semicolon()
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseThrowStatement() ast.Statement {
|
|
idx := self.expect(token.THROW)
|
|
|
|
if self.implicitSemicolon {
|
|
if self.chr == -1 { // Hackish
|
|
self.error(idx, "Unexpected end of input")
|
|
} else {
|
|
self.error(idx, "Illegal newline after throw")
|
|
}
|
|
self.nextStatement()
|
|
return &ast.BadStatement{From: idx, To: self.idx}
|
|
}
|
|
|
|
node := &ast.ThrowStatement{
|
|
Throw: idx,
|
|
Argument: self.parseExpression(),
|
|
}
|
|
|
|
self.semicolon()
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseSwitchStatement() ast.Statement {
|
|
idx := self.expect(token.SWITCH)
|
|
self.expect(token.LEFT_PARENTHESIS)
|
|
node := &ast.SwitchStatement{
|
|
Switch: idx,
|
|
Discriminant: self.parseExpression(),
|
|
Default: -1,
|
|
}
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
|
|
self.expect(token.LEFT_BRACE)
|
|
|
|
inSwitch := self.scope.inSwitch
|
|
self.scope.inSwitch = true
|
|
defer func() {
|
|
self.scope.inSwitch = inSwitch
|
|
}()
|
|
|
|
for index := 0; self.token != token.EOF; index++ {
|
|
if self.token == token.RIGHT_BRACE {
|
|
node.RightBrace = self.idx
|
|
self.next()
|
|
break
|
|
}
|
|
|
|
clause := self.parseCaseStatement()
|
|
if clause.Test == nil {
|
|
if node.Default != -1 {
|
|
self.error(clause.Case, "Already saw a default in switch")
|
|
}
|
|
node.Default = index
|
|
}
|
|
node.Body = append(node.Body, clause)
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseWithStatement() ast.Statement {
|
|
node := &ast.WithStatement{}
|
|
node.With = self.expect(token.WITH)
|
|
self.expect(token.LEFT_PARENTHESIS)
|
|
node.Object = self.parseExpression()
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
self.scope.allowLet = false
|
|
node.Body = self.parseStatement()
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseCaseStatement() *ast.CaseStatement {
|
|
|
|
node := &ast.CaseStatement{
|
|
Case: self.idx,
|
|
}
|
|
if self.token == token.DEFAULT {
|
|
self.next()
|
|
} else {
|
|
self.expect(token.CASE)
|
|
node.Test = self.parseExpression()
|
|
}
|
|
self.expect(token.COLON)
|
|
|
|
for {
|
|
if self.token == token.EOF ||
|
|
self.token == token.RIGHT_BRACE ||
|
|
self.token == token.CASE ||
|
|
self.token == token.DEFAULT {
|
|
break
|
|
}
|
|
self.scope.allowLet = true
|
|
node.Consequent = append(node.Consequent, self.parseStatement())
|
|
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseIterationStatement() ast.Statement {
|
|
inIteration := self.scope.inIteration
|
|
self.scope.inIteration = true
|
|
defer func() {
|
|
self.scope.inIteration = inIteration
|
|
}()
|
|
self.scope.allowLet = false
|
|
return self.parseStatement()
|
|
}
|
|
|
|
func (self *_parser) parseForIn(idx file.Idx, into ast.ForInto) *ast.ForInStatement {
|
|
|
|
// Already have consumed "<into> in"
|
|
|
|
source := self.parseExpression()
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
|
|
return &ast.ForInStatement{
|
|
For: idx,
|
|
Into: into,
|
|
Source: source,
|
|
Body: self.parseIterationStatement(),
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseForOf(idx file.Idx, into ast.ForInto) *ast.ForOfStatement {
|
|
|
|
// Already have consumed "<into> of"
|
|
|
|
source := self.parseAssignmentExpression()
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
|
|
return &ast.ForOfStatement{
|
|
For: idx,
|
|
Into: into,
|
|
Source: source,
|
|
Body: self.parseIterationStatement(),
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseFor(idx file.Idx, initializer ast.ForLoopInitializer) *ast.ForStatement {
|
|
|
|
// Already have consumed "<initializer> ;"
|
|
|
|
var test, update ast.Expression
|
|
|
|
if self.token != token.SEMICOLON {
|
|
test = self.parseExpression()
|
|
}
|
|
self.expect(token.SEMICOLON)
|
|
|
|
if self.token != token.RIGHT_PARENTHESIS {
|
|
update = self.parseExpression()
|
|
}
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
|
|
return &ast.ForStatement{
|
|
For: idx,
|
|
Initializer: initializer,
|
|
Test: test,
|
|
Update: update,
|
|
Body: self.parseIterationStatement(),
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseForOrForInStatement() ast.Statement {
|
|
idx := self.expect(token.FOR)
|
|
self.expect(token.LEFT_PARENTHESIS)
|
|
|
|
var initializer ast.ForLoopInitializer
|
|
|
|
forIn := false
|
|
forOf := false
|
|
var into ast.ForInto
|
|
if self.token != token.SEMICOLON {
|
|
|
|
allowIn := self.scope.allowIn
|
|
self.scope.allowIn = false
|
|
tok := self.token
|
|
if tok == token.LET {
|
|
switch self.peek() {
|
|
case token.IDENTIFIER, token.LEFT_BRACKET, token.LEFT_BRACE:
|
|
default:
|
|
tok = token.IDENTIFIER
|
|
}
|
|
}
|
|
if tok == token.VAR || tok == token.LET || tok == token.CONST {
|
|
idx := self.idx
|
|
self.next()
|
|
var list []*ast.Binding
|
|
if tok == token.VAR {
|
|
list = self.parseVarDeclarationList(idx)
|
|
} else {
|
|
list = self.parseVariableDeclarationList()
|
|
}
|
|
if len(list) == 1 {
|
|
if self.token == token.IN {
|
|
self.next() // in
|
|
forIn = true
|
|
} else if self.token == token.IDENTIFIER && self.literal == "of" {
|
|
self.next()
|
|
forOf = true
|
|
}
|
|
}
|
|
if forIn || forOf {
|
|
if list[0].Initializer != nil {
|
|
self.error(list[0].Initializer.Idx0(), "for-in loop variable declaration may not have an initializer")
|
|
}
|
|
if tok == token.VAR {
|
|
into = &ast.ForIntoVar{
|
|
Binding: list[0],
|
|
}
|
|
} else {
|
|
into = &ast.ForDeclaration{
|
|
Idx: idx,
|
|
IsConst: tok == token.CONST,
|
|
Target: list[0].Target,
|
|
}
|
|
}
|
|
} else {
|
|
self.ensurePatternInit(list)
|
|
if tok == token.VAR {
|
|
initializer = &ast.ForLoopInitializerVarDeclList{
|
|
List: list,
|
|
}
|
|
} else {
|
|
initializer = &ast.ForLoopInitializerLexicalDecl{
|
|
LexicalDeclaration: ast.LexicalDeclaration{
|
|
Idx: idx,
|
|
Token: tok,
|
|
List: list,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
expr := self.parseExpression()
|
|
if self.token == token.IN {
|
|
self.next()
|
|
forIn = true
|
|
} else if self.token == token.IDENTIFIER && self.literal == "of" {
|
|
self.next()
|
|
forOf = true
|
|
}
|
|
if forIn || forOf {
|
|
switch e := expr.(type) {
|
|
case *ast.Identifier, *ast.DotExpression, *ast.PrivateDotExpression, *ast.BracketExpression, *ast.Binding:
|
|
// These are all acceptable
|
|
case *ast.ObjectLiteral:
|
|
expr = self.reinterpretAsObjectAssignmentPattern(e)
|
|
case *ast.ArrayLiteral:
|
|
expr = self.reinterpretAsArrayAssignmentPattern(e)
|
|
default:
|
|
self.error(idx, "Invalid left-hand side in for-in or for-of")
|
|
self.nextStatement()
|
|
return &ast.BadStatement{From: idx, To: self.idx}
|
|
}
|
|
into = &ast.ForIntoExpression{
|
|
Expression: expr,
|
|
}
|
|
} else {
|
|
initializer = &ast.ForLoopInitializerExpression{
|
|
Expression: expr,
|
|
}
|
|
}
|
|
}
|
|
self.scope.allowIn = allowIn
|
|
}
|
|
|
|
if forIn {
|
|
return self.parseForIn(idx, into)
|
|
}
|
|
if forOf {
|
|
return self.parseForOf(idx, into)
|
|
}
|
|
|
|
self.expect(token.SEMICOLON)
|
|
return self.parseFor(idx, initializer)
|
|
}
|
|
|
|
func (self *_parser) ensurePatternInit(list []*ast.Binding) {
|
|
for _, item := range list {
|
|
if _, ok := item.Target.(ast.Pattern); ok {
|
|
if item.Initializer == nil {
|
|
self.error(item.Idx1(), "Missing initializer in destructuring declaration")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseVariableStatement() *ast.VariableStatement {
|
|
|
|
idx := self.expect(token.VAR)
|
|
|
|
list := self.parseVarDeclarationList(idx)
|
|
self.ensurePatternInit(list)
|
|
self.semicolon()
|
|
|
|
return &ast.VariableStatement{
|
|
Var: idx,
|
|
List: list,
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseLexicalDeclaration(tok token.Token) *ast.LexicalDeclaration {
|
|
idx := self.expect(tok)
|
|
if !self.scope.allowLet {
|
|
self.error(idx, "Lexical declaration cannot appear in a single-statement context")
|
|
}
|
|
|
|
list := self.parseVariableDeclarationList()
|
|
self.ensurePatternInit(list)
|
|
self.semicolon()
|
|
|
|
return &ast.LexicalDeclaration{
|
|
Idx: idx,
|
|
Token: tok,
|
|
List: list,
|
|
}
|
|
}
|
|
|
|
func (self *_parser) parseDoWhileStatement() ast.Statement {
|
|
inIteration := self.scope.inIteration
|
|
self.scope.inIteration = true
|
|
defer func() {
|
|
self.scope.inIteration = inIteration
|
|
}()
|
|
|
|
node := &ast.DoWhileStatement{}
|
|
node.Do = self.expect(token.DO)
|
|
if self.token == token.LEFT_BRACE {
|
|
node.Body = self.parseBlockStatement()
|
|
} else {
|
|
self.scope.allowLet = false
|
|
node.Body = self.parseStatement()
|
|
}
|
|
|
|
self.expect(token.WHILE)
|
|
self.expect(token.LEFT_PARENTHESIS)
|
|
node.Test = self.parseExpression()
|
|
node.RightParenthesis = self.expect(token.RIGHT_PARENTHESIS)
|
|
if self.token == token.SEMICOLON {
|
|
self.next()
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseWhileStatement() ast.Statement {
|
|
idx := self.expect(token.WHILE)
|
|
self.expect(token.LEFT_PARENTHESIS)
|
|
node := &ast.WhileStatement{
|
|
While: idx,
|
|
Test: self.parseExpression(),
|
|
}
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
node.Body = self.parseIterationStatement()
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseIfStatement() ast.Statement {
|
|
self.expect(token.IF)
|
|
self.expect(token.LEFT_PARENTHESIS)
|
|
node := &ast.IfStatement{
|
|
Test: self.parseExpression(),
|
|
}
|
|
self.expect(token.RIGHT_PARENTHESIS)
|
|
|
|
if self.token == token.LEFT_BRACE {
|
|
node.Consequent = self.parseBlockStatement()
|
|
} else {
|
|
self.scope.allowLet = false
|
|
node.Consequent = self.parseStatement()
|
|
}
|
|
|
|
if self.token == token.ELSE {
|
|
self.next()
|
|
self.scope.allowLet = false
|
|
node.Alternate = self.parseStatement()
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func (self *_parser) parseSourceElements() (body []ast.Statement) {
|
|
for self.token != token.EOF {
|
|
self.scope.allowLet = true
|
|
body = append(body, self.parseStatement())
|
|
}
|
|
|
|
return body
|
|
}
|
|
|
|
func (self *_parser) parseProgram() *ast.Program {
|
|
prg := &ast.Program{
|
|
Body: self.parseSourceElements(),
|
|
DeclarationList: self.scope.declarationList,
|
|
File: self.file,
|
|
}
|
|
self.file.SetSourceMap(self.parseSourceMap())
|
|
return prg
|
|
}
|
|
|
|
func extractSourceMapLine(str string) string {
|
|
for {
|
|
p := strings.LastIndexByte(str, '\n')
|
|
line := str[p+1:]
|
|
if line != "" && line != "})" {
|
|
if strings.HasPrefix(line, "//# sourceMappingURL=") {
|
|
return line
|
|
}
|
|
break
|
|
}
|
|
if p >= 0 {
|
|
str = str[:p]
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (self *_parser) parseSourceMap() *sourcemap.Consumer {
|
|
if self.opts.disableSourceMaps {
|
|
return nil
|
|
}
|
|
if smLine := extractSourceMapLine(self.str); smLine != "" {
|
|
urlIndex := strings.Index(smLine, "=")
|
|
urlStr := smLine[urlIndex+1:]
|
|
|
|
var data []byte
|
|
var err error
|
|
if strings.HasPrefix(urlStr, "data:application/json") {
|
|
b64Index := strings.Index(urlStr, ",")
|
|
b64 := urlStr[b64Index+1:]
|
|
data, err = base64.StdEncoding.DecodeString(b64)
|
|
} else {
|
|
if sourceURL := file.ResolveSourcemapURL(self.file.Name(), urlStr); sourceURL != nil {
|
|
if self.opts.sourceMapLoader != nil {
|
|
data, err = self.opts.sourceMapLoader(sourceURL.String())
|
|
} else {
|
|
if sourceURL.Scheme == "" || sourceURL.Scheme == "file" {
|
|
data, err = os.ReadFile(sourceURL.Path)
|
|
} else {
|
|
err = fmt.Errorf("unsupported source map URL scheme: %s", sourceURL.Scheme)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
self.error(file.Idx(0), "Could not load source map: %v", err)
|
|
return nil
|
|
}
|
|
if data == nil {
|
|
return nil
|
|
}
|
|
|
|
if sm, err := sourcemap.Parse(self.file.Name(), data); err == nil {
|
|
return sm
|
|
} else {
|
|
self.error(file.Idx(0), "Could not parse source map: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *_parser) parseBreakStatement() ast.Statement {
|
|
idx := self.expect(token.BREAK)
|
|
semicolon := self.implicitSemicolon
|
|
if self.token == token.SEMICOLON {
|
|
semicolon = true
|
|
self.next()
|
|
}
|
|
|
|
if semicolon || self.token == token.RIGHT_BRACE {
|
|
self.implicitSemicolon = false
|
|
if !self.scope.inIteration && !self.scope.inSwitch {
|
|
goto illegal
|
|
}
|
|
return &ast.BranchStatement{
|
|
Idx: idx,
|
|
Token: token.BREAK,
|
|
}
|
|
}
|
|
|
|
self.tokenToBindingId()
|
|
if self.token == token.IDENTIFIER {
|
|
identifier := self.parseIdentifier()
|
|
if !self.scope.hasLabel(identifier.Name) {
|
|
self.error(idx, "Undefined label '%s'", identifier.Name)
|
|
return &ast.BadStatement{From: idx, To: identifier.Idx1()}
|
|
}
|
|
self.semicolon()
|
|
return &ast.BranchStatement{
|
|
Idx: idx,
|
|
Token: token.BREAK,
|
|
Label: identifier,
|
|
}
|
|
}
|
|
|
|
self.expect(token.IDENTIFIER)
|
|
|
|
illegal:
|
|
self.error(idx, "Illegal break statement")
|
|
self.nextStatement()
|
|
return &ast.BadStatement{From: idx, To: self.idx}
|
|
}
|
|
|
|
func (self *_parser) parseContinueStatement() ast.Statement {
|
|
idx := self.expect(token.CONTINUE)
|
|
semicolon := self.implicitSemicolon
|
|
if self.token == token.SEMICOLON {
|
|
semicolon = true
|
|
self.next()
|
|
}
|
|
|
|
if semicolon || self.token == token.RIGHT_BRACE {
|
|
self.implicitSemicolon = false
|
|
if !self.scope.inIteration {
|
|
goto illegal
|
|
}
|
|
return &ast.BranchStatement{
|
|
Idx: idx,
|
|
Token: token.CONTINUE,
|
|
}
|
|
}
|
|
|
|
self.tokenToBindingId()
|
|
if self.token == token.IDENTIFIER {
|
|
identifier := self.parseIdentifier()
|
|
if !self.scope.hasLabel(identifier.Name) {
|
|
self.error(idx, "Undefined label '%s'", identifier.Name)
|
|
return &ast.BadStatement{From: idx, To: identifier.Idx1()}
|
|
}
|
|
if !self.scope.inIteration {
|
|
goto illegal
|
|
}
|
|
self.semicolon()
|
|
return &ast.BranchStatement{
|
|
Idx: idx,
|
|
Token: token.CONTINUE,
|
|
Label: identifier,
|
|
}
|
|
}
|
|
|
|
self.expect(token.IDENTIFIER)
|
|
|
|
illegal:
|
|
self.error(idx, "Illegal continue statement")
|
|
self.nextStatement()
|
|
return &ast.BadStatement{From: idx, To: self.idx}
|
|
}
|
|
|
|
// Find the next statement after an error (recover)
|
|
func (self *_parser) nextStatement() {
|
|
for {
|
|
switch self.token {
|
|
case token.BREAK, token.CONTINUE,
|
|
token.FOR, token.IF, token.RETURN, token.SWITCH,
|
|
token.VAR, token.DO, token.TRY, token.WITH,
|
|
token.WHILE, token.THROW, token.CATCH, token.FINALLY:
|
|
// Return only if parser made some progress since last
|
|
// sync or if it has not reached 10 next calls without
|
|
// progress. Otherwise consume at least one token to
|
|
// avoid an endless parser loop
|
|
if self.idx == self.recover.idx && self.recover.count < 10 {
|
|
self.recover.count++
|
|
return
|
|
}
|
|
if self.idx > self.recover.idx {
|
|
self.recover.idx = self.idx
|
|
self.recover.count = 0
|
|
return
|
|
}
|
|
// Reaching here indicates a parser bug, likely an
|
|
// incorrect token list in this function, but it only
|
|
// leads to skipping of possibly correct code if a
|
|
// previous error is present, and thus is preferred
|
|
// over a non-terminating parse.
|
|
case token.EOF:
|
|
return
|
|
}
|
|
self.next()
|
|
}
|
|
}
|