T3MP3ST public launch build
This commit is contained in:
1 parent
972247b881
commit
184c7d4e59
1589 files changed
+57150
-506
No files matched your search
@@ -0,0 +1,134 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type TokenKind int
|
||||
|
||||
func TokenKinds(tk ...TokenKind) []TokenKind {
|
||||
return tk
|
||||
}
|
||||
|
||||
const (
|
||||
EOF TokenKind = iota
|
||||
Symbol
|
||||
Comma
|
||||
Colon
|
||||
OpenBracket // [
|
||||
CloseBracket // ]
|
||||
OpenCurly
|
||||
CloseCurly
|
||||
OpenParen
|
||||
CloseParen
|
||||
Equal // ==
|
||||
Equals // =
|
||||
NotEqual // !=
|
||||
And
|
||||
Or
|
||||
Like // =~
|
||||
NotLike // !~
|
||||
String
|
||||
Number
|
||||
Bool
|
||||
Plus
|
||||
Increment
|
||||
IncrementBy
|
||||
Dash
|
||||
Decrement
|
||||
DecrementBy
|
||||
Star
|
||||
Slash
|
||||
Percent
|
||||
Dot
|
||||
Spread // ...
|
||||
RecursiveDescent // ..
|
||||
Dollar
|
||||
Variable
|
||||
GreaterThan
|
||||
GreaterThanOrEqual
|
||||
LessThan
|
||||
LessThanOrEqual
|
||||
Exclamation
|
||||
Null
|
||||
If
|
||||
Else
|
||||
ElseIf
|
||||
Branch
|
||||
Map
|
||||
Each
|
||||
Filter
|
||||
Search
|
||||
RegexPattern
|
||||
SortBy
|
||||
GroupBy
|
||||
Reduce
|
||||
MapValues
|
||||
Any
|
||||
All
|
||||
Count
|
||||
Asc
|
||||
Desc
|
||||
QuestionMark
|
||||
DoubleQuestionMark
|
||||
Semicolon
|
||||
)
|
||||
|
||||
type Tokens []Token
|
||||
|
||||
func (tt Tokens) Split(kind TokenKind) []Tokens {
|
||||
var res []Tokens
|
||||
var cur Tokens
|
||||
for _, t := range tt {
|
||||
if t.Kind == kind {
|
||||
if len(cur) > 0 {
|
||||
res = append(res, cur)
|
||||
}
|
||||
cur = nil
|
||||
continue
|
||||
}
|
||||
cur = append(cur, t)
|
||||
}
|
||||
if len(cur) > 0 {
|
||||
res = append(res, cur)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
Kind TokenKind
|
||||
Value string
|
||||
Pos int
|
||||
Len int
|
||||
}
|
||||
|
||||
func NewToken(kind TokenKind, value string, pos int, len int) Token {
|
||||
return Token{
|
||||
Kind: kind,
|
||||
Value: value,
|
||||
Pos: pos,
|
||||
Len: len,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Token) IsKind(kind ...TokenKind) bool {
|
||||
return slices.Contains(kind, t.Kind)
|
||||
}
|
||||
|
||||
type UnexpectedTokenError struct {
|
||||
Pos int
|
||||
Token rune
|
||||
}
|
||||
|
||||
func (e *UnexpectedTokenError) Error() string {
|
||||
return fmt.Sprintf("failed to tokenize: unexpected token: %s at position %d.", string(e.Token), e.Pos)
|
||||
}
|
||||
|
||||
type UnexpectedEOFError struct {
|
||||
Pos int
|
||||
}
|
||||
|
||||
func (e *UnexpectedEOFError) Error() string {
|
||||
return fmt.Sprintf("failed to tokenize: unexpected EOF at position %d.", e.Pos)
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/tomwright/dasel/v3/internal/ptr"
|
||||
)
|
||||
|
||||
type Tokenizer struct {
|
||||
i int
|
||||
src string
|
||||
srcLen int
|
||||
}
|
||||
|
||||
func NewTokenizer(src string) *Tokenizer {
|
||||
return &Tokenizer{
|
||||
i: 0,
|
||||
src: src,
|
||||
srcLen: len([]rune(src)),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Tokenizer) Tokenize() (Tokens, error) {
|
||||
var tokens Tokens
|
||||
for {
|
||||
tok, err := p.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tok.Kind == EOF {
|
||||
break
|
||||
}
|
||||
tokens = append(tokens, tok)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func (p *Tokenizer) peekRuneEqual(i int, to rune) bool {
|
||||
if i >= p.srcLen {
|
||||
return false
|
||||
}
|
||||
return rune(p.src[i]) == to
|
||||
}
|
||||
|
||||
func (p *Tokenizer) peekRuneMatches(i int, fn func(rune) bool) bool {
|
||||
if i >= p.srcLen {
|
||||
return false
|
||||
}
|
||||
return fn(rune(p.src[i]))
|
||||
}
|
||||
|
||||
func (p *Tokenizer) parseCurRune() (Token, error) {
|
||||
// Skip over whitespace
|
||||
for p.i < p.srcLen && unicode.IsSpace(rune(p.src[p.i])) {
|
||||
p.i++
|
||||
}
|
||||
|
||||
// Skip over comments
|
||||
if p.src[p.i] == '/' && p.i+1 < p.srcLen && p.src[p.i+1] == '/' {
|
||||
p.i += 2
|
||||
for p.i < p.srcLen && p.src[p.i] != '\n' {
|
||||
p.i++
|
||||
}
|
||||
// After skipping the comment, skip any whitespace again.
|
||||
for p.i < p.srcLen && unicode.IsSpace(rune(p.src[p.i])) {
|
||||
p.i++
|
||||
}
|
||||
if p.i >= p.srcLen {
|
||||
return NewToken(EOF, "", p.i, 0), nil
|
||||
}
|
||||
}
|
||||
|
||||
switch p.src[p.i] {
|
||||
case '.':
|
||||
if p.peekRuneEqual(p.i+1, '.') && p.peekRuneEqual(p.i+2, '.') {
|
||||
return NewToken(Spread, "...", p.i, 3), nil
|
||||
}
|
||||
if p.peekRuneEqual(p.i+1, '.') {
|
||||
return NewToken(RecursiveDescent, "..", p.i, 2), nil
|
||||
}
|
||||
return NewToken(Dot, ".", p.i, 1), nil
|
||||
case ',':
|
||||
return NewToken(Comma, ",", p.i, 1), nil
|
||||
case ':':
|
||||
return NewToken(Colon, ":", p.i, 1), nil
|
||||
case ';':
|
||||
return NewToken(Semicolon, ";", p.i, 1), nil
|
||||
case '[':
|
||||
return NewToken(OpenBracket, "[", p.i, 1), nil
|
||||
case ']':
|
||||
return NewToken(CloseBracket, "]", p.i, 1), nil
|
||||
case '(':
|
||||
return NewToken(OpenParen, "(", p.i, 1), nil
|
||||
case ')':
|
||||
return NewToken(CloseParen, ")", p.i, 1), nil
|
||||
case '{':
|
||||
return NewToken(OpenCurly, "{", p.i, 1), nil
|
||||
case '}':
|
||||
return NewToken(CloseCurly, "}", p.i, 1), nil
|
||||
case '*':
|
||||
return NewToken(Star, "*", p.i, 1), nil
|
||||
case '/':
|
||||
return NewToken(Slash, "/", p.i, 1), nil
|
||||
case '%':
|
||||
return NewToken(Percent, "%", p.i, 1), nil
|
||||
case '$':
|
||||
if p.peekRuneMatches(p.i+1, unicode.IsLetter) || p.peekRuneEqual(p.i+1, '_') {
|
||||
pos := p.i + 1
|
||||
for pos < p.srcLen && (unicode.IsLetter(rune(p.src[pos])) ||
|
||||
unicode.IsDigit(rune(p.src[pos])) ||
|
||||
p.src[pos] == '_') {
|
||||
pos++
|
||||
}
|
||||
return NewToken(Variable, p.src[p.i+1:pos], p.i, pos-p.i), nil
|
||||
}
|
||||
return NewToken(Dollar, "$", p.i, 1), nil
|
||||
case '=':
|
||||
if p.peekRuneEqual(p.i+1, '=') {
|
||||
return NewToken(Equal, "==", p.i, 2), nil
|
||||
}
|
||||
if p.peekRuneEqual(p.i+1, '~') {
|
||||
return NewToken(Like, "=~", p.i, 2), nil
|
||||
}
|
||||
return NewToken(Equals, "=", p.i, 1), nil
|
||||
case '+':
|
||||
if p.peekRuneEqual(p.i+1, '=') {
|
||||
return NewToken(IncrementBy, "+=", p.i, 2), nil
|
||||
}
|
||||
if p.peekRuneEqual(p.i+1, '+') {
|
||||
return NewToken(Increment, "++", p.i, 2), nil
|
||||
}
|
||||
return NewToken(Plus, "+", p.i, 1), nil
|
||||
case '-':
|
||||
if p.peekRuneEqual(p.i+1, '=') {
|
||||
return NewToken(DecrementBy, "-=", p.i, 2), nil
|
||||
}
|
||||
if p.peekRuneEqual(p.i+1, '-') {
|
||||
return NewToken(Decrement, "--", p.i, 2), nil
|
||||
}
|
||||
return NewToken(Dash, "-", p.i, 1), nil
|
||||
case '>':
|
||||
if p.peekRuneEqual(p.i+1, '=') {
|
||||
return NewToken(GreaterThanOrEqual, ">=", p.i, 2), nil
|
||||
}
|
||||
return NewToken(GreaterThan, ">", p.i, 1), nil
|
||||
case '<':
|
||||
if p.peekRuneEqual(p.i+1, '=') {
|
||||
return NewToken(LessThanOrEqual, "<=", p.i, 2), nil
|
||||
}
|
||||
return NewToken(LessThan, "<", p.i, 1), nil
|
||||
case '!':
|
||||
if p.peekRuneEqual(p.i+1, '=') {
|
||||
return NewToken(NotEqual, "!=", p.i, 2), nil
|
||||
}
|
||||
if p.peekRuneEqual(p.i+1, '~') {
|
||||
return NewToken(NotLike, "!~", p.i, 2), nil
|
||||
}
|
||||
return NewToken(Exclamation, "!", p.i, 1), nil
|
||||
case '&':
|
||||
if p.peekRuneEqual(p.i+1, '&') {
|
||||
return NewToken(And, "&&", p.i, 2), nil
|
||||
}
|
||||
return Token{}, &UnexpectedTokenError{
|
||||
Pos: p.i,
|
||||
Token: rune(p.src[p.i]),
|
||||
}
|
||||
case '|':
|
||||
if p.peekRuneEqual(p.i+1, '|') {
|
||||
return NewToken(Or, "||", p.i, 2), nil
|
||||
}
|
||||
return Token{}, &UnexpectedTokenError{
|
||||
Pos: p.i,
|
||||
Token: rune(p.src[p.i]),
|
||||
}
|
||||
case '?':
|
||||
if p.peekRuneEqual(p.i+1, '?') {
|
||||
return NewToken(DoubleQuestionMark, "??", p.i, 2), nil
|
||||
}
|
||||
return NewToken(QuestionMark, "?", p.i, 1), nil
|
||||
case '"', '\'':
|
||||
pos := p.i
|
||||
buf := make([]rune, 0)
|
||||
|
||||
escapeCharacters := map[rune]bool{
|
||||
// The closing quote character can be escaped.
|
||||
rune(p.src[p.i]): true,
|
||||
'\\': true,
|
||||
't': true,
|
||||
'r': true,
|
||||
'n': true,
|
||||
}
|
||||
|
||||
pos++
|
||||
foundCloseRune := false
|
||||
for pos < p.srcLen {
|
||||
if p.src[pos] == p.src[p.i] {
|
||||
foundCloseRune = true
|
||||
break
|
||||
}
|
||||
if p.src[pos] == '\\' {
|
||||
// Handle escape characters.
|
||||
pos++
|
||||
|
||||
if pos >= p.srcLen {
|
||||
return Token{}, &UnexpectedEOFError{Pos: pos}
|
||||
}
|
||||
|
||||
if p.peekRuneMatches(pos, func(r rune) bool {
|
||||
_, ok := escapeCharacters[r]
|
||||
return ok
|
||||
}) {
|
||||
switch p.src[pos] {
|
||||
case '\\':
|
||||
buf = append(buf, '\\')
|
||||
case 't':
|
||||
buf = append(buf, '\t')
|
||||
case 'r':
|
||||
buf = append(buf, '\r')
|
||||
case 'n':
|
||||
buf = append(buf, '\n')
|
||||
default:
|
||||
// This must be the closing quote character.
|
||||
buf = append(buf, rune(p.src[pos]))
|
||||
}
|
||||
pos++
|
||||
} else {
|
||||
buf = append(buf, rune(p.src[pos]))
|
||||
pos++
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
buf = append(buf, rune(p.src[pos]))
|
||||
pos++
|
||||
}
|
||||
if !foundCloseRune {
|
||||
// We didn't find a closing quote.
|
||||
if pos < p.srcLen {
|
||||
// This shouldn't be possible.
|
||||
return Token{}, &UnexpectedTokenError{
|
||||
Pos: p.i,
|
||||
Token: rune(p.src[pos]),
|
||||
}
|
||||
}
|
||||
// This can happen if the selector ends before the closing quote.
|
||||
return Token{}, &UnexpectedEOFError{
|
||||
Pos: pos,
|
||||
}
|
||||
}
|
||||
res := NewToken(String, string(buf), p.i, pos+1-p.i)
|
||||
return res, nil
|
||||
default:
|
||||
pos := p.i
|
||||
|
||||
matchStr := func(pos int, m string, caseInsensitive bool, kind TokenKind) *Token {
|
||||
l := len(m)
|
||||
if pos+(l-1) >= p.srcLen {
|
||||
return nil
|
||||
}
|
||||
other := p.src[pos : pos+l]
|
||||
if m != other && (!caseInsensitive || !strings.EqualFold(m, other)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if pos+(l) < p.srcLen && (unicode.IsLetter(rune(p.src[pos+l])) || unicode.IsDigit(rune(p.src[pos+l]))) {
|
||||
// There is a follow letter or digit.
|
||||
return nil
|
||||
}
|
||||
|
||||
return ptr.To(NewToken(kind, other, pos, l))
|
||||
}
|
||||
|
||||
matchRegexPattern := func(pos int) *Token {
|
||||
if p.src[pos] != 'r' || !p.peekRuneEqual(pos+1, '/') {
|
||||
return nil
|
||||
}
|
||||
start := pos
|
||||
pos += 2
|
||||
for !p.peekRuneEqual(pos, '/') {
|
||||
pos++
|
||||
}
|
||||
pos++
|
||||
return ptr.To(NewToken(RegexPattern, p.src[start+2:pos-1], start, pos-start))
|
||||
}
|
||||
|
||||
if t := matchStr(pos, "null", true, Null); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "true", true, Bool); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "false", true, Bool); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "elseif", false, ElseIf); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "if", false, If); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "else", false, Else); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "branch", false, Branch); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "map", false, Map); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "each", false, Each); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "filter", false, Filter); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "search", false, Search); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "sortBy", false, SortBy); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "groupBy", false, GroupBy); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "reduce", false, Reduce); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "mapValues", false, MapValues); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "any", false, Any); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "all", false, All); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "count", false, Count); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "asc", false, Asc); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
if t := matchStr(pos, "desc", false, Desc); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
|
||||
if t := matchRegexPattern(pos); t != nil {
|
||||
return *t, nil
|
||||
}
|
||||
|
||||
if unicode.IsDigit(rune(p.src[pos])) {
|
||||
// Handle whole numbers
|
||||
for pos < p.srcLen && unicode.IsDigit(rune(p.src[pos])) {
|
||||
pos++
|
||||
}
|
||||
// Handle floats
|
||||
if pos < p.srcLen && p.src[pos] == '.' && pos+1 < p.srcLen && unicode.IsDigit(rune(p.src[pos+1])) {
|
||||
pos++
|
||||
for pos < p.srcLen && unicode.IsDigit(rune(p.src[pos])) {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
return NewToken(Number, p.src[p.i:pos], p.i, pos-p.i), nil
|
||||
}
|
||||
|
||||
if unicode.IsLetter(rune(p.src[pos])) || p.src[pos] == '_' {
|
||||
for pos < p.srcLen && (unicode.IsLetter(rune(p.src[pos])) ||
|
||||
unicode.IsDigit(rune(p.src[pos])) ||
|
||||
p.src[pos] == '_') {
|
||||
pos++
|
||||
}
|
||||
return NewToken(Symbol, p.src[p.i:pos], p.i, pos-p.i), nil
|
||||
}
|
||||
|
||||
return Token{}, &UnexpectedTokenError{
|
||||
Pos: p.i,
|
||||
Token: rune(p.src[p.i]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Tokenizer) Next() (Token, error) {
|
||||
if p.i >= len(p.src) {
|
||||
return NewToken(EOF, "", p.i, 0), nil
|
||||
}
|
||||
|
||||
t, err := p.parseCurRune()
|
||||
if err != nil {
|
||||
return Token{}, err
|
||||
}
|
||||
p.i += t.Len
|
||||
return t, nil
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package lexer_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/tomwright/dasel/v3/selector/lexer"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
in string
|
||||
out []lexer.TokenKind
|
||||
}
|
||||
|
||||
func (tc testCase) run(t *testing.T) {
|
||||
tok := lexer.NewTokenizer(tc.in)
|
||||
tokens, err := tok.Tokenize()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(tokens) != len(tc.out) {
|
||||
t.Fatalf("unexpected number of tokens: %d", len(tokens))
|
||||
}
|
||||
for i := range tokens {
|
||||
if tokens[i].Kind != tc.out[i] {
|
||||
t.Errorf("unexpected token kind at position %d: exp %v, got %v", i, tc.out[i], tokens[i].Kind)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type errTestCase struct {
|
||||
in string
|
||||
match func(error) bool
|
||||
}
|
||||
|
||||
func (tc errTestCase) run(t *testing.T) {
|
||||
tok := lexer.NewTokenizer(tc.in)
|
||||
tokens, err := tok.Tokenize()
|
||||
if !tc.match(err) {
|
||||
t.Errorf("unexpected error, got %v", err)
|
||||
}
|
||||
if tokens != nil {
|
||||
t.Errorf("unexpected tokens: %v", tokens)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func matchUnexpectedError(r rune, p int) func(error) bool {
|
||||
return func(err error) bool {
|
||||
var e *lexer.UnexpectedTokenError
|
||||
if !errors.As(err, &e) {
|
||||
return false
|
||||
}
|
||||
|
||||
return e.Token == r && e.Pos == p
|
||||
}
|
||||
}
|
||||
|
||||
func matchUnexpectedEOFError(p int) func(error) bool {
|
||||
return func(err error) bool {
|
||||
var e *lexer.UnexpectedEOFError
|
||||
if !errors.As(err, &e) {
|
||||
return false
|
||||
}
|
||||
|
||||
return e.Pos == p
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenizer_Parse(t *testing.T) {
|
||||
t.Run("variables", testCase{
|
||||
in: "$foo $bar123 $baz $ $quietLoudSCREAM $_hello $hello_world",
|
||||
out: []lexer.TokenKind{
|
||||
lexer.Variable,
|
||||
lexer.Variable,
|
||||
lexer.Variable,
|
||||
lexer.Dollar,
|
||||
lexer.Variable,
|
||||
lexer.Variable,
|
||||
lexer.Variable,
|
||||
},
|
||||
}.run)
|
||||
|
||||
t.Run("escaped strings", testCase{
|
||||
in: `'a\'b' "a\"b" "a\nb" "a\rb" "a\tb" "a\\b"`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.String,
|
||||
lexer.String,
|
||||
lexer.String,
|
||||
lexer.String,
|
||||
lexer.String,
|
||||
lexer.String,
|
||||
},
|
||||
}.run)
|
||||
|
||||
t.Run("if", testCase{
|
||||
in: `if elseif else`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.If,
|
||||
lexer.ElseIf,
|
||||
lexer.Else,
|
||||
},
|
||||
}.run)
|
||||
|
||||
t.Run("regex", testCase{
|
||||
in: `r/asd/ r/hello there/`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.RegexPattern,
|
||||
lexer.RegexPattern,
|
||||
},
|
||||
}.run)
|
||||
|
||||
t.Run("sort by", testCase{
|
||||
in: `sortBy(foo, asc)`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.SortBy,
|
||||
lexer.OpenParen,
|
||||
lexer.Symbol,
|
||||
lexer.Comma,
|
||||
lexer.Asc,
|
||||
lexer.CloseParen,
|
||||
},
|
||||
}.run)
|
||||
|
||||
t.Run("recursive descent", func(t *testing.T) {
|
||||
t.Run("key", testCase{
|
||||
in: `..foo`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.RecursiveDescent,
|
||||
lexer.Symbol,
|
||||
},
|
||||
}.run)
|
||||
t.Run("index", testCase{
|
||||
in: `..[1]`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.RecursiveDescent,
|
||||
lexer.OpenBracket,
|
||||
lexer.Number,
|
||||
lexer.CloseBracket,
|
||||
},
|
||||
}.run)
|
||||
t.Run("wildcard", testCase{
|
||||
in: `..*`,
|
||||
out: []lexer.TokenKind{
|
||||
lexer.RecursiveDescent,
|
||||
lexer.Star,
|
||||
},
|
||||
}.run)
|
||||
})
|
||||
|
||||
t.Run("everything", testCase{
|
||||
in: "foo.bar.baz[1] != 42.123 || foo.b_a_r.baz['hello'] == 42 && x == 'a\\'b' + false true . .... asd... $name null",
|
||||
out: []lexer.TokenKind{
|
||||
lexer.Symbol, lexer.Dot, lexer.Symbol, lexer.Dot, lexer.Symbol, lexer.OpenBracket, lexer.Number, lexer.CloseBracket, lexer.NotEqual, lexer.Number,
|
||||
lexer.Or,
|
||||
lexer.Symbol, lexer.Dot, lexer.Symbol, lexer.Dot, lexer.Symbol, lexer.OpenBracket, lexer.String, lexer.CloseBracket, lexer.Equal, lexer.Number,
|
||||
lexer.And,
|
||||
lexer.Symbol, lexer.Equal, lexer.String,
|
||||
lexer.Plus, lexer.Bool, lexer.Bool,
|
||||
lexer.Dot, lexer.Spread, lexer.Dot,
|
||||
lexer.Symbol, lexer.Spread,
|
||||
lexer.Variable, lexer.Null,
|
||||
},
|
||||
}.run)
|
||||
|
||||
t.Run("unhappy", func(t *testing.T) {
|
||||
t.Run("unfinished double quote", errTestCase{
|
||||
in: `"hello`,
|
||||
match: matchUnexpectedEOFError(6),
|
||||
}.run)
|
||||
t.Run("unfinished single quote", errTestCase{
|
||||
in: `'hello`,
|
||||
match: matchUnexpectedEOFError(6),
|
||||
}.run)
|
||||
t.Run("trailing backslash in double quote", errTestCase{
|
||||
in: `"\`,
|
||||
match: matchUnexpectedEOFError(2),
|
||||
}.run)
|
||||
t.Run("trailing backslash in single quote", errTestCase{
|
||||
in: `'\`,
|
||||
match: matchUnexpectedEOFError(2),
|
||||
}.run)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user