diff options
Diffstat (limited to 'vendor/github.com/tdewolff/parse/v2/js/lex.go')
| -rw-r--r-- | vendor/github.com/tdewolff/parse/v2/js/lex.go | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/parse/v2/js/lex.go b/vendor/github.com/tdewolff/parse/v2/js/lex.go new file mode 100644 index 0000000..7d75bf5 --- /dev/null +++ b/vendor/github.com/tdewolff/parse/v2/js/lex.go @@ -0,0 +1,793 @@ +// Package js is an ECMAScript5.1 lexer following the specifications at http://www.ecma-international.org/ecma-262/5.1/. +package js + +import ( + "unicode" + "unicode/utf8" + + "github.com/tdewolff/parse/v2" +) + +var identifierStart = []*unicode.RangeTable{unicode.Lu, unicode.Ll, unicode.Lt, unicode.Lm, unicode.Lo, unicode.Nl, unicode.Other_ID_Start} +var identifierContinue = []*unicode.RangeTable{unicode.Lu, unicode.Ll, unicode.Lt, unicode.Lm, unicode.Lo, unicode.Nl, unicode.Mn, unicode.Mc, unicode.Nd, unicode.Pc, unicode.Other_ID_Continue} + +// IsIdentifierStart returns true if the byte-slice start is the start of an identifier +func IsIdentifierStart(b []byte) bool { + r, _ := utf8.DecodeRune(b) + return r == '$' || r == '\\' || r == '_' || unicode.IsOneOf(identifierStart, r) +} + +// IsIdentifierContinue returns true if the byte-slice start is a continuation of an identifier +func IsIdentifierContinue(b []byte) bool { + r, _ := utf8.DecodeRune(b) + return r == '$' || r == '\\' || r == '\u200C' || r == '\u200D' || unicode.IsOneOf(identifierContinue, r) +} + +// IsIdentifierEnd returns true if the byte-slice end is a start or continuation of an identifier +func IsIdentifierEnd(b []byte) bool { + r, _ := utf8.DecodeLastRune(b) + return r == '$' || r == '\\' || r == '\u200C' || r == '\u200D' || unicode.IsOneOf(identifierContinue, r) +} + +//////////////////////////////////////////////////////////////// + +// Lexer is the state for the lexer. +type Lexer struct { + r *parse.Input + err error + prevLineTerminator bool + prevNumericLiteral bool + level int + templateLevels []int +} + +// NewLexer returns a new Lexer for a given io.Reader. +func NewLexer(r *parse.Input) *Lexer { + return &Lexer{ + r: r, + prevLineTerminator: true, + level: 0, + templateLevels: []int{}, + } +} + +// Err returns the error encountered during lexing, this is often io.EOF but also other errors can be returned. +func (l *Lexer) Err() error { + if l.err != nil { + return l.err + } + return l.r.Err() +} + +// RegExp reparses the input stream for a regular expression. It is assumed that we just received DivToken or DivEqToken with Next(). This function will go back and read that as a regular expression. +func (l *Lexer) RegExp() (TokenType, []byte) { + if 0 < l.r.Offset() && l.r.Peek(-1) == '/' { + l.r.Move(-1) + } else if 1 < l.r.Offset() && l.r.Peek(-1) == '=' && l.r.Peek(-2) == '/' { + l.r.Move(-2) + } else { + l.err = parse.NewErrorLexer(l.r, "expected / or /=") + return ErrorToken, nil + } + l.r.Skip() // trick to set start = pos + + if l.consumeRegExpToken() { + return RegExpToken, l.r.Shift() + } + l.err = parse.NewErrorLexer(l.r, "unexpected EOF or newline") + return ErrorToken, nil +} + +// Next returns the next Token. It returns ErrorToken when an error was encountered. Using Err() one can retrieve the error message. +func (l *Lexer) Next() (TokenType, []byte) { + prevLineTerminator := l.prevLineTerminator + l.prevLineTerminator = false + + prevNumericLiteral := l.prevNumericLiteral + l.prevNumericLiteral = false + + // study on 50x jQuery shows: + // spaces: 20k + // alpha: 16k + // newlines: 14.4k + // operators: 4k + // numbers and dot: 3.6k + // (): 3.4k + // {}: 1.8k + // []: 0.9k + // "': 1k + // semicolon: 2.4k + // colon: 0.8k + // comma: 2.4k + // slash: 1.4k + // `~: almost 0 + + c := l.r.Peek(0) + switch c { + case ' ', '\t', '\v', '\f': + l.r.Move(1) + for l.consumeWhitespace() { + } + l.prevLineTerminator = prevLineTerminator + return WhitespaceToken, l.r.Shift() + case '\n', '\r': + l.r.Move(1) + for l.consumeLineTerminator() { + } + l.prevLineTerminator = true + return LineTerminatorToken, l.r.Shift() + case '>', '=', '!', '+', '*', '%', '&', '|', '^', '~', '?': + if tt := l.consumeOperatorToken(); tt != ErrorToken { + return tt, l.r.Shift() + } + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': + if tt := l.consumeNumericToken(); tt != ErrorToken || l.r.Pos() != 0 { + l.prevNumericLiteral = true + return tt, l.r.Shift() + } else if c == '.' { + l.r.Move(1) + if l.r.Peek(0) == '.' && l.r.Peek(1) == '.' { + l.r.Move(2) + return EllipsisToken, l.r.Shift() + } + return DotToken, l.r.Shift() + } + case ',': + l.r.Move(1) + return CommaToken, l.r.Shift() + case ';': + l.r.Move(1) + return SemicolonToken, l.r.Shift() + case '(': + l.level++ + l.r.Move(1) + return OpenParenToken, l.r.Shift() + case ')': + l.level-- + l.r.Move(1) + return CloseParenToken, l.r.Shift() + case '/': + if tt := l.consumeCommentToken(); tt != ErrorToken { + return tt, l.r.Shift() + } else if tt := l.consumeOperatorToken(); tt != ErrorToken { + return tt, l.r.Shift() + } + case '{': + l.level++ + l.r.Move(1) + return OpenBraceToken, l.r.Shift() + case '}': + l.level-- + if len(l.templateLevels) != 0 && l.level == l.templateLevels[len(l.templateLevels)-1] { + return l.consumeTemplateToken(), l.r.Shift() + } + l.r.Move(1) + return CloseBraceToken, l.r.Shift() + case ':': + l.r.Move(1) + return ColonToken, l.r.Shift() + case '\'', '"': + return l.consumeStringToken(), l.r.Shift() + case ']': + l.r.Move(1) + return CloseBracketToken, l.r.Shift() + case '[': + l.r.Move(1) + return OpenBracketToken, l.r.Shift() + case '<', '-': + if l.consumeHTMLLikeCommentToken(prevLineTerminator) { + return CommentToken, l.r.Shift() + } else if tt := l.consumeOperatorToken(); tt != ErrorToken { + return tt, l.r.Shift() + } + case '`': + l.templateLevels = append(l.templateLevels, l.level) + return l.consumeTemplateToken(), l.r.Shift() + case '#': + l.r.Move(1) + if l.consumeIdentifierToken() { + return PrivateIdentifierToken, l.r.Shift() + } + return ErrorToken, nil + default: + if l.consumeIdentifierToken() { + if prevNumericLiteral { + l.err = parse.NewErrorLexer(l.r, "unexpected identifier after number") + return ErrorToken, nil + } else if keyword, ok := Keywords[string(l.r.Lexeme())]; ok { + return keyword, l.r.Shift() + } + return IdentifierToken, l.r.Shift() + } + if 0xC0 <= c { + if l.consumeWhitespace() { + for l.consumeWhitespace() { + } + l.prevLineTerminator = prevLineTerminator + return WhitespaceToken, l.r.Shift() + } else if l.consumeLineTerminator() { + for l.consumeLineTerminator() { + } + l.prevLineTerminator = true + return LineTerminatorToken, l.r.Shift() + } + } else if c == 0 && l.r.Err() != nil { + return ErrorToken, nil + } + } + + r, _ := l.r.PeekRune(0) + l.err = parse.NewErrorLexer(l.r, "unexpected %s", parse.Printable(r)) + return ErrorToken, l.r.Shift() +} + +//////////////////////////////////////////////////////////////// + +/* +The following functions follow the specifications at http://www.ecma-international.org/ecma-262/5.1/ +*/ + +func (l *Lexer) consumeWhitespace() bool { + c := l.r.Peek(0) + if c == ' ' || c == '\t' || c == '\v' || c == '\f' { + l.r.Move(1) + return true + } else if 0xC0 <= c { + if r, n := l.r.PeekRune(0); r == '\u00A0' || r == '\uFEFF' || unicode.Is(unicode.Zs, r) { + l.r.Move(n) + return true + } + } + return false +} + +func (l *Lexer) isLineTerminator() bool { + c := l.r.Peek(0) + if c == '\n' || c == '\r' { + return true + } else if c == 0xE2 && l.r.Peek(1) == 0x80 && (l.r.Peek(2) == 0xA8 || l.r.Peek(2) == 0xA9) { + return true + } + return false +} + +func (l *Lexer) consumeLineTerminator() bool { + c := l.r.Peek(0) + if c == '\n' { + l.r.Move(1) + return true + } else if c == '\r' { + if l.r.Peek(1) == '\n' { + l.r.Move(2) + } else { + l.r.Move(1) + } + return true + } else if c == 0xE2 && l.r.Peek(1) == 0x80 && (l.r.Peek(2) == 0xA8 || l.r.Peek(2) == 0xA9) { + l.r.Move(3) + return true + } + return false +} + +func (l *Lexer) consumeDigit() bool { + if c := l.r.Peek(0); c >= '0' && c <= '9' { + l.r.Move(1) + return true + } + return false +} + +func (l *Lexer) consumeHexDigit() bool { + if c := l.r.Peek(0); (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') { + l.r.Move(1) + return true + } + return false +} + +func (l *Lexer) consumeBinaryDigit() bool { + if c := l.r.Peek(0); c == '0' || c == '1' { + l.r.Move(1) + return true + } + return false +} + +func (l *Lexer) consumeOctalDigit() bool { + if c := l.r.Peek(0); c >= '0' && c <= '7' { + l.r.Move(1) + return true + } + return false +} + +func (l *Lexer) consumeUnicodeEscape() bool { + if l.r.Peek(0) != '\\' || l.r.Peek(1) != 'u' { + return false + } + mark := l.r.Pos() + l.r.Move(2) + if c := l.r.Peek(0); c == '{' { + l.r.Move(1) + if l.consumeHexDigit() { + for l.consumeHexDigit() { + } + if c := l.r.Peek(0); c == '}' { + l.r.Move(1) + return true + } + } + l.r.Rewind(mark) + return false + } else if !l.consumeHexDigit() || !l.consumeHexDigit() || !l.consumeHexDigit() || !l.consumeHexDigit() { + l.r.Rewind(mark) + return false + } + return true +} + +func (l *Lexer) consumeSingleLineComment() { + for { + c := l.r.Peek(0) + if c == '\r' || c == '\n' || c == 0 && l.r.Err() != nil { + break + } else if 0xC0 <= c { + if r, _ := l.r.PeekRune(0); r == '\u2028' || r == '\u2029' { + break + } + } + l.r.Move(1) + } +} + +//////////////////////////////////////////////////////////////// + +func (l *Lexer) consumeHTMLLikeCommentToken(prevLineTerminator bool) bool { + c := l.r.Peek(0) + if c == '<' && l.r.Peek(1) == '!' && l.r.Peek(2) == '-' && l.r.Peek(3) == '-' { + // opening HTML-style single line comment + l.r.Move(4) + l.consumeSingleLineComment() + return true + } else if prevLineTerminator && c == '-' && l.r.Peek(1) == '-' && l.r.Peek(2) == '>' { + // closing HTML-style single line comment + // (only if current line didn't contain any meaningful tokens) + l.r.Move(3) + l.consumeSingleLineComment() + return true + } + return false +} + +func (l *Lexer) consumeCommentToken() TokenType { + c := l.r.Peek(1) + if c == '/' { + // single line comment + l.r.Move(2) + l.consumeSingleLineComment() + return CommentToken + } else if c == '*' { + l.r.Move(2) + tt := CommentToken + for { + c := l.r.Peek(0) + if c == '*' && l.r.Peek(1) == '/' { + l.r.Move(2) + break + } else if c == 0 && l.r.Err() != nil { + break + } else if l.consumeLineTerminator() { + l.prevLineTerminator = true + tt = CommentLineTerminatorToken + } else { + l.r.Move(1) + } + } + return tt + } + return ErrorToken +} + +var opTokens = map[byte]TokenType{ + '=': EqToken, + '!': NotToken, + '<': LtToken, + '>': GtToken, + '+': AddToken, + '-': SubToken, + '*': MulToken, + '/': DivToken, + '%': ModToken, + '&': BitAndToken, + '|': BitOrToken, + '^': BitXorToken, + '~': BitNotToken, + '?': QuestionToken, +} + +var opEqTokens = map[byte]TokenType{ + '=': EqEqToken, + '!': NotEqToken, + '<': LtEqToken, + '>': GtEqToken, + '+': AddEqToken, + '-': SubEqToken, + '*': MulEqToken, + '/': DivEqToken, + '%': ModEqToken, + '&': BitAndEqToken, + '|': BitOrEqToken, + '^': BitXorEqToken, +} + +var opOpTokens = map[byte]TokenType{ + '<': LtLtToken, + '+': IncrToken, + '-': DecrToken, + '*': ExpToken, + '&': AndToken, + '|': OrToken, + '?': NullishToken, +} + +var opOpEqTokens = map[byte]TokenType{ + '<': LtLtEqToken, + '*': ExpEqToken, + '&': AndEqToken, + '|': OrEqToken, + '?': NullishEqToken, +} + +func (l *Lexer) consumeOperatorToken() TokenType { + c := l.r.Peek(0) + l.r.Move(1) + if l.r.Peek(0) == '=' { + l.r.Move(1) + if l.r.Peek(0) == '=' && (c == '!' || c == '=') { + l.r.Move(1) + if c == '!' { + return NotEqEqToken + } + return EqEqEqToken + } + return opEqTokens[c] + } else if l.r.Peek(0) == c && (c == '+' || c == '-' || c == '*' || c == '&' || c == '|' || c == '?' || c == '<') { + l.r.Move(1) + if l.r.Peek(0) == '=' && c != '+' && c != '-' { + l.r.Move(1) + return opOpEqTokens[c] + } + return opOpTokens[c] + } else if c == '?' && l.r.Peek(0) == '.' && (l.r.Peek(1) < '0' || l.r.Peek(1) > '9') { + l.r.Move(1) + return OptChainToken + } else if c == '=' && l.r.Peek(0) == '>' { + l.r.Move(1) + return ArrowToken + } else if c == '>' && l.r.Peek(0) == '>' { + l.r.Move(1) + if l.r.Peek(0) == '>' { + l.r.Move(1) + if l.r.Peek(0) == '=' { + l.r.Move(1) + return GtGtGtEqToken + } + return GtGtGtToken + } else if l.r.Peek(0) == '=' { + l.r.Move(1) + return GtGtEqToken + } + return GtGtToken + } + return opTokens[c] +} + +func (l *Lexer) consumeIdentifierToken() bool { + c := l.r.Peek(0) + if identifierStartTable[c] { + l.r.Move(1) + } else if 0xC0 <= c { + if r, n := l.r.PeekRune(0); unicode.IsOneOf(identifierStart, r) { + l.r.Move(n) + } else { + return false + } + } else if !l.consumeUnicodeEscape() { + return false + } + for { + c := l.r.Peek(0) + if identifierTable[c] { + l.r.Move(1) + } else if 0xC0 <= c { + if r, n := l.r.PeekRune(0); r == '\u200C' || r == '\u200D' || unicode.IsOneOf(identifierContinue, r) { + l.r.Move(n) + } else { + break + } + } else if !l.consumeUnicodeEscape() { + break + } + } + return true +} + +func (l *Lexer) consumeNumericSeparator(f func() bool) bool { + if l.r.Peek(0) != '_' { + return false + } + l.r.Move(1) + if !f() { + l.r.Move(-1) + return false + } + return true +} + +func (l *Lexer) consumeNumericToken() TokenType { + // assume to be on 0 1 2 3 4 5 6 7 8 9 . + first := l.r.Peek(0) + if first == '0' { + l.r.Move(1) + if l.r.Peek(0) == 'x' || l.r.Peek(0) == 'X' { + l.r.Move(1) + if l.consumeHexDigit() { + for l.consumeHexDigit() || l.consumeNumericSeparator(l.consumeHexDigit) { + } + return HexadecimalToken + } + l.err = parse.NewErrorLexer(l.r, "invalid hexadecimal number") + return ErrorToken + } else if l.r.Peek(0) == 'b' || l.r.Peek(0) == 'B' { + l.r.Move(1) + if l.consumeBinaryDigit() { + for l.consumeBinaryDigit() || l.consumeNumericSeparator(l.consumeBinaryDigit) { + } + return BinaryToken + } + l.err = parse.NewErrorLexer(l.r, "invalid binary number") + return ErrorToken + } else if l.r.Peek(0) == 'o' || l.r.Peek(0) == 'O' { + l.r.Move(1) + if l.consumeOctalDigit() { + for l.consumeOctalDigit() || l.consumeNumericSeparator(l.consumeOctalDigit) { + } + return OctalToken + } + l.err = parse.NewErrorLexer(l.r, "invalid octal number") + return ErrorToken + } else if l.r.Peek(0) == 'n' { + l.r.Move(1) + return BigIntToken + } else if '0' <= l.r.Peek(0) && l.r.Peek(0) <= '9' { + l.err = parse.NewErrorLexer(l.r, "legacy octal numbers are not supported") + return ErrorToken + } + } else if first != '.' { + for l.consumeDigit() || l.consumeNumericSeparator(l.consumeDigit) { + } + } + // we have parsed a 0 or an integer number + c := l.r.Peek(0) + if c == '.' { + l.r.Move(1) + if l.consumeDigit() { + for l.consumeDigit() || l.consumeNumericSeparator(l.consumeDigit) { + } + c = l.r.Peek(0) + } else if first == '.' { + // number starts with a dot and must be followed by digits + l.r.Move(-1) + return ErrorToken // may be dot or ellipsis + } else { + c = l.r.Peek(0) + } + } else if c == 'n' { + l.r.Move(1) + return BigIntToken + } + if c == 'e' || c == 'E' { + l.r.Move(1) + c = l.r.Peek(0) + if c == '+' || c == '-' { + l.r.Move(1) + } + if !l.consumeDigit() { + l.err = parse.NewErrorLexer(l.r, "invalid number") + return ErrorToken + } + for l.consumeDigit() || l.consumeNumericSeparator(l.consumeDigit) { + } + } + return DecimalToken +} + +func (l *Lexer) consumeStringToken() TokenType { + // assume to be on ' or " + delim := l.r.Peek(0) + l.r.Move(1) + for { + c := l.r.Peek(0) + if c == delim { + l.r.Move(1) + break + } else if c == '\\' { + l.r.Move(1) + if !l.consumeLineTerminator() { + if c := l.r.Peek(0); c == delim || c == '\\' { + l.r.Move(1) + } + } + continue + } else if c == '\n' || c == '\r' || c == 0 && l.r.Err() != nil { + l.err = parse.NewErrorLexer(l.r, "unterminated string literal") + return ErrorToken + } + l.r.Move(1) + } + return StringToken +} + +func (l *Lexer) consumeRegExpToken() bool { + // assume to be on / + l.r.Move(1) + inClass := false + for { + c := l.r.Peek(0) + if !inClass && c == '/' { + l.r.Move(1) + break + } else if c == '[' { + inClass = true + } else if c == ']' { + inClass = false + } else if c == '\\' { + l.r.Move(1) + if l.isLineTerminator() || l.r.Peek(0) == 0 && l.r.Err() != nil { + return false + } + } else if l.isLineTerminator() || c == 0 && l.r.Err() != nil { + return false + } + l.r.Move(1) + } + // flags + for { + c := l.r.Peek(0) + if identifierTable[c] { + l.r.Move(1) + } else if 0xC0 <= c { + if r, n := l.r.PeekRune(0); r == '\u200C' || r == '\u200D' || unicode.IsOneOf(identifierContinue, r) { + l.r.Move(n) + } else { + break + } + } else { + break + } + } + return true +} + +func (l *Lexer) consumeTemplateToken() TokenType { + // assume to be on ` or } when already within template + continuation := l.r.Peek(0) == '}' + l.r.Move(1) + for { + c := l.r.Peek(0) + if c == '`' { + l.templateLevels = l.templateLevels[:len(l.templateLevels)-1] + l.r.Move(1) + if continuation { + return TemplateEndToken + } + return TemplateToken + } else if c == '$' && l.r.Peek(1) == '{' { + l.level++ + l.r.Move(2) + if continuation { + return TemplateMiddleToken + } + return TemplateStartToken + } else if c == '\\' { + l.r.Move(1) + if c := l.r.Peek(0); c != 0 { + l.r.Move(1) + } + continue + } else if c == 0 && l.r.Err() != nil { + l.err = parse.NewErrorLexer(l.r, "unterminated template literal") + return ErrorToken + } + l.r.Move(1) + } +} + +var identifierStartTable = [256]bool{ + // ASCII + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, true, false, false, false, // $ + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, true, true, true, true, true, true, true, // A, B, C, D, E, F, G + true, true, true, true, true, true, true, true, // H, I, J, K, L, M, N, O + true, true, true, true, true, true, true, true, // P, Q, R, S, T, U, V, W + true, true, true, false, false, false, false, true, // X, Y, Z, _ + + false, true, true, true, true, true, true, true, // a, b, c, d, e, f, g + true, true, true, true, true, true, true, true, // h, i, j, k, l, m, n, o + true, true, true, true, true, true, true, true, // p, q, r, s, t, u, v, w + true, true, true, false, false, false, false, false, // x, y, z + + // non-ASCII + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, +} + +var identifierTable = [256]bool{ + // ASCII + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, true, false, false, false, // $ + false, false, false, false, false, false, false, false, + true, true, true, true, true, true, true, true, // 0, 1, 2, 3, 4, 5, 6, 7 + true, true, false, false, false, false, false, false, // 8, 9 + + false, true, true, true, true, true, true, true, // A, B, C, D, E, F, G + true, true, true, true, true, true, true, true, // H, I, J, K, L, M, N, O + true, true, true, true, true, true, true, true, // P, Q, R, S, T, U, V, W + true, true, true, false, false, false, false, true, // X, Y, Z, _ + + false, true, true, true, true, true, true, true, // a, b, c, d, e, f, g + true, true, true, true, true, true, true, true, // h, i, j, k, l, m, n, o + true, true, true, true, true, true, true, true, // p, q, r, s, t, u, v, w + true, true, true, false, false, false, false, false, // x, y, z + + // non-ASCII + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, +} |
