4
vendor/github.com/robertkrimen/otto/parser/Makefile
generated
vendored
Normal file
4
vendor/github.com/robertkrimen/otto/parser/Makefile
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.PHONY: test
|
||||
|
||||
test:
|
||||
go test
|
190
vendor/github.com/robertkrimen/otto/parser/README.markdown
generated
vendored
Normal file
190
vendor/github.com/robertkrimen/otto/parser/README.markdown
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
# parser
|
||||
--
|
||||
import "github.com/robertkrimen/otto/parser"
|
||||
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
|
||||
filename := "" // A filename is optional
|
||||
src := `
|
||||
// Sample xyzzy example
|
||||
(function(){
|
||||
if (3.14159 > 0) {
|
||||
console.log("Hello, World.");
|
||||
return;
|
||||
}
|
||||
|
||||
var xyzzy = NaN;
|
||||
console.log("Nothing happens.");
|
||||
return xyzzy;
|
||||
})();
|
||||
`
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, filename, src, 0)
|
||||
|
||||
|
||||
### Warning
|
||||
|
||||
The parser and AST interfaces are still works-in-progress (particularly where
|
||||
node types are concerned) and may change in the future.
|
||||
|
||||
## Usage
|
||||
|
||||
#### func ParseFile
|
||||
|
||||
```go
|
||||
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
|
||||
```
|
||||
ParseFile parses the source code of a single JavaScript/ECMAScript source file
|
||||
and returns the corresponding ast.Program node.
|
||||
|
||||
If fileSet == nil, ParseFile parses source without a FileSet. If fileSet != nil,
|
||||
ParseFile first adds filename and src to fileSet.
|
||||
|
||||
The filename argument is optional and is used for labelling errors, etc.
|
||||
|
||||
src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST
|
||||
always be in UTF-8.
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
|
||||
|
||||
#### func ParseFunction
|
||||
|
||||
```go
|
||||
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error)
|
||||
```
|
||||
ParseFunction parses a given parameter list and body as a function and returns
|
||||
the corresponding ast.FunctionLiteral node.
|
||||
|
||||
The parameter list, if any, should be a comma-separated list of identifiers.
|
||||
|
||||
#### func ReadSource
|
||||
|
||||
```go
|
||||
func ReadSource(filename string, src interface{}) ([]byte, error)
|
||||
```
|
||||
|
||||
#### func TransformRegExp
|
||||
|
||||
```go
|
||||
func TransformRegExp(pattern string) (string, error)
|
||||
```
|
||||
TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
|
||||
|
||||
re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
|
||||
backreference (\1, \2, ...) will cause an error.
|
||||
|
||||
re2 (Go) has a different definition for \s: [\t\n\f\r ]. The JavaScript
|
||||
definition, on the other hand, also includes \v, Unicode "Separator, Space",
|
||||
etc.
|
||||
|
||||
If the pattern is invalid (not valid even in JavaScript), then this function
|
||||
returns the empty string and an error.
|
||||
|
||||
If the pattern is valid, but incompatible (contains a lookahead or
|
||||
backreference), then this function returns the transformation (a non-empty
|
||||
string) AND an error.
|
||||
|
||||
#### type Error
|
||||
|
||||
```go
|
||||
type Error struct {
|
||||
Position file.Position
|
||||
Message string
|
||||
}
|
||||
```
|
||||
|
||||
An Error represents a parsing error. It includes the position where the error
|
||||
occurred and a message/description.
|
||||
|
||||
#### func (Error) Error
|
||||
|
||||
```go
|
||||
func (self Error) Error() string
|
||||
```
|
||||
|
||||
#### type ErrorList
|
||||
|
||||
```go
|
||||
type ErrorList []*Error
|
||||
```
|
||||
|
||||
ErrorList is a list of *Errors.
|
||||
|
||||
#### func (*ErrorList) Add
|
||||
|
||||
```go
|
||||
func (self *ErrorList) Add(position file.Position, msg string)
|
||||
```
|
||||
Add adds an Error with given position and message to an ErrorList.
|
||||
|
||||
#### func (ErrorList) Err
|
||||
|
||||
```go
|
||||
func (self ErrorList) Err() error
|
||||
```
|
||||
Err returns an error equivalent to this ErrorList. If the list is empty, Err
|
||||
returns nil.
|
||||
|
||||
#### func (ErrorList) Error
|
||||
|
||||
```go
|
||||
func (self ErrorList) Error() string
|
||||
```
|
||||
Error implements the Error interface.
|
||||
|
||||
#### func (ErrorList) Len
|
||||
|
||||
```go
|
||||
func (self ErrorList) Len() int
|
||||
```
|
||||
|
||||
#### func (ErrorList) Less
|
||||
|
||||
```go
|
||||
func (self ErrorList) Less(i, j int) bool
|
||||
```
|
||||
|
||||
#### func (*ErrorList) Reset
|
||||
|
||||
```go
|
||||
func (self *ErrorList) Reset()
|
||||
```
|
||||
Reset resets an ErrorList to no errors.
|
||||
|
||||
#### func (ErrorList) Sort
|
||||
|
||||
```go
|
||||
func (self ErrorList) Sort()
|
||||
```
|
||||
|
||||
#### func (ErrorList) Swap
|
||||
|
||||
```go
|
||||
func (self ErrorList) Swap(i, j int)
|
||||
```
|
||||
|
||||
#### type Mode
|
||||
|
||||
```go
|
||||
type Mode uint
|
||||
```
|
||||
|
||||
A Mode value is a set of flags (or 0). They control optional parser
|
||||
functionality.
|
||||
|
||||
```go
|
||||
const (
|
||||
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
|
||||
)
|
||||
```
|
||||
|
||||
--
|
||||
**godocdown** http://github.com/robertkrimen/godocdown
|
9
vendor/github.com/robertkrimen/otto/parser/dbg.go
generated
vendored
Normal file
9
vendor/github.com/robertkrimen/otto/parser/dbg.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file was AUTOMATICALLY GENERATED by dbg-import (smuggol) for github.com/robertkrimen/dbg
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
Dbg "github.com/robertkrimen/otto/dbg"
|
||||
)
|
||||
|
||||
var dbg, dbgf = Dbg.New()
|
175
vendor/github.com/robertkrimen/otto/parser/error.go
generated
vendored
Normal file
175
vendor/github.com/robertkrimen/otto/parser/error.go
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/robertkrimen/otto/file"
|
||||
"github.com/robertkrimen/otto/token"
|
||||
)
|
||||
|
||||
const (
|
||||
err_UnexpectedToken = "Unexpected token %v"
|
||||
err_UnexpectedEndOfInput = "Unexpected end of input"
|
||||
err_UnexpectedEscape = "Unexpected escape"
|
||||
)
|
||||
|
||||
// UnexpectedNumber: 'Unexpected number',
|
||||
// UnexpectedString: 'Unexpected string',
|
||||
// UnexpectedIdentifier: 'Unexpected identifier',
|
||||
// UnexpectedReserved: 'Unexpected reserved word',
|
||||
// NewlineAfterThrow: 'Illegal newline after throw',
|
||||
// InvalidRegExp: 'Invalid regular expression',
|
||||
// UnterminatedRegExp: 'Invalid regular expression: missing /',
|
||||
// InvalidLHSInAssignment: 'Invalid left-hand side in assignment',
|
||||
// InvalidLHSInForIn: 'Invalid left-hand side in for-in',
|
||||
// MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
|
||||
// NoCatchOrFinally: 'Missing catch or finally after try',
|
||||
// UnknownLabel: 'Undefined label \'%0\'',
|
||||
// Redeclaration: '%0 \'%1\' has already been declared',
|
||||
// IllegalContinue: 'Illegal continue statement',
|
||||
// IllegalBreak: 'Illegal break statement',
|
||||
// IllegalReturn: 'Illegal return statement',
|
||||
// StrictModeWith: 'Strict mode code may not include a with statement',
|
||||
// StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode',
|
||||
// StrictVarName: 'Variable name may not be eval or arguments in strict mode',
|
||||
// StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode',
|
||||
// StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
|
||||
// StrictFunctionName: 'Function name may not be eval or arguments in strict mode',
|
||||
// StrictOctalLiteral: 'Octal literals are not allowed in strict mode.',
|
||||
// StrictDelete: 'Delete of an unqualified identifier in strict mode.',
|
||||
// StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode',
|
||||
// AccessorDataProperty: 'Object literal may not have data and accessor property with the same name',
|
||||
// AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name',
|
||||
// StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode',
|
||||
// StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
|
||||
// StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
|
||||
// StrictReservedWord: 'Use of future reserved word in strict mode'
|
||||
|
||||
// A SyntaxError is a description of an ECMAScript syntax error.
|
||||
|
||||
// An Error represents a parsing error. It includes the position where the error occurred and a message/description.
|
||||
type Error struct {
|
||||
Position file.Position
|
||||
Message string
|
||||
}
|
||||
|
||||
// FIXME Should this be "SyntaxError"?
|
||||
|
||||
func (self Error) Error() string {
|
||||
filename := self.Position.Filename
|
||||
if filename == "" {
|
||||
filename = "(anonymous)"
|
||||
}
|
||||
return fmt.Sprintf("%s: Line %d:%d %s",
|
||||
filename,
|
||||
self.Position.Line,
|
||||
self.Position.Column,
|
||||
self.Message,
|
||||
)
|
||||
}
|
||||
|
||||
func (self *_parser) error(place interface{}, msg string, msgValues ...interface{}) *Error {
|
||||
idx := file.Idx(0)
|
||||
switch place := place.(type) {
|
||||
case int:
|
||||
idx = self.idxOf(place)
|
||||
case file.Idx:
|
||||
if place == 0 {
|
||||
idx = self.idxOf(self.chrOffset)
|
||||
} else {
|
||||
idx = place
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("error(%T, ...)", place))
|
||||
}
|
||||
|
||||
position := self.position(idx)
|
||||
msg = fmt.Sprintf(msg, msgValues...)
|
||||
self.errors.Add(position, msg)
|
||||
return self.errors[len(self.errors)-1]
|
||||
}
|
||||
|
||||
func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error {
|
||||
if chr == -1 {
|
||||
return self.error(idx, err_UnexpectedEndOfInput)
|
||||
}
|
||||
return self.error(idx, err_UnexpectedToken, token.ILLEGAL)
|
||||
}
|
||||
|
||||
func (self *_parser) errorUnexpectedToken(tkn token.Token) error {
|
||||
switch tkn {
|
||||
case token.EOF:
|
||||
return self.error(file.Idx(0), err_UnexpectedEndOfInput)
|
||||
}
|
||||
value := tkn.String()
|
||||
switch tkn {
|
||||
case token.BOOLEAN, token.NULL:
|
||||
value = self.literal
|
||||
case token.IDENTIFIER:
|
||||
return self.error(self.idx, "Unexpected identifier")
|
||||
case token.KEYWORD:
|
||||
// TODO Might be a future reserved word
|
||||
return self.error(self.idx, "Unexpected reserved word")
|
||||
case token.NUMBER:
|
||||
return self.error(self.idx, "Unexpected number")
|
||||
case token.STRING:
|
||||
return self.error(self.idx, "Unexpected string")
|
||||
}
|
||||
return self.error(self.idx, err_UnexpectedToken, value)
|
||||
}
|
||||
|
||||
// ErrorList is a list of *Errors.
|
||||
//
|
||||
type ErrorList []*Error
|
||||
|
||||
// Add adds an Error with given position and message to an ErrorList.
|
||||
func (self *ErrorList) Add(position file.Position, msg string) {
|
||||
*self = append(*self, &Error{position, msg})
|
||||
}
|
||||
|
||||
// Reset resets an ErrorList to no errors.
|
||||
func (self *ErrorList) Reset() { *self = (*self)[0:0] }
|
||||
|
||||
func (self ErrorList) Len() int { return len(self) }
|
||||
func (self ErrorList) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
|
||||
func (self ErrorList) Less(i, j int) bool {
|
||||
x := &self[i].Position
|
||||
y := &self[j].Position
|
||||
if x.Filename < y.Filename {
|
||||
return true
|
||||
}
|
||||
if x.Filename == y.Filename {
|
||||
if x.Line < y.Line {
|
||||
return true
|
||||
}
|
||||
if x.Line == y.Line {
|
||||
return x.Column < y.Column
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self ErrorList) Sort() {
|
||||
sort.Sort(self)
|
||||
}
|
||||
|
||||
// Error implements the Error interface.
|
||||
func (self ErrorList) Error() string {
|
||||
switch len(self) {
|
||||
case 0:
|
||||
return "no errors"
|
||||
case 1:
|
||||
return self[0].Error()
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d more errors)", self[0].Error(), len(self)-1)
|
||||
}
|
||||
|
||||
// Err returns an error equivalent to this ErrorList.
|
||||
// If the list is empty, Err returns nil.
|
||||
func (self ErrorList) Err() error {
|
||||
if len(self) == 0 {
|
||||
return nil
|
||||
}
|
||||
return self
|
||||
}
|
1005
vendor/github.com/robertkrimen/otto/parser/expression.go
generated
vendored
Normal file
1005
vendor/github.com/robertkrimen/otto/parser/expression.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
866
vendor/github.com/robertkrimen/otto/parser/lexer.go
generated
vendored
Normal file
866
vendor/github.com/robertkrimen/otto/parser/lexer.go
generated
vendored
Normal file
@@ -0,0 +1,866 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
"github.com/robertkrimen/otto/file"
|
||||
"github.com/robertkrimen/otto/token"
|
||||
)
|
||||
|
||||
type _chr struct {
|
||||
value rune
|
||||
width int
|
||||
}
|
||||
|
||||
var matchIdentifier = regexp.MustCompile(`^[$_\p{L}][$_\p{L}\d}]*$`)
|
||||
|
||||
func isDecimalDigit(chr rune) bool {
|
||||
return '0' <= chr && chr <= '9'
|
||||
}
|
||||
|
||||
func digitValue(chr rune) int {
|
||||
switch {
|
||||
case '0' <= chr && chr <= '9':
|
||||
return int(chr - '0')
|
||||
case 'a' <= chr && chr <= 'f':
|
||||
return int(chr - 'a' + 10)
|
||||
case 'A' <= chr && chr <= 'F':
|
||||
return int(chr - 'A' + 10)
|
||||
}
|
||||
return 16 // Larger than any legal digit value
|
||||
}
|
||||
|
||||
func isDigit(chr rune, base int) bool {
|
||||
return digitValue(chr) < base
|
||||
}
|
||||
|
||||
func isIdentifierStart(chr rune) bool {
|
||||
return chr == '$' || chr == '_' || chr == '\\' ||
|
||||
'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
|
||||
chr >= utf8.RuneSelf && unicode.IsLetter(chr)
|
||||
}
|
||||
|
||||
func isIdentifierPart(chr rune) bool {
|
||||
return chr == '$' || chr == '_' || chr == '\\' ||
|
||||
'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' ||
|
||||
'0' <= chr && chr <= '9' ||
|
||||
chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
|
||||
}
|
||||
|
||||
func (self *_parser) scanIdentifier() (string, error) {
|
||||
offset := self.chrOffset
|
||||
parse := false
|
||||
for isIdentifierPart(self.chr) {
|
||||
if self.chr == '\\' {
|
||||
distance := self.chrOffset - offset
|
||||
self.read()
|
||||
if self.chr != 'u' {
|
||||
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
|
||||
}
|
||||
parse = true
|
||||
var value rune
|
||||
for j := 0; j < 4; j++ {
|
||||
self.read()
|
||||
decimal, ok := hex2decimal(byte(self.chr))
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
|
||||
}
|
||||
value = value<<4 | decimal
|
||||
}
|
||||
if value == '\\' {
|
||||
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
|
||||
} else if distance == 0 {
|
||||
if !isIdentifierStart(value) {
|
||||
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
|
||||
}
|
||||
} else if distance > 0 {
|
||||
if !isIdentifierPart(value) {
|
||||
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
literal := string(self.str[offset:self.chrOffset])
|
||||
if parse {
|
||||
return parseStringLiteral(literal)
|
||||
}
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
// 7.2
|
||||
func isLineWhiteSpace(chr rune) bool {
|
||||
switch chr {
|
||||
case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff':
|
||||
return true
|
||||
case '\u000a', '\u000d', '\u2028', '\u2029':
|
||||
return false
|
||||
case '\u0085':
|
||||
return false
|
||||
}
|
||||
return unicode.IsSpace(chr)
|
||||
}
|
||||
|
||||
// 7.3
|
||||
func isLineTerminator(chr rune) bool {
|
||||
switch chr {
|
||||
case '\u000a', '\u000d', '\u2028', '\u2029':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
|
||||
|
||||
self.implicitSemicolon = false
|
||||
|
||||
for {
|
||||
self.skipWhiteSpace()
|
||||
|
||||
idx = self.idxOf(self.chrOffset)
|
||||
insertSemicolon := false
|
||||
|
||||
switch chr := self.chr; {
|
||||
case isIdentifierStart(chr):
|
||||
var err error
|
||||
literal, err = self.scanIdentifier()
|
||||
if err != nil {
|
||||
tkn = token.ILLEGAL
|
||||
break
|
||||
}
|
||||
if len(literal) > 1 {
|
||||
// Keywords are longer than 1 character, avoid lookup otherwise
|
||||
var strict bool
|
||||
tkn, strict = token.IsKeyword(literal)
|
||||
|
||||
switch tkn {
|
||||
|
||||
case 0: // Not a keyword
|
||||
if literal == "true" || literal == "false" {
|
||||
self.insertSemicolon = true
|
||||
tkn = token.BOOLEAN
|
||||
return
|
||||
} else if literal == "null" {
|
||||
self.insertSemicolon = true
|
||||
tkn = token.NULL
|
||||
return
|
||||
}
|
||||
|
||||
case token.KEYWORD:
|
||||
tkn = token.KEYWORD
|
||||
if strict {
|
||||
// TODO If strict and in strict mode, then this is not a break
|
||||
break
|
||||
}
|
||||
return
|
||||
|
||||
case
|
||||
token.THIS,
|
||||
token.BREAK,
|
||||
token.THROW, // A newline after a throw is not allowed, but we need to detect it
|
||||
token.RETURN,
|
||||
token.CONTINUE,
|
||||
token.DEBUGGER:
|
||||
self.insertSemicolon = true
|
||||
return
|
||||
|
||||
default:
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
self.insertSemicolon = true
|
||||
tkn = token.IDENTIFIER
|
||||
return
|
||||
case '0' <= chr && chr <= '9':
|
||||
self.insertSemicolon = true
|
||||
tkn, literal = self.scanNumericLiteral(false)
|
||||
return
|
||||
default:
|
||||
self.read()
|
||||
switch chr {
|
||||
case -1:
|
||||
if self.insertSemicolon {
|
||||
self.insertSemicolon = false
|
||||
self.implicitSemicolon = true
|
||||
}
|
||||
tkn = token.EOF
|
||||
case '\r', '\n', '\u2028', '\u2029':
|
||||
self.insertSemicolon = false
|
||||
self.implicitSemicolon = true
|
||||
self.comments.AtLineBreak()
|
||||
continue
|
||||
case ':':
|
||||
tkn = token.COLON
|
||||
case '.':
|
||||
if digitValue(self.chr) < 10 {
|
||||
insertSemicolon = true
|
||||
tkn, literal = self.scanNumericLiteral(true)
|
||||
} else {
|
||||
tkn = token.PERIOD
|
||||
}
|
||||
case ',':
|
||||
tkn = token.COMMA
|
||||
case ';':
|
||||
tkn = token.SEMICOLON
|
||||
case '(':
|
||||
tkn = token.LEFT_PARENTHESIS
|
||||
case ')':
|
||||
tkn = token.RIGHT_PARENTHESIS
|
||||
insertSemicolon = true
|
||||
case '[':
|
||||
tkn = token.LEFT_BRACKET
|
||||
case ']':
|
||||
tkn = token.RIGHT_BRACKET
|
||||
insertSemicolon = true
|
||||
case '{':
|
||||
tkn = token.LEFT_BRACE
|
||||
case '}':
|
||||
tkn = token.RIGHT_BRACE
|
||||
insertSemicolon = true
|
||||
case '+':
|
||||
tkn = self.switch3(token.PLUS, token.ADD_ASSIGN, '+', token.INCREMENT)
|
||||
if tkn == token.INCREMENT {
|
||||
insertSemicolon = true
|
||||
}
|
||||
case '-':
|
||||
tkn = self.switch3(token.MINUS, token.SUBTRACT_ASSIGN, '-', token.DECREMENT)
|
||||
if tkn == token.DECREMENT {
|
||||
insertSemicolon = true
|
||||
}
|
||||
case '*':
|
||||
tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
|
||||
case '/':
|
||||
if self.chr == '/' {
|
||||
if self.mode&StoreComments != 0 {
|
||||
literal := string(self.readSingleLineComment())
|
||||
self.comments.AddComment(ast.NewComment(literal, self.idx))
|
||||
continue
|
||||
}
|
||||
self.skipSingleLineComment()
|
||||
continue
|
||||
} else if self.chr == '*' {
|
||||
if self.mode&StoreComments != 0 {
|
||||
literal = string(self.readMultiLineComment())
|
||||
self.comments.AddComment(ast.NewComment(literal, self.idx))
|
||||
continue
|
||||
}
|
||||
self.skipMultiLineComment()
|
||||
continue
|
||||
} else {
|
||||
// Could be division, could be RegExp literal
|
||||
tkn = self.switch2(token.SLASH, token.QUOTIENT_ASSIGN)
|
||||
insertSemicolon = true
|
||||
}
|
||||
case '%':
|
||||
tkn = self.switch2(token.REMAINDER, token.REMAINDER_ASSIGN)
|
||||
case '^':
|
||||
tkn = self.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN)
|
||||
case '<':
|
||||
tkn = self.switch4(token.LESS, token.LESS_OR_EQUAL, '<', token.SHIFT_LEFT, token.SHIFT_LEFT_ASSIGN)
|
||||
case '>':
|
||||
tkn = self.switch6(token.GREATER, token.GREATER_OR_EQUAL, '>', token.SHIFT_RIGHT, token.SHIFT_RIGHT_ASSIGN, '>', token.UNSIGNED_SHIFT_RIGHT, token.UNSIGNED_SHIFT_RIGHT_ASSIGN)
|
||||
case '=':
|
||||
tkn = self.switch2(token.ASSIGN, token.EQUAL)
|
||||
if tkn == token.EQUAL && self.chr == '=' {
|
||||
self.read()
|
||||
tkn = token.STRICT_EQUAL
|
||||
}
|
||||
case '!':
|
||||
tkn = self.switch2(token.NOT, token.NOT_EQUAL)
|
||||
if tkn == token.NOT_EQUAL && self.chr == '=' {
|
||||
self.read()
|
||||
tkn = token.STRICT_NOT_EQUAL
|
||||
}
|
||||
case '&':
|
||||
if self.chr == '^' {
|
||||
self.read()
|
||||
tkn = self.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
|
||||
} else {
|
||||
tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
|
||||
}
|
||||
case '|':
|
||||
tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
|
||||
case '~':
|
||||
tkn = token.BITWISE_NOT
|
||||
case '?':
|
||||
tkn = token.QUESTION_MARK
|
||||
case '"', '\'':
|
||||
insertSemicolon = true
|
||||
tkn = token.STRING
|
||||
var err error
|
||||
literal, err = self.scanString(self.chrOffset - 1)
|
||||
if err != nil {
|
||||
tkn = token.ILLEGAL
|
||||
}
|
||||
default:
|
||||
self.errorUnexpected(idx, chr)
|
||||
tkn = token.ILLEGAL
|
||||
}
|
||||
}
|
||||
self.insertSemicolon = insertSemicolon
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) switch2(tkn0, tkn1 token.Token) token.Token {
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn1
|
||||
}
|
||||
return tkn0
|
||||
}
|
||||
|
||||
func (self *_parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token {
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn1
|
||||
}
|
||||
if self.chr == chr2 {
|
||||
self.read()
|
||||
return tkn2
|
||||
}
|
||||
return tkn0
|
||||
}
|
||||
|
||||
func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token {
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn1
|
||||
}
|
||||
if self.chr == chr2 {
|
||||
self.read()
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn3
|
||||
}
|
||||
return tkn2
|
||||
}
|
||||
return tkn0
|
||||
}
|
||||
|
||||
func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token {
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn1
|
||||
}
|
||||
if self.chr == chr2 {
|
||||
self.read()
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn3
|
||||
}
|
||||
if self.chr == chr3 {
|
||||
self.read()
|
||||
if self.chr == '=' {
|
||||
self.read()
|
||||
return tkn5
|
||||
}
|
||||
return tkn4
|
||||
}
|
||||
return tkn2
|
||||
}
|
||||
return tkn0
|
||||
}
|
||||
|
||||
func (self *_parser) chrAt(index int) _chr {
|
||||
value, width := utf8.DecodeRuneInString(self.str[index:])
|
||||
return _chr{
|
||||
value: value,
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) _peek() rune {
|
||||
if self.offset+1 < self.length {
|
||||
return rune(self.str[self.offset+1])
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (self *_parser) read() {
|
||||
if self.offset < self.length {
|
||||
self.chrOffset = self.offset
|
||||
chr, width := rune(self.str[self.offset]), 1
|
||||
if chr >= utf8.RuneSelf { // !ASCII
|
||||
chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
|
||||
if chr == utf8.RuneError && width == 1 {
|
||||
self.error(self.chrOffset, "Invalid UTF-8 character")
|
||||
}
|
||||
}
|
||||
self.offset += width
|
||||
self.chr = chr
|
||||
} else {
|
||||
self.chrOffset = self.length
|
||||
self.chr = -1 // EOF
|
||||
}
|
||||
}
|
||||
|
||||
// This is here since the functions are so similar
|
||||
func (self *_RegExp_parser) read() {
|
||||
if self.offset < self.length {
|
||||
self.chrOffset = self.offset
|
||||
chr, width := rune(self.str[self.offset]), 1
|
||||
if chr >= utf8.RuneSelf { // !ASCII
|
||||
chr, width = utf8.DecodeRuneInString(self.str[self.offset:])
|
||||
if chr == utf8.RuneError && width == 1 {
|
||||
self.error(self.chrOffset, "Invalid UTF-8 character")
|
||||
}
|
||||
}
|
||||
self.offset += width
|
||||
self.chr = chr
|
||||
} else {
|
||||
self.chrOffset = self.length
|
||||
self.chr = -1 // EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) readSingleLineComment() (result []rune) {
|
||||
for self.chr != -1 {
|
||||
self.read()
|
||||
if isLineTerminator(self.chr) {
|
||||
return
|
||||
}
|
||||
result = append(result, self.chr)
|
||||
}
|
||||
|
||||
// Get rid of the trailing -1
|
||||
result = result[:len(result)-1]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *_parser) readMultiLineComment() (result []rune) {
|
||||
self.read()
|
||||
for self.chr >= 0 {
|
||||
chr := self.chr
|
||||
self.read()
|
||||
if chr == '*' && self.chr == '/' {
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
|
||||
result = append(result, chr)
|
||||
}
|
||||
|
||||
self.errorUnexpected(0, self.chr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (self *_parser) skipSingleLineComment() {
|
||||
for self.chr != -1 {
|
||||
self.read()
|
||||
if isLineTerminator(self.chr) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) skipMultiLineComment() {
|
||||
self.read()
|
||||
for self.chr >= 0 {
|
||||
chr := self.chr
|
||||
self.read()
|
||||
if chr == '*' && self.chr == '/' {
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.errorUnexpected(0, self.chr)
|
||||
}
|
||||
|
||||
func (self *_parser) skipWhiteSpace() {
|
||||
for {
|
||||
switch self.chr {
|
||||
case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff':
|
||||
self.read()
|
||||
continue
|
||||
case '\r':
|
||||
if self._peek() == '\n' {
|
||||
self.comments.AtLineBreak()
|
||||
self.read()
|
||||
}
|
||||
fallthrough
|
||||
case '\u2028', '\u2029', '\n':
|
||||
if self.insertSemicolon {
|
||||
return
|
||||
}
|
||||
self.comments.AtLineBreak()
|
||||
self.read()
|
||||
continue
|
||||
}
|
||||
if self.chr >= utf8.RuneSelf {
|
||||
if unicode.IsSpace(self.chr) {
|
||||
self.read()
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) skipLineWhiteSpace() {
|
||||
for isLineWhiteSpace(self.chr) {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) scanMantissa(base int) {
|
||||
for digitValue(self.chr) < base {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) scanEscape(quote rune) {
|
||||
|
||||
var length, base uint32
|
||||
switch self.chr {
|
||||
//case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
// Octal:
|
||||
// length, base, limit = 3, 8, 255
|
||||
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0':
|
||||
self.read()
|
||||
return
|
||||
case '\r', '\n', '\u2028', '\u2029':
|
||||
self.scanNewline()
|
||||
return
|
||||
case 'x':
|
||||
self.read()
|
||||
length, base = 2, 16
|
||||
case 'u':
|
||||
self.read()
|
||||
length, base = 4, 16
|
||||
default:
|
||||
self.read() // Always make progress
|
||||
return
|
||||
}
|
||||
|
||||
var value uint32
|
||||
for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
break
|
||||
}
|
||||
value = value*base + digit
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) scanString(offset int) (string, error) {
|
||||
// " ' /
|
||||
quote := rune(self.str[offset])
|
||||
|
||||
for self.chr != quote {
|
||||
chr := self.chr
|
||||
if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
|
||||
goto newline
|
||||
}
|
||||
self.read()
|
||||
if chr == '\\' {
|
||||
if quote == '/' {
|
||||
if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 {
|
||||
goto newline
|
||||
}
|
||||
self.read()
|
||||
} else {
|
||||
self.scanEscape(quote)
|
||||
}
|
||||
} else if chr == '[' && quote == '/' {
|
||||
// Allow a slash (/) in a bracket character class ([...])
|
||||
// TODO Fix this, this is hacky...
|
||||
quote = -1
|
||||
} else if chr == ']' && quote == -1 {
|
||||
quote = '/'
|
||||
}
|
||||
}
|
||||
|
||||
// " ' /
|
||||
self.read()
|
||||
|
||||
return string(self.str[offset:self.chrOffset]), nil
|
||||
|
||||
newline:
|
||||
self.scanNewline()
|
||||
err := "String not terminated"
|
||||
if quote == '/' {
|
||||
err = "Invalid regular expression: missing /"
|
||||
self.error(self.idxOf(offset), err)
|
||||
}
|
||||
return "", errors.New(err)
|
||||
}
|
||||
|
||||
func (self *_parser) scanNewline() {
|
||||
if self.chr == '\r' {
|
||||
self.read()
|
||||
if self.chr != '\n' {
|
||||
return
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
|
||||
func hex2decimal(chr byte) (value rune, ok bool) {
|
||||
{
|
||||
chr := rune(chr)
|
||||
switch {
|
||||
case '0' <= chr && chr <= '9':
|
||||
return chr - '0', true
|
||||
case 'a' <= chr && chr <= 'f':
|
||||
return chr - 'a' + 10, true
|
||||
case 'A' <= chr && chr <= 'F':
|
||||
return chr - 'A' + 10, true
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func parseNumberLiteral(literal string) (value interface{}, err error) {
|
||||
// TODO Is Uint okay? What about -MAX_UINT
|
||||
value, err = strconv.ParseInt(literal, 0, 64)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
parseIntErr := err // Save this first error, just in case
|
||||
|
||||
value, err = strconv.ParseFloat(literal, 64)
|
||||
if err == nil {
|
||||
return
|
||||
} else if err.(*strconv.NumError).Err == strconv.ErrRange {
|
||||
// Infinity, etc.
|
||||
return value, nil
|
||||
}
|
||||
|
||||
err = parseIntErr
|
||||
|
||||
if err.(*strconv.NumError).Err == strconv.ErrRange {
|
||||
if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') {
|
||||
// Could just be a very large number (e.g. 0x8000000000000000)
|
||||
var value float64
|
||||
literal = literal[2:]
|
||||
for _, chr := range literal {
|
||||
digit := digitValue(chr)
|
||||
if digit >= 16 {
|
||||
goto error
|
||||
}
|
||||
value = value*16 + float64(digit)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
return nil, errors.New("Illegal numeric literal")
|
||||
}
|
||||
|
||||
func parseStringLiteral(literal string) (string, error) {
|
||||
// Best case scenario...
|
||||
if literal == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Slightly less-best case scenario...
|
||||
if !strings.ContainsRune(literal, '\\') {
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
str := literal
|
||||
buffer := bytes.NewBuffer(make([]byte, 0, 3*len(literal)/2))
|
||||
|
||||
for len(str) > 0 {
|
||||
switch chr := str[0]; {
|
||||
// We do not explicitly handle the case of the quote
|
||||
// value, which can be: " ' /
|
||||
// This assumes we're already passed a partially well-formed literal
|
||||
case chr >= utf8.RuneSelf:
|
||||
chr, size := utf8.DecodeRuneInString(str)
|
||||
buffer.WriteRune(chr)
|
||||
str = str[size:]
|
||||
continue
|
||||
case chr != '\\':
|
||||
buffer.WriteByte(chr)
|
||||
str = str[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if len(str) <= 1 {
|
||||
panic("len(str) <= 1")
|
||||
}
|
||||
chr := str[1]
|
||||
var value rune
|
||||
if chr >= utf8.RuneSelf {
|
||||
str = str[1:]
|
||||
var size int
|
||||
value, size = utf8.DecodeRuneInString(str)
|
||||
str = str[size:] // \ + <character>
|
||||
} else {
|
||||
str = str[2:] // \<character>
|
||||
switch chr {
|
||||
case 'b':
|
||||
value = '\b'
|
||||
case 'f':
|
||||
value = '\f'
|
||||
case 'n':
|
||||
value = '\n'
|
||||
case 'r':
|
||||
value = '\r'
|
||||
case 't':
|
||||
value = '\t'
|
||||
case 'v':
|
||||
value = '\v'
|
||||
case 'x', 'u':
|
||||
size := 0
|
||||
switch chr {
|
||||
case 'x':
|
||||
size = 2
|
||||
case 'u':
|
||||
size = 4
|
||||
}
|
||||
if len(str) < size {
|
||||
return "", fmt.Errorf("invalid escape: \\%s: len(%q) != %d", string(chr), str, size)
|
||||
}
|
||||
for j := 0; j < size; j++ {
|
||||
decimal, ok := hex2decimal(str[j])
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid escape: \\%s: %q", string(chr), str[:size])
|
||||
}
|
||||
value = value<<4 | decimal
|
||||
}
|
||||
str = str[size:]
|
||||
if chr == 'x' {
|
||||
break
|
||||
}
|
||||
if value > utf8.MaxRune {
|
||||
panic("value > utf8.MaxRune")
|
||||
}
|
||||
case '0':
|
||||
if len(str) == 0 || '0' > str[0] || str[0] > '7' {
|
||||
value = 0
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case '1', '2', '3', '4', '5', '6', '7':
|
||||
// TODO strict
|
||||
value = rune(chr) - '0'
|
||||
j := 0
|
||||
for ; j < 2; j++ {
|
||||
if len(str) < j+1 {
|
||||
break
|
||||
}
|
||||
chr := str[j]
|
||||
if '0' > chr || chr > '7' {
|
||||
break
|
||||
}
|
||||
decimal := rune(str[j]) - '0'
|
||||
value = (value << 3) | decimal
|
||||
}
|
||||
str = str[j:]
|
||||
case '\\':
|
||||
value = '\\'
|
||||
case '\'', '"':
|
||||
value = rune(chr)
|
||||
case '\r':
|
||||
if len(str) > 0 {
|
||||
if str[0] == '\n' {
|
||||
str = str[1:]
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
case '\n':
|
||||
continue
|
||||
default:
|
||||
value = rune(chr)
|
||||
}
|
||||
}
|
||||
buffer.WriteRune(value)
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {
|
||||
|
||||
offset := self.chrOffset
|
||||
tkn := token.NUMBER
|
||||
|
||||
if decimalPoint {
|
||||
offset--
|
||||
self.scanMantissa(10)
|
||||
goto exponent
|
||||
}
|
||||
|
||||
if self.chr == '0' {
|
||||
offset := self.chrOffset
|
||||
self.read()
|
||||
if self.chr == 'x' || self.chr == 'X' {
|
||||
// Hexadecimal
|
||||
self.read()
|
||||
if isDigit(self.chr, 16) {
|
||||
self.read()
|
||||
} else {
|
||||
return token.ILLEGAL, self.str[offset:self.chrOffset]
|
||||
}
|
||||
self.scanMantissa(16)
|
||||
|
||||
if self.chrOffset-offset <= 2 {
|
||||
// Only "0x" or "0X"
|
||||
self.error(0, "Illegal hexadecimal number")
|
||||
}
|
||||
|
||||
goto hexadecimal
|
||||
} else if self.chr == '.' {
|
||||
// Float
|
||||
goto float
|
||||
} else {
|
||||
// Octal, Float
|
||||
if self.chr == 'e' || self.chr == 'E' {
|
||||
goto exponent
|
||||
}
|
||||
self.scanMantissa(8)
|
||||
if self.chr == '8' || self.chr == '9' {
|
||||
return token.ILLEGAL, self.str[offset:self.chrOffset]
|
||||
}
|
||||
goto octal
|
||||
}
|
||||
}
|
||||
|
||||
self.scanMantissa(10)
|
||||
|
||||
float:
|
||||
if self.chr == '.' {
|
||||
self.read()
|
||||
self.scanMantissa(10)
|
||||
}
|
||||
|
||||
exponent:
|
||||
if self.chr == 'e' || self.chr == 'E' {
|
||||
self.read()
|
||||
if self.chr == '-' || self.chr == '+' {
|
||||
self.read()
|
||||
}
|
||||
if isDecimalDigit(self.chr) {
|
||||
self.read()
|
||||
self.scanMantissa(10)
|
||||
} else {
|
||||
return token.ILLEGAL, self.str[offset:self.chrOffset]
|
||||
}
|
||||
}
|
||||
|
||||
hexadecimal:
|
||||
octal:
|
||||
if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) {
|
||||
return token.ILLEGAL, self.str[offset:self.chrOffset]
|
||||
}
|
||||
|
||||
return tkn, self.str[offset:self.chrOffset]
|
||||
}
|
344
vendor/github.com/robertkrimen/otto/parser/parser.go
generated
vendored
Normal file
344
vendor/github.com/robertkrimen/otto/parser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/parser"
|
||||
)
|
||||
|
||||
Parse and return an AST
|
||||
|
||||
filename := "" // A filename is optional
|
||||
src := `
|
||||
// Sample xyzzy example
|
||||
(function(){
|
||||
if (3.14159 > 0) {
|
||||
console.log("Hello, World.");
|
||||
return;
|
||||
}
|
||||
|
||||
var xyzzy = NaN;
|
||||
console.log("Nothing happens.");
|
||||
return xyzzy;
|
||||
})();
|
||||
`
|
||||
|
||||
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
program, err := parser.ParseFile(nil, filename, src, 0)
|
||||
|
||||
Warning
|
||||
|
||||
The parser and AST interfaces are still works-in-progress (particularly where
|
||||
node types are concerned) and may change in the future.
|
||||
|
||||
*/
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
"github.com/robertkrimen/otto/file"
|
||||
"github.com/robertkrimen/otto/token"
|
||||
"gopkg.in/sourcemap.v1"
|
||||
)
|
||||
|
||||
// A Mode value is a set of flags (or 0). They control optional parser functionality.
|
||||
type Mode uint
|
||||
|
||||
const (
|
||||
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
|
||||
StoreComments // Store the comments from source to the comments map
|
||||
)
|
||||
|
||||
type _parser struct {
|
||||
str string
|
||||
length int
|
||||
base int
|
||||
|
||||
chr rune // The current character
|
||||
chrOffset int // The offset of current character
|
||||
offset int // The offset after current character (may be greater than 1)
|
||||
|
||||
idx file.Idx // The index of token
|
||||
token token.Token // The token
|
||||
literal string // The literal of the token, if any
|
||||
|
||||
scope *_scope
|
||||
insertSemicolon bool // If we see a newline, then insert an implicit semicolon
|
||||
implicitSemicolon bool // An implicit semicolon exists
|
||||
|
||||
errors ErrorList
|
||||
|
||||
recover struct {
|
||||
// Scratch when trying to seek to the next statement, etc.
|
||||
idx file.Idx
|
||||
count int
|
||||
}
|
||||
|
||||
mode Mode
|
||||
|
||||
file *file.File
|
||||
|
||||
comments *ast.Comments
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
Scan() (tkn token.Token, literal string, idx file.Idx)
|
||||
}
|
||||
|
||||
func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser {
|
||||
return &_parser{
|
||||
chr: ' ', // This is set so we can start scanning by skipping whitespace
|
||||
str: src,
|
||||
length: len(src),
|
||||
base: base,
|
||||
file: file.NewFile(filename, src, base).WithSourceMap(sm),
|
||||
comments: ast.NewComments(),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a new Parser.
|
||||
func NewParser(filename, src string) Parser {
|
||||
return _newParser(filename, src, 1, nil)
|
||||
}
|
||||
|
||||
func ReadSource(filename string, src interface{}) ([]byte, error) {
|
||||
if src != nil {
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return []byte(src), nil
|
||||
case []byte:
|
||||
return src, nil
|
||||
case *bytes.Buffer:
|
||||
if src != nil {
|
||||
return src.Bytes(), nil
|
||||
}
|
||||
case io.Reader:
|
||||
var bfr bytes.Buffer
|
||||
if _, err := io.Copy(&bfr, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bfr.Bytes(), nil
|
||||
}
|
||||
return nil, errors.New("invalid source")
|
||||
}
|
||||
return ioutil.ReadFile(filename)
|
||||
}
|
||||
|
||||
func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) {
|
||||
if src == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case string:
|
||||
return sourcemap.Parse(filename, []byte(src))
|
||||
case []byte:
|
||||
return sourcemap.Parse(filename, src)
|
||||
case *bytes.Buffer:
|
||||
if src != nil {
|
||||
return sourcemap.Parse(filename, src.Bytes())
|
||||
}
|
||||
case io.Reader:
|
||||
var bfr bytes.Buffer
|
||||
if _, err := io.Copy(&bfr, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sourcemap.Parse(filename, bfr.Bytes())
|
||||
case *sourcemap.Consumer:
|
||||
return src, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("invalid sourcemap type")
|
||||
}
|
||||
|
||||
func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) {
|
||||
src, err := ReadSource(filename, javascriptSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sourcemapSource == nil {
|
||||
lines := bytes.Split(src, []byte("\n"))
|
||||
lastLine := lines[len(lines)-1]
|
||||
if bytes.HasPrefix(lastLine, []byte("//# sourceMappingURL=data:application/json")) {
|
||||
bits := bytes.SplitN(lastLine, []byte(","), 2)
|
||||
if len(bits) == 2 {
|
||||
if d, err := base64.StdEncoding.DecodeString(string(bits[1])); err == nil {
|
||||
sourcemapSource = d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sm, err := ReadSourceMap(filename, sourcemapSource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := 1
|
||||
if fileSet != nil {
|
||||
base = fileSet.AddFile(filename, string(src))
|
||||
}
|
||||
|
||||
parser := _newParser(filename, string(src), base, sm)
|
||||
parser.mode = mode
|
||||
program, err := parser.parse()
|
||||
program.Comments = parser.comments.CommentMap
|
||||
|
||||
return program, err
|
||||
}
|
||||
|
||||
// ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
|
||||
// the corresponding ast.Program node.
|
||||
//
|
||||
// If fileSet == nil, ParseFile parses source without a FileSet.
|
||||
// If fileSet != nil, ParseFile first adds filename and src to fileSet.
|
||||
//
|
||||
// The filename argument is optional and is used for labelling errors, etc.
|
||||
//
|
||||
// src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
|
||||
//
|
||||
// // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||
// program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
|
||||
//
|
||||
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) {
|
||||
return ParseFileWithSourceMap(fileSet, filename, src, nil, mode)
|
||||
}
|
||||
|
||||
// ParseFunction parses a given parameter list and body as a function and returns the
|
||||
// corresponding ast.FunctionLiteral node.
|
||||
//
|
||||
// The parameter list, if any, should be a comma-separated list of identifiers.
|
||||
//
|
||||
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
|
||||
|
||||
src := "(function(" + parameterList + ") {\n" + body + "\n})"
|
||||
|
||||
parser := _newParser("", src, 1, nil)
|
||||
program, err := parser.parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
|
||||
}
|
||||
|
||||
// Scan reads a single token from the source at the current offset, increments the offset and
|
||||
// returns the token.Token token, a string literal representing the value of the token (if applicable)
|
||||
// and it's current file.Idx index.
|
||||
func (self *_parser) Scan() (tkn token.Token, literal string, idx file.Idx) {
|
||||
return self.scan()
|
||||
}
|
||||
|
||||
func (self *_parser) slice(idx0, idx1 file.Idx) string {
|
||||
from := int(idx0) - self.base
|
||||
to := int(idx1) - self.base
|
||||
if from >= 0 && to <= len(self.str) {
|
||||
return self.str[from:to]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *_parser) parse() (*ast.Program, error) {
|
||||
self.next()
|
||||
program := self.parseProgram()
|
||||
if false {
|
||||
self.errors.Sort()
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(program, self.comments.FetchAll(), ast.TRAILING)
|
||||
}
|
||||
|
||||
return program, self.errors.Err()
|
||||
}
|
||||
|
||||
func (self *_parser) next() {
|
||||
self.token, self.literal, self.idx = self.scan()
|
||||
}
|
||||
|
||||
func (self *_parser) optionalSemicolon() {
|
||||
if self.token == token.SEMICOLON {
|
||||
self.next()
|
||||
return
|
||||
}
|
||||
|
||||
if self.implicitSemicolon {
|
||||
self.implicitSemicolon = false
|
||||
return
|
||||
}
|
||||
|
||||
if self.token != token.EOF && self.token != token.RIGHT_BRACE {
|
||||
self.expect(token.SEMICOLON)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) semicolon() {
|
||||
if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
|
||||
if self.implicitSemicolon {
|
||||
self.implicitSemicolon = false
|
||||
return
|
||||
}
|
||||
|
||||
self.expect(token.SEMICOLON)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) idxOf(offset int) file.Idx {
|
||||
return file.Idx(self.base + offset)
|
||||
}
|
||||
|
||||
func (self *_parser) expect(value token.Token) file.Idx {
|
||||
idx := self.idx
|
||||
if self.token != value {
|
||||
self.errorUnexpectedToken(self.token)
|
||||
}
|
||||
self.next()
|
||||
return idx
|
||||
}
|
||||
|
||||
func lineCount(str string) (int, int) {
|
||||
line, last := 0, -1
|
||||
pair := false
|
||||
for index, chr := range str {
|
||||
switch chr {
|
||||
case '\r':
|
||||
line += 1
|
||||
last = index
|
||||
pair = true
|
||||
continue
|
||||
case '\n':
|
||||
if !pair {
|
||||
line += 1
|
||||
}
|
||||
last = index
|
||||
case '\u2028', '\u2029':
|
||||
line += 1
|
||||
last = index + 2
|
||||
}
|
||||
pair = false
|
||||
}
|
||||
return line, last
|
||||
}
|
||||
|
||||
func (self *_parser) position(idx file.Idx) file.Position {
|
||||
position := file.Position{}
|
||||
offset := int(idx) - self.base
|
||||
str := self.str[:offset]
|
||||
position.Filename = self.file.Name()
|
||||
line, last := lineCount(str)
|
||||
position.Line = 1 + line
|
||||
if last >= 0 {
|
||||
position.Column = offset - last
|
||||
} else {
|
||||
position.Column = 1 + len(str)
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
358
vendor/github.com/robertkrimen/otto/parser/regexp.go
generated
vendored
Normal file
358
vendor/github.com/robertkrimen/otto/parser/regexp.go
generated
vendored
Normal file
@@ -0,0 +1,358 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type _RegExp_parser struct {
|
||||
str string
|
||||
length int
|
||||
|
||||
chr rune // The current character
|
||||
chrOffset int // The offset of current character
|
||||
offset int // The offset after current character (may be greater than 1)
|
||||
|
||||
errors []error
|
||||
invalid bool // The input is an invalid JavaScript RegExp
|
||||
|
||||
goRegexp *bytes.Buffer
|
||||
}
|
||||
|
||||
// TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
|
||||
//
|
||||
// re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
|
||||
// backreference (\1, \2, ...) will cause an error.
|
||||
//
|
||||
// re2 (Go) has a different definition for \s: [\t\n\f\r ].
|
||||
// The JavaScript definition, on the other hand, also includes \v, Unicode "Separator, Space", etc.
|
||||
//
|
||||
// If the pattern is invalid (not valid even in JavaScript), then this function
|
||||
// returns the empty string and an error.
|
||||
//
|
||||
// If the pattern is valid, but incompatible (contains a lookahead or backreference),
|
||||
// then this function returns the transformation (a non-empty string) AND an error.
|
||||
func TransformRegExp(pattern string) (string, error) {
|
||||
|
||||
if pattern == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// TODO If without \, if without (?=, (?!, then another shortcut
|
||||
|
||||
parser := _RegExp_parser{
|
||||
str: pattern,
|
||||
length: len(pattern),
|
||||
goRegexp: bytes.NewBuffer(make([]byte, 0, 3*len(pattern)/2)),
|
||||
}
|
||||
parser.read() // Pull in the first character
|
||||
parser.scan()
|
||||
var err error
|
||||
if len(parser.errors) > 0 {
|
||||
err = parser.errors[0]
|
||||
}
|
||||
if parser.invalid {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Might not be re2 compatible, but is still a valid JavaScript RegExp
|
||||
return parser.goRegexp.String(), err
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) scan() {
|
||||
for self.chr != -1 {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.pass()
|
||||
self.scanBracket()
|
||||
case ')':
|
||||
self.error(-1, "Unmatched ')'")
|
||||
self.invalid = true
|
||||
self.pass()
|
||||
default:
|
||||
self.pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (...)
|
||||
func (self *_RegExp_parser) scanGroup() {
|
||||
str := self.str[self.chrOffset:]
|
||||
if len(str) > 1 { // A possibility of (?= or (?!
|
||||
if str[0] == '?' {
|
||||
if str[1] == '=' || str[1] == '!' {
|
||||
self.error(-1, "re2: Invalid (%s) <lookahead>", self.str[self.chrOffset:self.chrOffset+2])
|
||||
}
|
||||
}
|
||||
}
|
||||
for self.chr != -1 && self.chr != ')' {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.pass()
|
||||
self.scanBracket()
|
||||
default:
|
||||
self.pass()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if self.chr != ')' {
|
||||
self.error(-1, "Unterminated group")
|
||||
self.invalid = true
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// [...]
|
||||
func (self *_RegExp_parser) scanBracket() {
|
||||
for self.chr != -1 {
|
||||
if self.chr == ']' {
|
||||
break
|
||||
} else if self.chr == '\\' {
|
||||
self.read()
|
||||
self.scanEscape(true)
|
||||
continue
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
if self.chr != ']' {
|
||||
self.error(-1, "Unterminated character class")
|
||||
self.invalid = true
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// \...
|
||||
func (self *_RegExp_parser) scanEscape(inClass bool) {
|
||||
offset := self.chrOffset
|
||||
|
||||
var length, base uint32
|
||||
switch self.chr {
|
||||
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
var value int64
|
||||
size := 0
|
||||
for {
|
||||
digit := int64(digitValue(self.chr))
|
||||
if digit >= 8 {
|
||||
// Not a valid digit
|
||||
break
|
||||
}
|
||||
value = value*8 + digit
|
||||
self.read()
|
||||
size += 1
|
||||
}
|
||||
if size == 1 { // The number of characters read
|
||||
_, err := self.goRegexp.Write([]byte{'\\', byte(value) + '0'})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
if value != 0 {
|
||||
// An invalid backreference
|
||||
self.error(-1, "re2: Invalid \\%d <backreference>", value)
|
||||
}
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
_, err := self.goRegexp.Write(tmp)
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
return
|
||||
|
||||
case '8', '9':
|
||||
size := 0
|
||||
for {
|
||||
digit := digitValue(self.chr)
|
||||
if digit >= 10 {
|
||||
// Not a valid digit
|
||||
break
|
||||
}
|
||||
self.read()
|
||||
size += 1
|
||||
}
|
||||
err := self.goRegexp.WriteByte('\\')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
_, err = self.goRegexp.WriteString(self.str[offset:self.chrOffset])
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.error(-1, "re2: Invalid \\%s <backreference>", self.str[offset:self.chrOffset])
|
||||
return
|
||||
|
||||
case 'x':
|
||||
self.read()
|
||||
length, base = 2, 16
|
||||
|
||||
case 'u':
|
||||
self.read()
|
||||
length, base = 4, 16
|
||||
|
||||
case 'b':
|
||||
if inClass {
|
||||
_, err := self.goRegexp.Write([]byte{'\\', 'x', '0', '8'})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 'B':
|
||||
fallthrough
|
||||
|
||||
case 'd', 'D', 's', 'S', 'w', 'W':
|
||||
// This is slightly broken, because ECMAScript
|
||||
// includes \v in \s, \S, while re2 does not
|
||||
fallthrough
|
||||
|
||||
case '\\':
|
||||
fallthrough
|
||||
|
||||
case 'f', 'n', 'r', 't', 'v':
|
||||
err := self.goRegexp.WriteByte('\\')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.pass()
|
||||
return
|
||||
|
||||
case 'c':
|
||||
self.read()
|
||||
var value int64
|
||||
if 'a' <= self.chr && self.chr <= 'z' {
|
||||
value = int64(self.chr) - 'a' + 1
|
||||
} else if 'A' <= self.chr && self.chr <= 'Z' {
|
||||
value = int64(self.chr) - 'A' + 1
|
||||
} else {
|
||||
err := self.goRegexp.WriteByte('c')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
_, err := self.goRegexp.Write(tmp)
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
|
||||
default:
|
||||
// $ is an identifier character, so we have to have
|
||||
// a special case for it here
|
||||
if self.chr == '$' || !isIdentifierPart(self.chr) {
|
||||
// A non-identifier character needs escaping
|
||||
err := self.goRegexp.WriteByte('\\')
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
} else {
|
||||
// Unescape the character for re2
|
||||
}
|
||||
self.pass()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we're a \u.... or \x...
|
||||
valueOffset := self.chrOffset
|
||||
|
||||
var value uint32
|
||||
{
|
||||
length := length
|
||||
for ; length > 0; length-- {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
// Not a valid digit
|
||||
goto skip
|
||||
}
|
||||
value = value*base + digit
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
if length == 4 {
|
||||
_, err := self.goRegexp.Write([]byte{
|
||||
'\\',
|
||||
'x',
|
||||
'{',
|
||||
self.str[valueOffset+0],
|
||||
self.str[valueOffset+1],
|
||||
self.str[valueOffset+2],
|
||||
self.str[valueOffset+3],
|
||||
'}',
|
||||
})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
} else if length == 2 {
|
||||
_, err := self.goRegexp.Write([]byte{
|
||||
'\\',
|
||||
'x',
|
||||
self.str[valueOffset+0],
|
||||
self.str[valueOffset+1],
|
||||
})
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
} else {
|
||||
// Should never, ever get here...
|
||||
self.error(-1, "re2: Illegal branch in scanEscape")
|
||||
goto skip
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
skip:
|
||||
_, err := self.goRegexp.WriteString(self.str[offset:self.chrOffset])
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) pass() {
|
||||
if self.chr != -1 {
|
||||
_, err := self.goRegexp.WriteRune(self.chr)
|
||||
if err != nil {
|
||||
self.errors = append(self.errors, err)
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
|
||||
// TODO Better error reporting, use the offset, etc.
|
||||
func (self *_RegExp_parser) error(offset int, msg string, msgValues ...interface{}) error {
|
||||
err := fmt.Errorf(msg, msgValues...)
|
||||
self.errors = append(self.errors, err)
|
||||
return err
|
||||
}
|
44
vendor/github.com/robertkrimen/otto/parser/scope.go
generated
vendored
Normal file
44
vendor/github.com/robertkrimen/otto/parser/scope.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
)
|
||||
|
||||
type _scope struct {
|
||||
outer *_scope
|
||||
allowIn bool
|
||||
inIteration bool
|
||||
inSwitch bool
|
||||
inFunction bool
|
||||
declarationList []ast.Declaration
|
||||
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (self *_parser) openScope() {
|
||||
self.scope = &_scope{
|
||||
outer: self.scope,
|
||||
allowIn: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) closeScope() {
|
||||
self.scope = self.scope.outer
|
||||
}
|
||||
|
||||
func (self *_scope) declare(declaration ast.Declaration) {
|
||||
self.declarationList = append(self.declarationList, declaration)
|
||||
}
|
||||
|
||||
func (self *_scope) hasLabel(name string) bool {
|
||||
for _, label := range self.labels {
|
||||
if label == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if self.outer != nil && !self.inFunction {
|
||||
// Crossing a function boundary to look for a label is verboten
|
||||
return self.outer.hasLabel(name)
|
||||
}
|
||||
return false
|
||||
}
|
940
vendor/github.com/robertkrimen/otto/parser/statement.go
generated
vendored
Normal file
940
vendor/github.com/robertkrimen/otto/parser/statement.go
generated
vendored
Normal file
@@ -0,0 +1,940 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/robertkrimen/otto/ast"
|
||||
"github.com/robertkrimen/otto/token"
|
||||
)
|
||||
|
||||
func (self *_parser) parseBlockStatement() *ast.BlockStatement {
|
||||
node := &ast.BlockStatement{}
|
||||
|
||||
// Find comments before the leading brace
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, self.comments.FetchAll(), ast.LEADING)
|
||||
self.comments.Unset()
|
||||
}
|
||||
|
||||
node.LeftBrace = self.expect(token.LEFT_BRACE)
|
||||
node.List = self.parseStatementList()
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
self.comments.CommentMap.AddComments(node, self.comments.FetchAll(), ast.FINAL)
|
||||
self.comments.AfterBlock()
|
||||
}
|
||||
|
||||
node.RightBrace = self.expect(token.RIGHT_BRACE)
|
||||
|
||||
// Find comments after the trailing brace
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.ResetLineBreak()
|
||||
self.comments.CommentMap.AddComments(node, self.comments.Fetch(), ast.TRAILING)
|
||||
}
|
||||
|
||||
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 {
|
||||
statement := self.parseStatement()
|
||||
list = append(list, statement)
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.ResetLineBreak()
|
||||
}
|
||||
|
||||
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:
|
||||
statement := self.parseDoWhileStatement()
|
||||
self.comments.PostProcessNode(statement)
|
||||
return statement
|
||||
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.FUNCTION:
|
||||
return self.parseFunctionStatement()
|
||||
case token.SWITCH:
|
||||
return self.parseSwitchStatement()
|
||||
case token.RETURN:
|
||||
return self.parseReturnStatement()
|
||||
case token.THROW:
|
||||
return self.parseThrowStatement()
|
||||
case token.TRY:
|
||||
return self.parseTryStatement()
|
||||
}
|
||||
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
expression := self.parseExpression()
|
||||
|
||||
if identifier, isIdentifier := expression.(*ast.Identifier); isIdentifier && self.token == token.COLON {
|
||||
// LabelledStatement
|
||||
colon := self.idx
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.next() // :
|
||||
|
||||
label := identifier.Name
|
||||
for _, value := range self.scope.labels {
|
||||
if label == value {
|
||||
self.error(identifier.Idx0(), "Label '%s' already exists", label)
|
||||
}
|
||||
}
|
||||
var labelComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
labelComments = self.comments.FetchAll()
|
||||
}
|
||||
self.scope.labels = append(self.scope.labels, label) // Push the label
|
||||
statement := self.parseStatement()
|
||||
self.scope.labels = self.scope.labels[:len(self.scope.labels)-1] // Pop the label
|
||||
exp := &ast.LabelledStatement{
|
||||
Label: identifier,
|
||||
Colon: colon,
|
||||
Statement: statement,
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(exp, labelComments, ast.LEADING)
|
||||
}
|
||||
|
||||
return exp
|
||||
}
|
||||
|
||||
self.optionalSemicolon()
|
||||
|
||||
statement := &ast.ExpressionStatement{
|
||||
Expression: expression,
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(statement, comments, ast.LEADING)
|
||||
}
|
||||
return statement
|
||||
}
|
||||
|
||||
func (self *_parser) parseTryStatement() ast.Statement {
|
||||
var tryComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
tryComments = self.comments.FetchAll()
|
||||
}
|
||||
node := &ast.TryStatement{
|
||||
Try: self.expect(token.TRY),
|
||||
Body: self.parseBlockStatement(),
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, tryComments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(node.Body, self.comments.FetchAll(), ast.TRAILING)
|
||||
}
|
||||
|
||||
if self.token == token.CATCH {
|
||||
catch := self.idx
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.next()
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
if self.token != token.IDENTIFIER {
|
||||
self.expect(token.IDENTIFIER)
|
||||
self.nextStatement()
|
||||
return &ast.BadStatement{From: catch, To: self.idx}
|
||||
} else {
|
||||
identifier := self.parseIdentifier()
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
node.Catch = &ast.CatchStatement{
|
||||
Catch: catch,
|
||||
Parameter: identifier,
|
||||
Body: self.parseBlockStatement(),
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node.Catch.Body, self.comments.FetchAll(), ast.TRAILING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.token == token.FINALLY {
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.next()
|
||||
if self.mode&StoreComments != 0 {
|
||||
tryComments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
node.Finally = self.parseBlockStatement()
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node.Finally, tryComments, ast.LEADING)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
var list []*ast.Identifier
|
||||
for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF {
|
||||
if self.token != token.IDENTIFIER {
|
||||
self.expect(token.IDENTIFIER)
|
||||
} else {
|
||||
identifier := self.parseIdentifier()
|
||||
list = append(list, identifier)
|
||||
}
|
||||
if self.token != token.RIGHT_PARENTHESIS {
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.expect(token.COMMA)
|
||||
}
|
||||
}
|
||||
closing := self.expect(token.RIGHT_PARENTHESIS)
|
||||
|
||||
return &ast.ParameterList{
|
||||
Opening: opening,
|
||||
List: list,
|
||||
Closing: closing,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) parseParameterList() (list []string) {
|
||||
for self.token != token.EOF {
|
||||
if self.token != token.IDENTIFIER {
|
||||
self.expect(token.IDENTIFIER)
|
||||
}
|
||||
list = append(list, self.literal)
|
||||
self.next()
|
||||
if self.token != token.EOF {
|
||||
self.expect(token.COMMA)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *_parser) parseFunctionStatement() *ast.FunctionStatement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
function := &ast.FunctionStatement{
|
||||
Function: self.parseFunction(true),
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(function, comments, ast.LEADING)
|
||||
}
|
||||
|
||||
return function
|
||||
}
|
||||
|
||||
func (self *_parser) parseFunction(declaration bool) *ast.FunctionLiteral {
|
||||
|
||||
node := &ast.FunctionLiteral{
|
||||
Function: self.expect(token.FUNCTION),
|
||||
}
|
||||
|
||||
var name *ast.Identifier
|
||||
if self.token == token.IDENTIFIER {
|
||||
name = self.parseIdentifier()
|
||||
if declaration {
|
||||
self.scope.declare(&ast.FunctionDeclaration{
|
||||
Function: node,
|
||||
})
|
||||
}
|
||||
} else if declaration {
|
||||
// Use expect error handling
|
||||
self.expect(token.IDENTIFIER)
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
node.Name = name
|
||||
node.ParameterList = self.parseFunctionParameterList()
|
||||
self.parseFunctionBlock(node)
|
||||
node.Source = self.slice(node.Idx0(), node.Idx1())
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseFunctionBlock(node *ast.FunctionLiteral) {
|
||||
{
|
||||
self.openScope()
|
||||
inFunction := self.scope.inFunction
|
||||
self.scope.inFunction = true
|
||||
defer func() {
|
||||
self.scope.inFunction = inFunction
|
||||
self.closeScope()
|
||||
}()
|
||||
node.Body = self.parseBlockStatement()
|
||||
node.DeclarationList = self.scope.declarationList
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) parseDebuggerStatement() ast.Statement {
|
||||
idx := self.expect(token.DEBUGGER)
|
||||
|
||||
node := &ast.DebuggerStatement{
|
||||
Debugger: idx,
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, self.comments.FetchAll(), ast.TRAILING)
|
||||
}
|
||||
|
||||
self.semicolon()
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseReturnStatement() ast.Statement {
|
||||
idx := self.expect(token.RETURN)
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
}
|
||||
|
||||
self.semicolon()
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseThrowStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
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: self.idx,
|
||||
Argument: self.parseExpression(),
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
}
|
||||
|
||||
self.semicolon()
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseSwitchStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.SWITCH)
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = append(comments, self.comments.FetchAll()...)
|
||||
}
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
node := &ast.SwitchStatement{
|
||||
Discriminant: self.parseExpression(),
|
||||
Default: -1,
|
||||
}
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = append(comments, self.comments.FetchAll()...)
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseWithStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.WITH)
|
||||
var withComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
withComments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
|
||||
node := &ast.WithStatement{
|
||||
Object: self.parseExpression(),
|
||||
}
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
//comments = append(comments, self.comments.FetchAll()...)
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(node, withComments, ast.WITH)
|
||||
}
|
||||
|
||||
node.Body = self.parseStatement()
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseCaseStatement() *ast.CaseStatement {
|
||||
node := &ast.CaseStatement{
|
||||
Case: self.idx,
|
||||
}
|
||||
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
self.comments.Unset()
|
||||
}
|
||||
|
||||
if self.token == token.DEFAULT {
|
||||
self.next()
|
||||
} else {
|
||||
self.expect(token.CASE)
|
||||
node.Test = self.parseExpression()
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.expect(token.COLON)
|
||||
|
||||
for {
|
||||
if self.token == token.EOF ||
|
||||
self.token == token.RIGHT_BRACE ||
|
||||
self.token == token.CASE ||
|
||||
self.token == token.DEFAULT {
|
||||
break
|
||||
}
|
||||
consequent := self.parseStatement()
|
||||
node.Consequent = append(node.Consequent, consequent)
|
||||
}
|
||||
|
||||
// Link the comments to the case statement
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseIterationStatement() ast.Statement {
|
||||
inIteration := self.scope.inIteration
|
||||
self.scope.inIteration = true
|
||||
defer func() {
|
||||
self.scope.inIteration = inIteration
|
||||
}()
|
||||
return self.parseStatement()
|
||||
}
|
||||
|
||||
func (self *_parser) parseForIn(into ast.Expression) *ast.ForInStatement {
|
||||
|
||||
// Already have consumed "<into> in"
|
||||
|
||||
source := self.parseExpression()
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
body := self.parseIterationStatement()
|
||||
|
||||
forin := &ast.ForInStatement{
|
||||
Into: into,
|
||||
Source: source,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return forin
|
||||
}
|
||||
|
||||
func (self *_parser) parseFor(initializer ast.Expression) *ast.ForStatement {
|
||||
|
||||
// Already have consumed "<initializer> ;"
|
||||
|
||||
var test, update ast.Expression
|
||||
|
||||
if self.token != token.SEMICOLON {
|
||||
test = self.parseExpression()
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.expect(token.SEMICOLON)
|
||||
|
||||
if self.token != token.RIGHT_PARENTHESIS {
|
||||
update = self.parseExpression()
|
||||
}
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
body := self.parseIterationStatement()
|
||||
|
||||
forstatement := &ast.ForStatement{
|
||||
Initializer: initializer,
|
||||
Test: test,
|
||||
Update: update,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return forstatement
|
||||
}
|
||||
|
||||
func (self *_parser) parseForOrForInStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
idx := self.expect(token.FOR)
|
||||
var forComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
forComments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
|
||||
var left []ast.Expression
|
||||
|
||||
forIn := false
|
||||
if self.token != token.SEMICOLON {
|
||||
|
||||
allowIn := self.scope.allowIn
|
||||
self.scope.allowIn = false
|
||||
if self.token == token.VAR {
|
||||
var_ := self.idx
|
||||
var varComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
varComments = self.comments.FetchAll()
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.next()
|
||||
list := self.parseVariableDeclarationList(var_)
|
||||
if len(list) == 1 && self.token == token.IN {
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.next() // in
|
||||
forIn = true
|
||||
left = []ast.Expression{list[0]} // There is only one declaration
|
||||
} else {
|
||||
left = list
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(left[0], varComments, ast.LEADING)
|
||||
}
|
||||
} else {
|
||||
left = append(left, self.parseExpression())
|
||||
if self.token == token.IN {
|
||||
self.next()
|
||||
forIn = true
|
||||
}
|
||||
}
|
||||
self.scope.allowIn = allowIn
|
||||
}
|
||||
|
||||
if forIn {
|
||||
switch left[0].(type) {
|
||||
case *ast.Identifier, *ast.DotExpression, *ast.BracketExpression, *ast.VariableExpression:
|
||||
// These are all acceptable
|
||||
default:
|
||||
self.error(idx, "Invalid left-hand side in for-in")
|
||||
self.nextStatement()
|
||||
return &ast.BadStatement{From: idx, To: self.idx}
|
||||
}
|
||||
|
||||
forin := self.parseForIn(left[0])
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(forin, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(forin, forComments, ast.FOR)
|
||||
}
|
||||
return forin
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.expect(token.SEMICOLON)
|
||||
initializer := &ast.SequenceExpression{Sequence: left}
|
||||
forstatement := self.parseFor(initializer)
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(forstatement, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(forstatement, forComments, ast.FOR)
|
||||
}
|
||||
return forstatement
|
||||
}
|
||||
|
||||
func (self *_parser) parseVariableStatement() *ast.VariableStatement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
idx := self.expect(token.VAR)
|
||||
|
||||
list := self.parseVariableDeclarationList(idx)
|
||||
|
||||
statement := &ast.VariableStatement{
|
||||
Var: idx,
|
||||
List: list,
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(statement, comments, ast.LEADING)
|
||||
self.comments.Unset()
|
||||
}
|
||||
self.semicolon()
|
||||
|
||||
return statement
|
||||
}
|
||||
|
||||
func (self *_parser) parseDoWhileStatement() ast.Statement {
|
||||
inIteration := self.scope.inIteration
|
||||
self.scope.inIteration = true
|
||||
defer func() {
|
||||
self.scope.inIteration = inIteration
|
||||
}()
|
||||
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.DO)
|
||||
var doComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
doComments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
node := &ast.DoWhileStatement{}
|
||||
if self.token == token.LEFT_BRACE {
|
||||
node.Body = self.parseBlockStatement()
|
||||
} else {
|
||||
node.Body = self.parseStatement()
|
||||
}
|
||||
|
||||
self.expect(token.WHILE)
|
||||
var whileComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
whileComments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
node.Test = self.parseExpression()
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(node, doComments, ast.DO)
|
||||
self.comments.CommentMap.AddComments(node, whileComments, ast.WHILE)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseWhileStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.WHILE)
|
||||
|
||||
var whileComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
whileComments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
node := &ast.WhileStatement{
|
||||
Test: self.parseExpression(),
|
||||
}
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
node.Body = self.parseIterationStatement()
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(node, whileComments, ast.WHILE)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseIfStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
self.expect(token.IF)
|
||||
var ifComments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
ifComments = self.comments.FetchAll()
|
||||
}
|
||||
|
||||
self.expect(token.LEFT_PARENTHESIS)
|
||||
node := &ast.IfStatement{
|
||||
If: self.idx,
|
||||
Test: self.parseExpression(),
|
||||
}
|
||||
self.expect(token.RIGHT_PARENTHESIS)
|
||||
if self.token == token.LEFT_BRACE {
|
||||
node.Consequent = self.parseBlockStatement()
|
||||
} else {
|
||||
node.Consequent = self.parseStatement()
|
||||
}
|
||||
|
||||
if self.token == token.ELSE {
|
||||
self.next()
|
||||
node.Alternate = self.parseStatement()
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(node, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(node, ifComments, ast.IF)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (self *_parser) parseSourceElement() ast.Statement {
|
||||
statement := self.parseStatement()
|
||||
//self.comments.Unset()
|
||||
return statement
|
||||
}
|
||||
|
||||
func (self *_parser) parseSourceElements() []ast.Statement {
|
||||
body := []ast.Statement(nil)
|
||||
|
||||
for {
|
||||
if self.token != token.STRING {
|
||||
break
|
||||
}
|
||||
body = append(body, self.parseSourceElement())
|
||||
}
|
||||
|
||||
for self.token != token.EOF {
|
||||
body = append(body, self.parseSourceElement())
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
func (self *_parser) parseProgram() *ast.Program {
|
||||
self.openScope()
|
||||
defer self.closeScope()
|
||||
return &ast.Program{
|
||||
Body: self.parseSourceElements(),
|
||||
DeclarationList: self.scope.declarationList,
|
||||
File: self.file,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_parser) parseBreakStatement() ast.Statement {
|
||||
var comments []*ast.Comment
|
||||
if self.mode&StoreComments != 0 {
|
||||
comments = self.comments.FetchAll()
|
||||
}
|
||||
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
|
||||
}
|
||||
breakStatement := &ast.BranchStatement{
|
||||
Idx: idx,
|
||||
Token: token.BREAK,
|
||||
}
|
||||
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(breakStatement, comments, ast.LEADING)
|
||||
self.comments.CommentMap.AddComments(breakStatement, self.comments.FetchAll(), ast.TRAILING)
|
||||
}
|
||||
|
||||
return breakStatement
|
||||
}
|
||||
|
||||
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()
|
||||
breakStatement := &ast.BranchStatement{
|
||||
Idx: idx,
|
||||
Token: token.BREAK,
|
||||
Label: identifier,
|
||||
}
|
||||
if self.mode&StoreComments != 0 {
|
||||
self.comments.CommentMap.AddComments(breakStatement, comments, ast.LEADING)
|
||||
}
|
||||
|
||||
return breakStatement
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user