refactor(common): 重构 Conn 实体并优化地图进入逻辑
- 优化 Conn 实体的 SendPack 方法,提高代码复用性 - 添加 goja 模块到 go.work 文件 - 重构地图进入逻辑,增加玩家广播和刷怪功能 - 调整 OutInfo 结构中的 Vip 和 Viped 字段类型 - 简化 MonsterRefresh 结构体定义
This commit is contained in:
184
common/utils/goja/parser/README.markdown
Normal file
184
common/utils/goja/parser/README.markdown
Normal file
@@ -0,0 +1,184 @@
|
||||
# parser
|
||||
--
|
||||
import "github.com/dop251/goja/parser"
|
||||
|
||||
Package parser implements a parser for JavaScript. Borrowed from https://github.com/robertkrimen/otto/tree/master/parser
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/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.
|
||||
|
||||
--
|
||||
**godocdown** http://github.com/robertkrimen/godocdown
|
||||
176
common/utils/goja/parser/error.go
Normal file
176
common/utils/goja/parser/error.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/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.ESCAPED_RESERVED_WORD:
|
||||
return self.error(self.idx, "Keyword must not contain escaped characters")
|
||||
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
|
||||
}
|
||||
1660
common/utils/goja/parser/expression.go
Normal file
1660
common/utils/goja/parser/expression.go
Normal file
File diff suppressed because it is too large
Load Diff
1179
common/utils/goja/parser/lexer.go
Normal file
1179
common/utils/goja/parser/lexer.go
Normal file
File diff suppressed because it is too large
Load Diff
401
common/utils/goja/parser/lexer_test.go
Normal file
401
common/utils/goja/parser/lexer_test.go
Normal file
@@ -0,0 +1,401 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
tt(t, func() {
|
||||
setup := func(src string) *_parser {
|
||||
parser := newParser("", src)
|
||||
return parser
|
||||
}
|
||||
|
||||
test := func(src string, test ...interface{}) {
|
||||
parser := setup(src)
|
||||
for len(test) > 0 {
|
||||
tkn, literal, _, idx := parser.scan()
|
||||
if len(test) > 0 {
|
||||
is(tkn, test[0].(token.Token))
|
||||
test = test[1:]
|
||||
}
|
||||
if len(test) > 0 {
|
||||
is(literal, unistring.String(test[0].(string)))
|
||||
test = test[1:]
|
||||
}
|
||||
if len(test) > 0 {
|
||||
// FIXME terst, Fix this so that cast to file.Idx is not necessary?
|
||||
is(idx, file.Idx(test[0].(int)))
|
||||
test = test[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("",
|
||||
token.EOF, "", 1,
|
||||
)
|
||||
|
||||
test("#!",
|
||||
token.EOF, "", 3,
|
||||
)
|
||||
|
||||
test("#!\n1",
|
||||
token.NUMBER, "1", 4,
|
||||
token.EOF, "", 5,
|
||||
)
|
||||
|
||||
test("1",
|
||||
token.NUMBER, "1", 1,
|
||||
token.EOF, "", 2,
|
||||
)
|
||||
|
||||
test(".0",
|
||||
token.NUMBER, ".0", 1,
|
||||
token.EOF, "", 3,
|
||||
)
|
||||
|
||||
test("abc",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.EOF, "", 4,
|
||||
)
|
||||
|
||||
test("abc(1)",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.LEFT_PARENTHESIS, "", 4,
|
||||
token.NUMBER, "1", 5,
|
||||
token.RIGHT_PARENTHESIS, "", 6,
|
||||
token.EOF, "", 7,
|
||||
)
|
||||
|
||||
test(".",
|
||||
token.PERIOD, "", 1,
|
||||
token.EOF, "", 2,
|
||||
)
|
||||
|
||||
test("===.",
|
||||
token.STRICT_EQUAL, "", 1,
|
||||
token.PERIOD, "", 4,
|
||||
token.EOF, "", 5,
|
||||
)
|
||||
|
||||
test(">>>=.0",
|
||||
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
|
||||
token.NUMBER, ".0", 5,
|
||||
token.EOF, "", 7,
|
||||
)
|
||||
|
||||
test(">>>=0.0.",
|
||||
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
|
||||
token.NUMBER, "0.0", 5,
|
||||
token.PERIOD, "", 8,
|
||||
token.EOF, "", 9,
|
||||
)
|
||||
|
||||
test("\"abc\"",
|
||||
token.STRING, "\"abc\"", 1,
|
||||
token.EOF, "", 6,
|
||||
)
|
||||
|
||||
test("abc = //",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.ASSIGN, "", 5,
|
||||
token.EOF, "", 9,
|
||||
)
|
||||
|
||||
test("abc = 1 / 2",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.ASSIGN, "", 5,
|
||||
token.NUMBER, "1", 7,
|
||||
token.SLASH, "", 9,
|
||||
token.NUMBER, "2", 11,
|
||||
token.EOF, "", 12,
|
||||
)
|
||||
|
||||
test("xyzzy = 'Nothing happens.'",
|
||||
token.IDENTIFIER, "xyzzy", 1,
|
||||
token.ASSIGN, "", 7,
|
||||
token.STRING, "'Nothing happens.'", 9,
|
||||
token.EOF, "", 27,
|
||||
)
|
||||
|
||||
test("abc = !false",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.ASSIGN, "", 5,
|
||||
token.NOT, "", 7,
|
||||
token.BOOLEAN, "false", 8,
|
||||
token.EOF, "", 13,
|
||||
)
|
||||
|
||||
test("abc = !!true",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.ASSIGN, "", 5,
|
||||
token.NOT, "", 7,
|
||||
token.NOT, "", 8,
|
||||
token.BOOLEAN, "true", 9,
|
||||
token.EOF, "", 13,
|
||||
)
|
||||
|
||||
test("abc *= 1",
|
||||
token.IDENTIFIER, "abc", 1,
|
||||
token.MULTIPLY_ASSIGN, "", 5,
|
||||
token.NUMBER, "1", 8,
|
||||
token.EOF, "", 9,
|
||||
)
|
||||
|
||||
test("if 1 else",
|
||||
token.IF, "if", 1,
|
||||
token.NUMBER, "1", 4,
|
||||
token.ELSE, "else", 6,
|
||||
token.EOF, "", 10,
|
||||
)
|
||||
|
||||
test("null",
|
||||
token.NULL, "null", 1,
|
||||
token.EOF, "", 5,
|
||||
)
|
||||
|
||||
test(`"\u007a\x79\u000a\x78"`,
|
||||
token.STRING, "\"\\u007a\\x79\\u000a\\x78\"", 1,
|
||||
token.EOF, "", 23,
|
||||
)
|
||||
|
||||
test(`"[First line \
|
||||
Second line \
|
||||
Third line\
|
||||
. ]"
|
||||
`,
|
||||
token.STRING, "\"[First line \\\nSecond line \\\n Third line\\\n. ]\"", 1,
|
||||
token.EOF, "", 53,
|
||||
)
|
||||
|
||||
test("/",
|
||||
token.SLASH, "", 1,
|
||||
token.EOF, "", 2,
|
||||
)
|
||||
|
||||
test("var abc = \"abc\uFFFFabc\"",
|
||||
token.VAR, "var", 1,
|
||||
token.IDENTIFIER, "abc", 5,
|
||||
token.ASSIGN, "", 9,
|
||||
token.STRING, "\"abc\uFFFFabc\"", 11,
|
||||
token.EOF, "", 22,
|
||||
)
|
||||
|
||||
test(`'\t' === '\r'`,
|
||||
token.STRING, "'\\t'", 1,
|
||||
token.STRICT_EQUAL, "", 6,
|
||||
token.STRING, "'\\r'", 10,
|
||||
token.EOF, "", 14,
|
||||
)
|
||||
|
||||
test(`var \u0024 = 1`,
|
||||
token.VAR, "var", 1,
|
||||
token.IDENTIFIER, "\\u0024", 5,
|
||||
token.ASSIGN, "", 12,
|
||||
token.NUMBER, "1", 14,
|
||||
token.EOF, "", 15,
|
||||
)
|
||||
|
||||
test("10e10000",
|
||||
token.NUMBER, "10e10000", 1,
|
||||
token.EOF, "", 9,
|
||||
)
|
||||
|
||||
test(`var if var class`,
|
||||
token.VAR, "var", 1,
|
||||
token.IF, "if", 5,
|
||||
token.VAR, "var", 8,
|
||||
token.CLASS, "class", 12,
|
||||
token.EOF, "", 17,
|
||||
)
|
||||
|
||||
test(`-0`,
|
||||
token.MINUS, "", 1,
|
||||
token.NUMBER, "0", 2,
|
||||
token.EOF, "", 3,
|
||||
)
|
||||
|
||||
test(`.01`,
|
||||
token.NUMBER, ".01", 1,
|
||||
token.EOF, "", 4,
|
||||
)
|
||||
|
||||
test(`.01e+2`,
|
||||
token.NUMBER, ".01e+2", 1,
|
||||
token.EOF, "", 7,
|
||||
)
|
||||
|
||||
test(";",
|
||||
token.SEMICOLON, "", 1,
|
||||
token.EOF, "", 2,
|
||||
)
|
||||
|
||||
test(";;",
|
||||
token.SEMICOLON, "", 1,
|
||||
token.SEMICOLON, "", 2,
|
||||
token.EOF, "", 3,
|
||||
)
|
||||
|
||||
test("//",
|
||||
token.EOF, "", 3,
|
||||
)
|
||||
|
||||
test(";;//",
|
||||
token.SEMICOLON, "", 1,
|
||||
token.SEMICOLON, "", 2,
|
||||
token.EOF, "", 5,
|
||||
)
|
||||
|
||||
test("1",
|
||||
token.NUMBER, "1", 1,
|
||||
)
|
||||
|
||||
test("12 123",
|
||||
token.NUMBER, "12", 1,
|
||||
token.NUMBER, "123", 4,
|
||||
)
|
||||
|
||||
test("1.2 12.3",
|
||||
token.NUMBER, "1.2", 1,
|
||||
token.NUMBER, "12.3", 5,
|
||||
)
|
||||
|
||||
test("/ /=",
|
||||
token.SLASH, "", 1,
|
||||
token.QUOTIENT_ASSIGN, "", 3,
|
||||
)
|
||||
|
||||
test(`"abc"`,
|
||||
token.STRING, `"abc"`, 1,
|
||||
)
|
||||
|
||||
test(`'abc'`,
|
||||
token.STRING, `'abc'`, 1,
|
||||
)
|
||||
|
||||
test("++",
|
||||
token.INCREMENT, "", 1,
|
||||
)
|
||||
|
||||
test(">",
|
||||
token.GREATER, "", 1,
|
||||
)
|
||||
|
||||
test(">=",
|
||||
token.GREATER_OR_EQUAL, "", 1,
|
||||
)
|
||||
|
||||
test(">>",
|
||||
token.SHIFT_RIGHT, "", 1,
|
||||
)
|
||||
|
||||
test(">>=",
|
||||
token.SHIFT_RIGHT_ASSIGN, "", 1,
|
||||
)
|
||||
|
||||
test(">>>",
|
||||
token.UNSIGNED_SHIFT_RIGHT, "", 1,
|
||||
)
|
||||
|
||||
test(">>>=",
|
||||
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
|
||||
)
|
||||
|
||||
test("1 \"abc\"",
|
||||
token.NUMBER, "1", 1,
|
||||
token.STRING, "\"abc\"", 3,
|
||||
)
|
||||
|
||||
test(",",
|
||||
token.COMMA, "", 1,
|
||||
)
|
||||
|
||||
test("1, \"abc\"",
|
||||
token.NUMBER, "1", 1,
|
||||
token.COMMA, "", 2,
|
||||
token.STRING, "\"abc\"", 4,
|
||||
)
|
||||
|
||||
test("new abc(1, 3.14159);",
|
||||
token.NEW, "new", 1,
|
||||
token.IDENTIFIER, "abc", 5,
|
||||
token.LEFT_PARENTHESIS, "", 8,
|
||||
token.NUMBER, "1", 9,
|
||||
token.COMMA, "", 10,
|
||||
token.NUMBER, "3.14159", 12,
|
||||
token.RIGHT_PARENTHESIS, "", 19,
|
||||
token.SEMICOLON, "", 20,
|
||||
)
|
||||
|
||||
test("1 == \"1\"",
|
||||
token.NUMBER, "1", 1,
|
||||
token.EQUAL, "", 3,
|
||||
token.STRING, "\"1\"", 6,
|
||||
)
|
||||
|
||||
test("1\n[]\n",
|
||||
token.NUMBER, "1", 1,
|
||||
token.LEFT_BRACKET, "", 3,
|
||||
token.RIGHT_BRACKET, "", 4,
|
||||
)
|
||||
|
||||
test("1\ufeff[]\ufeff",
|
||||
token.NUMBER, "1", 1,
|
||||
token.LEFT_BRACKET, "", 5,
|
||||
token.RIGHT_BRACKET, "", 6,
|
||||
)
|
||||
|
||||
test("x ?.30 : false",
|
||||
token.IDENTIFIER, "x", 1,
|
||||
token.QUESTION_MARK, "", 3,
|
||||
token.NUMBER, ".30", 4,
|
||||
token.COLON, "", 8,
|
||||
token.BOOLEAN, "false", 10,
|
||||
)
|
||||
|
||||
test("a\n?.b",
|
||||
token.IDENTIFIER, "a", 1,
|
||||
token.QUESTION_DOT, "", 3,
|
||||
token.IDENTIFIER, "b", 5,
|
||||
)
|
||||
|
||||
// ILLEGAL
|
||||
|
||||
test(`3ea`,
|
||||
token.ILLEGAL, "3e", 1,
|
||||
token.IDENTIFIER, "a", 3,
|
||||
token.EOF, "", 4,
|
||||
)
|
||||
|
||||
test(`3in`,
|
||||
token.ILLEGAL, "3", 1,
|
||||
token.IN, "in", 2,
|
||||
token.EOF, "", 4,
|
||||
)
|
||||
|
||||
test("\"Hello\nWorld\"",
|
||||
token.ILLEGAL, "", 1,
|
||||
token.IDENTIFIER, "World", 8,
|
||||
token.ILLEGAL, "", 13,
|
||||
token.EOF, "", 14,
|
||||
)
|
||||
|
||||
test("\u203f = 10",
|
||||
token.ILLEGAL, "", 1,
|
||||
token.ASSIGN, "", 5,
|
||||
token.NUMBER, "10", 7,
|
||||
token.EOF, "", 9,
|
||||
)
|
||||
|
||||
test(`"\x0G"`,
|
||||
token.ILLEGAL, "\"\\x0G\"", 1,
|
||||
//token.STRING, "\"\\x0G\"", 1,
|
||||
token.EOF, "", 7,
|
||||
)
|
||||
|
||||
})
|
||||
}
|
||||
890
common/utils/goja/parser/marshal_test.go
Normal file
890
common/utils/goja/parser/marshal_test.go
Normal file
@@ -0,0 +1,890 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
)
|
||||
|
||||
func marshal(name string, children ...interface{}) interface{} {
|
||||
if len(children) == 1 {
|
||||
if name == "" {
|
||||
return testMarshalNode(children[0])
|
||||
}
|
||||
return map[string]interface{}{
|
||||
name: children[0],
|
||||
}
|
||||
}
|
||||
map_ := map[string]interface{}{}
|
||||
length := len(children) / 2
|
||||
for i := 0; i < length; i++ {
|
||||
name := children[i*2].(string)
|
||||
value := children[i*2+1]
|
||||
map_[name] = value
|
||||
}
|
||||
if name == "" {
|
||||
return map_
|
||||
}
|
||||
return map[string]interface{}{
|
||||
name: map_,
|
||||
}
|
||||
}
|
||||
|
||||
func testMarshalNode(node interface{}) interface{} {
|
||||
switch node := node.(type) {
|
||||
|
||||
// Expression
|
||||
|
||||
case *ast.ArrayLiteral:
|
||||
return marshal("Array", testMarshalNode(node.Value))
|
||||
|
||||
case *ast.AssignExpression:
|
||||
return marshal("Assign",
|
||||
"Left", testMarshalNode(node.Left),
|
||||
"Right", testMarshalNode(node.Right),
|
||||
)
|
||||
|
||||
case *ast.BinaryExpression:
|
||||
return marshal("BinaryExpression",
|
||||
"Operator", node.Operator.String(),
|
||||
"Left", testMarshalNode(node.Left),
|
||||
"Right", testMarshalNode(node.Right),
|
||||
)
|
||||
|
||||
case *ast.BooleanLiteral:
|
||||
return marshal("Literal", node.Value)
|
||||
|
||||
case *ast.CallExpression:
|
||||
return marshal("Call",
|
||||
"Callee", testMarshalNode(node.Callee),
|
||||
"ArgumentList", testMarshalNode(node.ArgumentList),
|
||||
)
|
||||
|
||||
case *ast.ConditionalExpression:
|
||||
return marshal("Conditional",
|
||||
"Test", testMarshalNode(node.Test),
|
||||
"Consequent", testMarshalNode(node.Consequent),
|
||||
"Alternate", testMarshalNode(node.Alternate),
|
||||
)
|
||||
|
||||
case *ast.DotExpression:
|
||||
return marshal("Dot",
|
||||
"Left", testMarshalNode(node.Left),
|
||||
"Member", node.Identifier.Name,
|
||||
)
|
||||
|
||||
case *ast.NewExpression:
|
||||
return marshal("New",
|
||||
"Callee", testMarshalNode(node.Callee),
|
||||
"ArgumentList", testMarshalNode(node.ArgumentList),
|
||||
)
|
||||
|
||||
case *ast.NullLiteral:
|
||||
return marshal("Literal", nil)
|
||||
|
||||
case *ast.NumberLiteral:
|
||||
return marshal("Literal", node.Value)
|
||||
|
||||
case *ast.ObjectLiteral:
|
||||
return marshal("Object", testMarshalNode(node.Value))
|
||||
|
||||
case *ast.RegExpLiteral:
|
||||
return marshal("Literal", node.Literal)
|
||||
|
||||
case *ast.StringLiteral:
|
||||
return marshal("Literal", node.Literal)
|
||||
|
||||
case *ast.Binding:
|
||||
return marshal("Binding", "Target", testMarshalNode(node.Target),
|
||||
"Initializer", testMarshalNode(node.Initializer))
|
||||
|
||||
// Statement
|
||||
|
||||
case *ast.Program:
|
||||
return testMarshalNode(node.Body)
|
||||
|
||||
case *ast.BlockStatement:
|
||||
return marshal("BlockStatement", testMarshalNode(node.List))
|
||||
|
||||
case *ast.EmptyStatement:
|
||||
return "EmptyStatement"
|
||||
|
||||
case *ast.ExpressionStatement:
|
||||
return testMarshalNode(node.Expression)
|
||||
|
||||
case *ast.ForInStatement:
|
||||
return marshal("ForIn",
|
||||
"Into", testMarshalNode(node.Into),
|
||||
"Source", marshal("", node.Source),
|
||||
"Body", marshal("", node.Body),
|
||||
)
|
||||
|
||||
case *ast.FunctionLiteral:
|
||||
return marshal("Function", testMarshalNode(node.Body))
|
||||
|
||||
case *ast.Identifier:
|
||||
return marshal("Identifier", node.Name)
|
||||
|
||||
case *ast.IfStatement:
|
||||
if_ := marshal("",
|
||||
"Test", testMarshalNode(node.Test),
|
||||
"Consequent", testMarshalNode(node.Consequent),
|
||||
).(map[string]interface{})
|
||||
if node.Alternate != nil {
|
||||
if_["Alternate"] = testMarshalNode(node.Alternate)
|
||||
}
|
||||
return marshal("If", if_)
|
||||
|
||||
case *ast.LabelledStatement:
|
||||
return marshal("Label",
|
||||
"Name", node.Label.Name,
|
||||
"Statement", testMarshalNode(node.Statement),
|
||||
)
|
||||
case *ast.PropertyKeyed:
|
||||
return marshal("",
|
||||
"Key", node.Key,
|
||||
"Value", testMarshalNode(node.Value),
|
||||
)
|
||||
|
||||
case *ast.ReturnStatement:
|
||||
return marshal("Return", testMarshalNode(node.Argument))
|
||||
|
||||
case *ast.SequenceExpression:
|
||||
return marshal("Sequence", testMarshalNode(node.Sequence))
|
||||
|
||||
case *ast.ThrowStatement:
|
||||
return marshal("Throw", testMarshalNode(node.Argument))
|
||||
|
||||
case *ast.VariableStatement:
|
||||
return marshal("Var", testMarshalNode(node.List))
|
||||
|
||||
// Special
|
||||
case *ast.ForDeclaration:
|
||||
return marshal("For-Into-Decl", testMarshalNode(node.Target))
|
||||
|
||||
case *ast.ForIntoVar:
|
||||
return marshal("For-Into-Var", testMarshalNode(node.Binding))
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
value := reflect.ValueOf(node)
|
||||
if value.Kind() == reflect.Slice {
|
||||
tmp0 := []interface{}{}
|
||||
for index := 0; index < value.Len(); index++ {
|
||||
tmp0 = append(tmp0, testMarshalNode(value.Index(index).Interface()))
|
||||
}
|
||||
return tmp0
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testMarshal(node interface{}) string {
|
||||
value, err := json.Marshal(testMarshalNode(node))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(value)
|
||||
}
|
||||
|
||||
func TestParserAST(t *testing.T) {
|
||||
tt(t, func() {
|
||||
|
||||
test := func(inputOutput string) {
|
||||
match := matchBeforeAfterSeparator.FindStringIndex(inputOutput)
|
||||
input := strings.TrimSpace(inputOutput[0:match[0]])
|
||||
wantOutput := strings.TrimSpace(inputOutput[match[1]:])
|
||||
_, program, err := testParse(input)
|
||||
is(err, nil)
|
||||
haveOutput := testMarshal(program)
|
||||
tmp0, tmp1 := bytes.Buffer{}, bytes.Buffer{}
|
||||
json.Indent(&tmp0, []byte(haveOutput), "\t\t", " ")
|
||||
json.Indent(&tmp1, []byte(wantOutput), "\t\t", " ")
|
||||
is("\n\t\t"+tmp0.String(), "\n\t\t"+tmp1.String())
|
||||
}
|
||||
|
||||
test(`
|
||||
---
|
||||
[]
|
||||
`)
|
||||
|
||||
test(`
|
||||
;
|
||||
---
|
||||
[
|
||||
"EmptyStatement"
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
;;;
|
||||
---
|
||||
[
|
||||
"EmptyStatement",
|
||||
"EmptyStatement",
|
||||
"EmptyStatement"
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
1; true; abc; "abc"; null;
|
||||
---
|
||||
[
|
||||
{
|
||||
"Literal": 1
|
||||
},
|
||||
{
|
||||
"Literal": true
|
||||
},
|
||||
{
|
||||
"Identifier": "abc"
|
||||
},
|
||||
{
|
||||
"Literal": "\"abc\""
|
||||
},
|
||||
{
|
||||
"Literal": null
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
{ 1; null; 3.14159; ; }
|
||||
---
|
||||
[
|
||||
{
|
||||
"BlockStatement": [
|
||||
{
|
||||
"Literal": 1
|
||||
},
|
||||
{
|
||||
"Literal": null
|
||||
},
|
||||
{
|
||||
"Literal": 3.14159
|
||||
},
|
||||
"EmptyStatement"
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
new abc();
|
||||
---
|
||||
[
|
||||
{
|
||||
"New": {
|
||||
"ArgumentList": [],
|
||||
"Callee": {
|
||||
"Identifier": "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
new abc(1, 3.14159)
|
||||
---
|
||||
[
|
||||
{
|
||||
"New": {
|
||||
"ArgumentList": [
|
||||
{
|
||||
"Literal": 1
|
||||
},
|
||||
{
|
||||
"Literal": 3.14159
|
||||
}
|
||||
],
|
||||
"Callee": {
|
||||
"Identifier": "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
true ? false : true
|
||||
---
|
||||
[
|
||||
{
|
||||
"Conditional": {
|
||||
"Alternate": {
|
||||
"Literal": true
|
||||
},
|
||||
"Consequent": {
|
||||
"Literal": false
|
||||
},
|
||||
"Test": {
|
||||
"Literal": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
true || false
|
||||
---
|
||||
[
|
||||
{
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"Literal": true
|
||||
},
|
||||
"Operator": "||",
|
||||
"Right": {
|
||||
"Literal": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
0 + { abc: true }
|
||||
---
|
||||
[
|
||||
{
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"Literal": 0
|
||||
},
|
||||
"Operator": "+",
|
||||
"Right": {
|
||||
"Object": [
|
||||
{
|
||||
"Key": {
|
||||
"Idx": 7,
|
||||
"Literal": "abc",
|
||||
"Value": "abc"
|
||||
},
|
||||
"Value": {
|
||||
"Literal": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
1 == "1"
|
||||
---
|
||||
[
|
||||
{
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"Literal": 1
|
||||
},
|
||||
"Operator": "==",
|
||||
"Right": {
|
||||
"Literal": "\"1\""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
abc(1)
|
||||
---
|
||||
[
|
||||
{
|
||||
"Call": {
|
||||
"ArgumentList": [
|
||||
{
|
||||
"Literal": 1
|
||||
}
|
||||
],
|
||||
"Callee": {
|
||||
"Identifier": "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
Math.pow(3, 2)
|
||||
---
|
||||
[
|
||||
{
|
||||
"Call": {
|
||||
"ArgumentList": [
|
||||
{
|
||||
"Literal": 3
|
||||
},
|
||||
{
|
||||
"Literal": 2
|
||||
}
|
||||
],
|
||||
"Callee": {
|
||||
"Dot": {
|
||||
"Left": {
|
||||
"Identifier": "Math"
|
||||
},
|
||||
"Member": "pow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
1, 2, 3
|
||||
---
|
||||
[
|
||||
{
|
||||
"Sequence": [
|
||||
{
|
||||
"Literal": 1
|
||||
},
|
||||
{
|
||||
"Literal": 2
|
||||
},
|
||||
{
|
||||
"Literal": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
/ abc /gim;
|
||||
---
|
||||
[
|
||||
{
|
||||
"Literal": "/ abc /gim"
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
if (0)
|
||||
1;
|
||||
---
|
||||
[
|
||||
{
|
||||
"If": {
|
||||
"Consequent": {
|
||||
"Literal": 1
|
||||
},
|
||||
"Test": {
|
||||
"Literal": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
0+function(){
|
||||
return;
|
||||
}
|
||||
---
|
||||
[
|
||||
{
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"Literal": 0
|
||||
},
|
||||
"Operator": "+",
|
||||
"Right": {
|
||||
"Function": {
|
||||
"BlockStatement": [
|
||||
{
|
||||
"Return": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
xyzzy // Ignore it
|
||||
// Ignore this
|
||||
// And this
|
||||
/* And all..
|
||||
|
||||
|
||||
|
||||
... of this!
|
||||
*/
|
||||
"Nothing happens."
|
||||
// And finally this
|
||||
---
|
||||
[
|
||||
{
|
||||
"Identifier": "xyzzy"
|
||||
},
|
||||
{
|
||||
"Literal": "\"Nothing happens.\""
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
((x & (x = 1)) !== 0)
|
||||
---
|
||||
[
|
||||
{
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"Identifier": "x"
|
||||
},
|
||||
"Operator": "\u0026",
|
||||
"Right": {
|
||||
"Assign": {
|
||||
"Left": {
|
||||
"Identifier": "x"
|
||||
},
|
||||
"Right": {
|
||||
"Literal": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Operator": "!==",
|
||||
"Right": {
|
||||
"Literal": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
{ abc: 'def' }
|
||||
---
|
||||
[
|
||||
{
|
||||
"BlockStatement": [
|
||||
{
|
||||
"Label": {
|
||||
"Name": "abc",
|
||||
"Statement": {
|
||||
"Literal": "'def'"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
// This is not an object, this is a string literal with a label!
|
||||
({ abc: 'def' })
|
||||
---
|
||||
[
|
||||
{
|
||||
"Object": [
|
||||
{
|
||||
"Key": {
|
||||
"Idx": 77,
|
||||
"Literal": "abc",
|
||||
"Value": "abc"
|
||||
},
|
||||
"Value": {
|
||||
"Literal": "'def'"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
[,]
|
||||
---
|
||||
[
|
||||
{
|
||||
"Array": [
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
[,,]
|
||||
---
|
||||
[
|
||||
{
|
||||
"Array": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
({ get abc() {} })
|
||||
---
|
||||
[
|
||||
{
|
||||
"Object": [
|
||||
{
|
||||
"Key": {
|
||||
"Idx": 8,
|
||||
"Literal": "abc",
|
||||
"Value": "abc"
|
||||
},
|
||||
"Value": {
|
||||
"Function": {
|
||||
"BlockStatement": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
/abc/.source
|
||||
---
|
||||
[
|
||||
{
|
||||
"Dot": {
|
||||
"Left": {
|
||||
"Literal": "/abc/"
|
||||
},
|
||||
"Member": "source"
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
xyzzy
|
||||
|
||||
throw new TypeError("Nothing happens.")
|
||||
---
|
||||
[
|
||||
{
|
||||
"Identifier": "xyzzy"
|
||||
},
|
||||
{
|
||||
"Throw": {
|
||||
"New": {
|
||||
"ArgumentList": [
|
||||
{
|
||||
"Literal": "\"Nothing happens.\""
|
||||
}
|
||||
],
|
||||
"Callee": {
|
||||
"Identifier": "TypeError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
// When run, this will call a type error to be thrown
|
||||
// This is essentially the same as:
|
||||
//
|
||||
// var abc = 1(function(){})()
|
||||
//
|
||||
test(`
|
||||
var abc = 1
|
||||
(function(){
|
||||
})()
|
||||
---
|
||||
[
|
||||
{
|
||||
"Var": [
|
||||
{
|
||||
"Binding": {
|
||||
"Initializer": {
|
||||
"Call": {
|
||||
"ArgumentList": [],
|
||||
"Callee": {
|
||||
"Call": {
|
||||
"ArgumentList": [
|
||||
{
|
||||
"Function": {
|
||||
"BlockStatement": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"Callee": {
|
||||
"Literal": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Target": {
|
||||
"Identifier": "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
"use strict"
|
||||
---
|
||||
[
|
||||
{
|
||||
"Literal": "\"use strict\""
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
"use strict"
|
||||
abc = 1 + 2 + 11
|
||||
---
|
||||
[
|
||||
{
|
||||
"Literal": "\"use strict\""
|
||||
},
|
||||
{
|
||||
"Assign": {
|
||||
"Left": {
|
||||
"Identifier": "abc"
|
||||
},
|
||||
"Right": {
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"BinaryExpression": {
|
||||
"Left": {
|
||||
"Literal": 1
|
||||
},
|
||||
"Operator": "+",
|
||||
"Right": {
|
||||
"Literal": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
"Operator": "+",
|
||||
"Right": {
|
||||
"Literal": 11
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
abc = function() { 'use strict' }
|
||||
---
|
||||
[
|
||||
{
|
||||
"Assign": {
|
||||
"Left": {
|
||||
"Identifier": "abc"
|
||||
},
|
||||
"Right": {
|
||||
"Function": {
|
||||
"BlockStatement": [
|
||||
{
|
||||
"Literal": "'use strict'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
for (var abc in def) {
|
||||
}
|
||||
---
|
||||
[
|
||||
{
|
||||
"ForIn": {
|
||||
"Body": {
|
||||
"BlockStatement": []
|
||||
},
|
||||
"Into": {
|
||||
"For-Into-Var": {
|
||||
"Binding": {
|
||||
"Initializer": null,
|
||||
"Target": {
|
||||
"Identifier": "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Source": {
|
||||
"Identifier": "def"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
test(`
|
||||
abc = {
|
||||
'"': "'",
|
||||
"'": '"',
|
||||
}
|
||||
---
|
||||
[
|
||||
{
|
||||
"Assign": {
|
||||
"Left": {
|
||||
"Identifier": "abc"
|
||||
},
|
||||
"Right": {
|
||||
"Object": [
|
||||
{
|
||||
"Key": {
|
||||
"Idx": 21,
|
||||
"Literal": "'\"'",
|
||||
"Value": "\""
|
||||
},
|
||||
"Value": {
|
||||
"Literal": "\"'\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"Key": {
|
||||
"Idx": 43,
|
||||
"Literal": "\"'\"",
|
||||
"Value": "'"
|
||||
},
|
||||
"Value": {
|
||||
"Literal": "'\"'"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
`)
|
||||
|
||||
})
|
||||
}
|
||||
268
common/utils/goja/parser/parser.go
Normal file
268
common/utils/goja/parser/parser.go
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
Package parser implements a parser for JavaScript.
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/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"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/file"
|
||||
"github.com/dop251/goja/token"
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
// 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)
|
||||
)
|
||||
|
||||
type options struct {
|
||||
disableSourceMaps bool
|
||||
sourceMapLoader func(path string) ([]byte, error)
|
||||
}
|
||||
|
||||
// Option represents one of the options for the parser to use in the Parse methods. Currently supported are:
|
||||
// WithDisableSourceMaps and WithSourceMapLoader.
|
||||
type Option func(*options)
|
||||
|
||||
// WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps
|
||||
// are not in use.
|
||||
func WithDisableSourceMaps(opts *options) {
|
||||
opts.disableSourceMaps = true
|
||||
}
|
||||
|
||||
// WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a
|
||||
// URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name
|
||||
// of the file being parsed. Any error returned by the loader will fail the parsing.
|
||||
// Note that setting this to nil does not disable source map support, there is a default loader which reads
|
||||
// from the filesystem. Use WithDisableSourceMaps to disable source map support.
|
||||
func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option {
|
||||
return func(opts *options) {
|
||||
opts.sourceMapLoader = loader
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
parsedLiteral unistring.String
|
||||
|
||||
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
|
||||
opts options
|
||||
|
||||
file *file.File
|
||||
}
|
||||
|
||||
func _newParser(filename, src string, base int, opts ...Option) *_parser {
|
||||
p := &_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),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&p.opts)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func newParser(filename, src string) *_parser {
|
||||
return _newParser(filename, src, 1)
|
||||
}
|
||||
|
||||
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 os.ReadFile(filename)
|
||||
}
|
||||
|
||||
// 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, options ...Option) (*ast.Program, error) {
|
||||
str, err := ReadSource(filename, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
{
|
||||
str := string(str)
|
||||
|
||||
base := 1
|
||||
if fileSet != nil {
|
||||
base = fileSet.AddFile(filename, str)
|
||||
}
|
||||
|
||||
parser := _newParser(filename, str, base, options...)
|
||||
parser.mode = mode
|
||||
return parser.parse()
|
||||
}
|
||||
}
|
||||
|
||||
// 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, options ...Option) (*ast.FunctionLiteral, error) {
|
||||
|
||||
src := "(function(" + parameterList + ") {\n" + body + "\n})"
|
||||
|
||||
parser := _newParser("", src, 1, options...)
|
||||
program, err := parser.parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
|
||||
}
|
||||
|
||||
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.openScope()
|
||||
defer self.closeScope()
|
||||
self.next()
|
||||
program := self.parseProgram()
|
||||
if false {
|
||||
self.errors.Sort()
|
||||
}
|
||||
return program, self.errors.Err()
|
||||
}
|
||||
|
||||
func (self *_parser) next() {
|
||||
self.token, self.literal, self.parsedLiteral, 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 (self *_parser) position(idx file.Idx) file.Position {
|
||||
return self.file.Position(int(idx) - self.base)
|
||||
}
|
||||
1321
common/utils/goja/parser/parser_test.go
Normal file
1321
common/utils/goja/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load Diff
458
common/utils/goja/parser/regexp.go
Normal file
458
common/utils/goja/parser/regexp.go
Normal file
@@ -0,0 +1,458 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
WhitespaceChars = " \f\n\r\t\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff"
|
||||
Re2Dot = "[^\r\n\u2028\u2029]"
|
||||
)
|
||||
|
||||
type regexpParseError struct {
|
||||
offset int
|
||||
err string
|
||||
}
|
||||
|
||||
type RegexpErrorIncompatible struct {
|
||||
regexpParseError
|
||||
}
|
||||
type RegexpSyntaxError struct {
|
||||
regexpParseError
|
||||
}
|
||||
|
||||
func (s regexpParseError) Error() string {
|
||||
return s.err
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err error
|
||||
|
||||
goRegexp strings.Builder
|
||||
passOffset int
|
||||
}
|
||||
|
||||
// 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 valid, but incompatible (contains a lookahead or backreference),
|
||||
// then this function returns an empty string an error of type RegexpErrorIncompatible.
|
||||
//
|
||||
// If the pattern is invalid (not valid even in JavaScript), then this function
|
||||
// returns an empty string and a generic error.
|
||||
func TransformRegExp(pattern string) (transformed string, err error) {
|
||||
|
||||
if pattern == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parser := _RegExp_parser{
|
||||
str: pattern,
|
||||
length: len(pattern),
|
||||
}
|
||||
err = parser.parse()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return parser.ResultString(), nil
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) ResultString() string {
|
||||
if self.passOffset != -1 {
|
||||
return self.str[:self.passOffset]
|
||||
}
|
||||
return self.goRegexp.String()
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) parse() (err error) {
|
||||
self.read() // Pull in the first character
|
||||
self.scan()
|
||||
return self.err
|
||||
}
|
||||
|
||||
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(true, "Invalid UTF-8 character")
|
||||
return
|
||||
}
|
||||
}
|
||||
self.offset += width
|
||||
self.chr = chr
|
||||
} else {
|
||||
self.chrOffset = self.length
|
||||
self.chr = -1 // EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) stopPassing() {
|
||||
self.goRegexp.Grow(3 * len(self.str) / 2)
|
||||
self.goRegexp.WriteString(self.str[:self.passOffset])
|
||||
self.passOffset = -1
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) write(p []byte) {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.Write(p)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) writeByte(b byte) {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.WriteByte(b)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) writeString(s string) {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.WriteString(s)
|
||||
}
|
||||
|
||||
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.scanBracket()
|
||||
case ')':
|
||||
self.error(true, "Unmatched ')'")
|
||||
return
|
||||
case '.':
|
||||
self.writeString(Re2Dot)
|
||||
self.read()
|
||||
default:
|
||||
self.pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (...)
|
||||
func (self *_RegExp_parser) scanGroup() {
|
||||
str := self.str[self.chrOffset:]
|
||||
if len(str) > 1 { // A possibility of (?= or (?!
|
||||
if str[0] == '?' {
|
||||
ch := str[1]
|
||||
switch {
|
||||
case ch == '=' || ch == '!':
|
||||
self.error(false, "re2: Invalid (%s) <lookahead>", self.str[self.chrOffset:self.chrOffset+2])
|
||||
return
|
||||
case ch == '<':
|
||||
self.error(false, "re2: Invalid (%s) <lookbehind>", self.str[self.chrOffset:self.chrOffset+2])
|
||||
return
|
||||
case ch != ':':
|
||||
self.error(true, "Invalid group")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for self.chr != -1 && self.chr != ')' {
|
||||
switch self.chr {
|
||||
case '\\':
|
||||
self.read()
|
||||
self.scanEscape(false)
|
||||
case '(':
|
||||
self.pass()
|
||||
self.scanGroup()
|
||||
case '[':
|
||||
self.scanBracket()
|
||||
case '.':
|
||||
self.writeString(Re2Dot)
|
||||
self.read()
|
||||
default:
|
||||
self.pass()
|
||||
continue
|
||||
}
|
||||
}
|
||||
if self.chr != ')' {
|
||||
self.error(true, "Unterminated group")
|
||||
return
|
||||
}
|
||||
self.pass()
|
||||
}
|
||||
|
||||
// [...]
|
||||
func (self *_RegExp_parser) scanBracket() {
|
||||
str := self.str[self.chrOffset:]
|
||||
if strings.HasPrefix(str, "[]") {
|
||||
// [] -- Empty character class
|
||||
self.writeString("[^\u0000-\U0001FFFF]")
|
||||
self.offset += 1
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(str, "[^]") {
|
||||
self.writeString("[\u0000-\U0001FFFF]")
|
||||
self.offset += 2
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
|
||||
self.pass()
|
||||
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(true, "Unterminated character class")
|
||||
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
|
||||
if value != 0 {
|
||||
// An invalid backreference
|
||||
self.error(false, "re2: Invalid \\%d <backreference>", value)
|
||||
return
|
||||
}
|
||||
self.passString(offset-1, self.chrOffset)
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
self.write(tmp)
|
||||
return
|
||||
|
||||
case '8', '9':
|
||||
self.read()
|
||||
self.error(false, "re2: Invalid \\%s <backreference>", self.str[offset:self.chrOffset])
|
||||
return
|
||||
|
||||
case 'x':
|
||||
self.read()
|
||||
length, base = 2, 16
|
||||
|
||||
case 'u':
|
||||
self.read()
|
||||
if self.chr == '{' {
|
||||
self.read()
|
||||
length, base = 0, 16
|
||||
} else {
|
||||
length, base = 4, 16
|
||||
}
|
||||
|
||||
case 'b':
|
||||
if inClass {
|
||||
self.write([]byte{'\\', 'x', '0', '8'})
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case 'B':
|
||||
fallthrough
|
||||
|
||||
case 'd', 'D', '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':
|
||||
self.passString(offset-1, self.offset)
|
||||
self.read()
|
||||
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 {
|
||||
self.writeByte('c')
|
||||
return
|
||||
}
|
||||
tmp := []byte{'\\', 'x', '0', 0}
|
||||
if value >= 16 {
|
||||
tmp = tmp[0:2]
|
||||
} else {
|
||||
tmp = tmp[0:3]
|
||||
}
|
||||
tmp = strconv.AppendInt(tmp, value, 16)
|
||||
self.write(tmp)
|
||||
self.read()
|
||||
return
|
||||
case 's':
|
||||
if inClass {
|
||||
self.writeString(WhitespaceChars)
|
||||
} else {
|
||||
self.writeString("[" + WhitespaceChars + "]")
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
case 'S':
|
||||
if inClass {
|
||||
self.error(false, "S in class")
|
||||
return
|
||||
} else {
|
||||
self.writeString("[^" + WhitespaceChars + "]")
|
||||
}
|
||||
self.read()
|
||||
return
|
||||
default:
|
||||
// $ is an identifier character, so we have to have
|
||||
// a special case for it here
|
||||
if self.chr == '$' || self.chr < utf8.RuneSelf && !isIdentifierPart(self.chr) {
|
||||
// A non-identifier character needs escaping
|
||||
self.passString(offset-1, self.offset)
|
||||
self.read()
|
||||
return
|
||||
}
|
||||
// Unescape the character for re2
|
||||
self.pass()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we're a \u.... or \x...
|
||||
valueOffset := self.chrOffset
|
||||
|
||||
if length > 0 {
|
||||
for length := length; length > 0; length-- {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
// Not a valid digit
|
||||
goto skip
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
} else {
|
||||
for self.chr != '}' && self.chr != -1 {
|
||||
digit := uint32(digitValue(self.chr))
|
||||
if digit >= base {
|
||||
// Not a valid digit
|
||||
goto skip
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
if length == 4 || length == 0 {
|
||||
self.write([]byte{
|
||||
'\\',
|
||||
'x',
|
||||
'{',
|
||||
})
|
||||
self.passString(valueOffset, self.chrOffset)
|
||||
if length != 0 {
|
||||
self.writeByte('}')
|
||||
}
|
||||
} else if length == 2 {
|
||||
self.passString(offset-1, valueOffset+2)
|
||||
} else {
|
||||
// Should never, ever get here...
|
||||
self.error(true, "re2: Illegal branch in scanEscape")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
skip:
|
||||
self.passString(offset, self.chrOffset)
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) pass() {
|
||||
if self.passOffset == self.chrOffset {
|
||||
self.passOffset = self.offset
|
||||
} else {
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
if self.chr != -1 {
|
||||
self.goRegexp.WriteRune(self.chr)
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) passString(start, end int) {
|
||||
if self.passOffset == start {
|
||||
self.passOffset = end
|
||||
return
|
||||
}
|
||||
if self.passOffset != -1 {
|
||||
self.stopPassing()
|
||||
}
|
||||
self.goRegexp.WriteString(self.str[start:end])
|
||||
}
|
||||
|
||||
func (self *_RegExp_parser) error(fatal bool, msg string, msgValues ...interface{}) {
|
||||
if self.err != nil {
|
||||
return
|
||||
}
|
||||
e := regexpParseError{
|
||||
offset: self.offset,
|
||||
err: fmt.Sprintf(msg, msgValues...),
|
||||
}
|
||||
if fatal {
|
||||
self.err = RegexpSyntaxError{e}
|
||||
} else {
|
||||
self.err = RegexpErrorIncompatible{e}
|
||||
}
|
||||
self.offset = self.length
|
||||
self.chr = -1
|
||||
}
|
||||
191
common/utils/goja/parser/regexp_test.go
Normal file
191
common/utils/goja/parser/regexp_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegExp(t *testing.T) {
|
||||
tt(t, func() {
|
||||
{
|
||||
// err
|
||||
test := func(input string, expect interface{}) {
|
||||
_, err := TransformRegExp(input)
|
||||
_, incompat := err.(RegexpErrorIncompatible)
|
||||
is(incompat, false)
|
||||
is(err, expect)
|
||||
}
|
||||
|
||||
test("[", "Unterminated character class")
|
||||
|
||||
test("(", "Unterminated group")
|
||||
|
||||
test("\\(?=)", "Unmatched ')'")
|
||||
|
||||
test(")", "Unmatched ')'")
|
||||
test("0:(?)", "Invalid group")
|
||||
test("(?)", "Invalid group")
|
||||
test("(?U)", "Invalid group")
|
||||
test("(?)|(?i)", "Invalid group")
|
||||
test("(?P<w>)(?P<w>)(?P<D>)", "Invalid group")
|
||||
}
|
||||
|
||||
{
|
||||
// incompatible
|
||||
test := func(input string, expectErr interface{}) {
|
||||
_, err := TransformRegExp(input)
|
||||
_, incompat := err.(RegexpErrorIncompatible)
|
||||
is(incompat, true)
|
||||
is(err, expectErr)
|
||||
}
|
||||
|
||||
test(`<%([\s\S]+?)%>`, "S in class")
|
||||
|
||||
test("(?<=y)x", "re2: Invalid (?<) <lookbehind>")
|
||||
|
||||
test(`(?!test)`, "re2: Invalid (?!) <lookahead>")
|
||||
|
||||
test(`\1`, "re2: Invalid \\1 <backreference>")
|
||||
|
||||
test(`\8`, "re2: Invalid \\8 <backreference>")
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
// err
|
||||
test := func(input string, expect string) {
|
||||
result, err := TransformRegExp(input)
|
||||
is(err, nil)
|
||||
_, incompat := err.(RegexpErrorIncompatible)
|
||||
is(incompat, false)
|
||||
is(result, expect)
|
||||
_, err = regexp.Compile(result)
|
||||
is(err, nil)
|
||||
}
|
||||
|
||||
test("", "")
|
||||
|
||||
test("abc", "abc")
|
||||
|
||||
test(`\abc`, `abc`)
|
||||
|
||||
test(`\a\b\c`, `a\bc`)
|
||||
|
||||
test(`\x`, `x`)
|
||||
|
||||
test(`\c`, `c`)
|
||||
|
||||
test(`\cA`, `\x01`)
|
||||
|
||||
test(`\cz`, `\x1a`)
|
||||
|
||||
test(`\ca`, `\x01`)
|
||||
|
||||
test(`\cj`, `\x0a`)
|
||||
|
||||
test(`\ck`, `\x0b`)
|
||||
|
||||
test(`\+`, `\+`)
|
||||
|
||||
test(`[\b]`, `[\x08]`)
|
||||
|
||||
test(`\u0z01\x\undefined`, `u0z01xundefined`)
|
||||
|
||||
test(`\\|'|\r|\n|\t|\u2028|\u2029`, `\\|'|\r|\n|\t|\x{2028}|\x{2029}`)
|
||||
|
||||
test("]", "]")
|
||||
|
||||
test("}", "}")
|
||||
|
||||
test("%", "%")
|
||||
|
||||
test("(%)", "(%)")
|
||||
|
||||
test("(?:[%\\s])", "(?:[%"+WhitespaceChars+"])")
|
||||
|
||||
test("[[]", "[[]")
|
||||
|
||||
test("\\101", "\\x41")
|
||||
|
||||
test("\\51", "\\x29")
|
||||
|
||||
test("\\051", "\\x29")
|
||||
|
||||
test("\\175", "\\x7d")
|
||||
|
||||
test("\\0", "\\0")
|
||||
|
||||
test("\\04", "\\x04")
|
||||
|
||||
test(`(.)^`, "("+Re2Dot+")^")
|
||||
|
||||
test(`\$`, `\$`)
|
||||
|
||||
test(`[G-b]`, `[G-b]`)
|
||||
|
||||
test(`[G-b\0]`, `[G-b\0]`)
|
||||
|
||||
test(`\k`, `k`)
|
||||
|
||||
test(`\x20`, `\x20`)
|
||||
|
||||
test(`😊`, `😊`)
|
||||
|
||||
test(`^.*`, `^`+Re2Dot+`*`)
|
||||
|
||||
test(`(\n)`, `(\n)`)
|
||||
|
||||
test(`(a(bc))`, `(a(bc))`)
|
||||
|
||||
test(`[]`, "[^\u0000-\U0001FFFF]")
|
||||
|
||||
test(`[^]`, "[\u0000-\U0001FFFF]")
|
||||
|
||||
test(`\s+`, "["+WhitespaceChars+"]+")
|
||||
|
||||
test(`\S+`, "[^"+WhitespaceChars+"]+")
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTransformRegExp(t *testing.T) {
|
||||
tt(t, func() {
|
||||
pattern, err := TransformRegExp(`\s+abc\s+`)
|
||||
is(err, nil)
|
||||
is(pattern, `[`+WhitespaceChars+`]+abc[`+WhitespaceChars+`]+`)
|
||||
is(regexp.MustCompile(pattern).MatchString("\t abc def"), true)
|
||||
})
|
||||
tt(t, func() {
|
||||
pattern, err := TransformRegExp(`\u{1d306}`)
|
||||
is(err, nil)
|
||||
is(pattern, `\x{1d306}`)
|
||||
})
|
||||
tt(t, func() {
|
||||
pattern, err := TransformRegExp(`\u1234`)
|
||||
is(err, nil)
|
||||
is(pattern, `\x{1234}`)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTransformRegExp(b *testing.B) {
|
||||
f := func(reStr string, b *testing.B) {
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = TransformRegExp(reStr)
|
||||
}
|
||||
}
|
||||
|
||||
b.Run("Re", func(b *testing.B) {
|
||||
f(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, b)
|
||||
})
|
||||
|
||||
b.Run("Re2-1", func(b *testing.B) {
|
||||
f(`(?=)^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, b)
|
||||
})
|
||||
|
||||
b.Run("Re2-1", func(b *testing.B) {
|
||||
f(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$(?=)`, b)
|
||||
})
|
||||
}
|
||||
50
common/utils/goja/parser/scope.go
Normal file
50
common/utils/goja/parser/scope.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/dop251/goja/ast"
|
||||
"github.com/dop251/goja/unistring"
|
||||
)
|
||||
|
||||
type _scope struct {
|
||||
outer *_scope
|
||||
allowIn bool
|
||||
allowLet bool
|
||||
inIteration bool
|
||||
inSwitch bool
|
||||
inFuncParams bool
|
||||
inFunction bool
|
||||
inAsync bool
|
||||
allowAwait bool
|
||||
allowYield bool
|
||||
declarationList []*ast.VariableDeclaration
|
||||
|
||||
labels []unistring.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.VariableDeclaration) {
|
||||
self.declarationList = append(self.declarationList, declaration)
|
||||
}
|
||||
|
||||
func (self *_scope) hasLabel(name unistring.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
|
||||
}
|
||||
1078
common/utils/goja/parser/statement.go
Normal file
1078
common/utils/goja/parser/statement.go
Normal file
File diff suppressed because it is too large
Load Diff
49
common/utils/goja/parser/testutil_test.go
Normal file
49
common/utils/goja/parser/testutil_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Quick and dirty replacement for terst
|
||||
|
||||
func tt(t *testing.T, f func()) {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
pcs := make([]uintptr, 16)
|
||||
pcs = pcs[:runtime.Callers(1, pcs)]
|
||||
frames := runtime.CallersFrames(pcs)
|
||||
var file string
|
||||
var line int
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
// The line number here must match the line where f() is called (see below)
|
||||
if frame.Line == 40 && filepath.Base(frame.File) == "testutil_test.go" {
|
||||
break
|
||||
}
|
||||
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
file, line = frame.File, frame.Line
|
||||
}
|
||||
if line > 0 {
|
||||
t.Errorf("Error at %s:%d: %v", filepath.Base(file), line, x)
|
||||
} else {
|
||||
t.Errorf("Error at <unknown>: %v", x)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
f()
|
||||
}
|
||||
|
||||
func is(a, b interface{}) {
|
||||
as := fmt.Sprintf("%v", a)
|
||||
bs := fmt.Sprintf("%v", b)
|
||||
if as != bs {
|
||||
panic(fmt.Errorf("%+v(%T) != %+v(%T)", a, a, b, b))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user