Files
sift/vendor/github.com/itchyny/gojq/lexer.go
T
kyle 9444a35322 p1: /v2/scrape, /v1/fetch, auth, CLI, contract tests
Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification
is ignored. No 502. keys add writes the secret only to --out. Release binary
has no -test-allow-prefix.
2026-09-22 21:54:01 -07:00

579 lines
11 KiB
Go

package gojq
import (
"encoding/json"
"unicode/utf8"
)
type lexer struct {
source string
offset int
result *Query
token string
tokenType int
inString bool
err error
}
func newLexer(src string) *lexer {
return &lexer{source: src}
}
const eof = -1
var keywords = map[string]int{
"or": tokOrOp,
"and": tokAndOp,
"module": tokModule,
"import": tokImport,
"include": tokInclude,
"def": tokDef,
"as": tokAs,
"label": tokLabel,
"break": tokBreak,
"null": tokNull,
"true": tokTrue,
"false": tokFalse,
"if": tokIf,
"then": tokThen,
"elif": tokElif,
"else": tokElse,
"end": tokEnd,
"try": tokTry,
"catch": tokCatch,
"reduce": tokReduce,
"foreach": tokForeach,
}
func (l *lexer) Lex(lval *yySymType) (tokenType int) {
defer func() { l.tokenType = tokenType }()
if len(l.source) == l.offset {
l.token = ""
return eof
}
if l.inString {
tok, str := l.scanString(l.offset)
lval.token = str
return tok
}
ch, iseof := l.next()
if iseof {
l.token = ""
return eof
}
switch {
case isIdent(ch, false):
i := l.offset - 1
j, isModule := l.scanIdentOrModule()
l.token = l.source[i:j]
lval.token = l.token
if isModule {
return tokModuleIdent
}
if tok, ok := keywords[l.token]; ok {
return tok
}
return tokIdent
case isNumber(ch):
i := l.offset - 1
j := l.scanNumber(numberStateLead)
if j < 0 {
l.token = l.source[i:-j]
return tokInvalid
}
l.token = l.source[i:j]
lval.token = l.token
return tokNumber
}
switch ch {
case '.':
ch := l.peek()
switch {
case ch == '.':
l.offset++
l.token = ".."
return tokRecurse
case isIdent(ch, false):
l.token = l.source[l.offset-1 : l.scanIdent()]
lval.token = l.token[1:]
return tokIndex
case isNumber(ch):
i := l.offset - 1
j := l.scanNumber(numberStateFloat)
if j < 0 {
l.token = l.source[i:-j]
return tokInvalid
}
l.token = l.source[i:j]
lval.token = l.token
return tokNumber
default:
return '.'
}
case '$':
if isIdent(l.peek(), false) {
i := l.offset - 1
j, isModule := l.scanIdentOrModule()
l.token = l.source[i:j]
lval.token = l.token
if isModule {
return tokModuleVariable
}
return tokVariable
}
case '|':
if l.peek() == '=' {
l.offset++
l.token = "|="
lval.operator = OpModify
return tokUpdateOp
}
case '?':
if l.peek() == '/' {
l.offset++
if l.peek() == '/' {
l.offset++
l.token = "?//"
return tokDestAltOp
}
l.offset--
}
case '+':
if l.peek() == '=' {
l.offset++
l.token = "+="
lval.operator = OpUpdateAdd
return tokUpdateOp
}
case '-':
if l.peek() == '=' {
l.offset++
l.token = "-="
lval.operator = OpUpdateSub
return tokUpdateOp
}
case '*':
if l.peek() == '=' {
l.offset++
l.token = "*="
lval.operator = OpUpdateMul
return tokUpdateOp
}
case '/':
switch l.peek() {
case '=':
l.offset++
l.token = "/="
lval.operator = OpUpdateDiv
return tokUpdateOp
case '/':
l.offset++
if l.peek() == '=' {
l.offset++
l.token = "//="
lval.operator = OpUpdateAlt
return tokUpdateOp
}
l.token = "//"
lval.operator = OpAlt
return tokAltOp
}
case '%':
if l.peek() == '=' {
l.offset++
l.token = "%="
lval.operator = OpUpdateMod
return tokUpdateOp
}
case '=':
if l.peek() == '=' {
l.offset++
l.token = "=="
lval.operator = OpEq
return tokCompareOp
}
l.token = "="
lval.operator = OpAssign
return tokUpdateOp
case '!':
if l.peek() == '=' {
l.offset++
l.token = "!="
lval.operator = OpNe
return tokCompareOp
}
case '>':
if l.peek() == '=' {
l.offset++
l.token = ">="
lval.operator = OpGe
return tokCompareOp
}
l.token = ">"
lval.operator = OpGt
return tokCompareOp
case '<':
if l.peek() == '=' {
l.offset++
l.token = "<="
lval.operator = OpLe
return tokCompareOp
}
l.token = "<"
lval.operator = OpLt
return tokCompareOp
case '@':
if isIdent(l.peek(), true) {
l.token = l.source[l.offset-1 : l.scanIdent()]
lval.token = l.token
return tokFormat
}
case '"':
tok, str := l.scanString(l.offset - 1)
lval.token = str
return tok
default:
if ch >= utf8.RuneSelf {
r, size := utf8.DecodeRuneInString(l.source[l.offset-1:])
// -1 to adjust for first byte consumed by next()
l.offset += size - 1
l.token = string(r)
}
}
return int(ch)
}
func (l *lexer) next() (byte, bool) {
for {
ch := l.source[l.offset]
l.offset++
if ch == '#' {
if l.skipComment() {
return 0, true
}
} else if !isWhite(ch) {
return ch, false
} else if len(l.source) == l.offset {
return 0, true
}
}
}
func (l *lexer) skipComment() bool {
for {
switch l.peek() {
case 0:
return true
case '\\':
switch l.offset++; l.peek() {
case '\\', '\n':
l.offset++
case '\r':
if l.offset++; l.peek() == '\n' {
l.offset++
}
}
case '\n', '\r':
return false
default:
l.offset++
}
}
}
func (l *lexer) peek() byte {
if len(l.source) == l.offset {
return 0
}
return l.source[l.offset]
}
func (l *lexer) scanIdent() int {
for isIdent(l.peek(), true) {
l.offset++
}
return l.offset
}
func (l *lexer) scanIdentOrModule() (int, bool) {
index := l.scanIdent()
var isModule bool
if l.peek() == ':' {
l.offset++
if l.peek() == ':' {
l.offset++
if isIdent(l.peek(), false) {
l.offset++
index = l.scanIdent()
isModule = true
} else {
l.offset -= 2
}
} else {
l.offset--
}
}
return index, isModule
}
func (l *lexer) validVarName() bool {
if l.peek() != '$' {
return false
}
l.offset++
return isIdent(l.peek(), false) && l.scanIdent() == len(l.source)
}
const (
numberStateLead = iota
numberStateFloat
numberStateExpLead
numberStateExp
)
func (l *lexer) scanNumber(state int) int {
for {
switch state {
case numberStateLead, numberStateFloat:
if ch := l.peek(); isNumber(ch) {
l.offset++
} else {
switch ch {
case '.':
if state != numberStateLead {
l.offset++
return -l.offset
}
l.offset++
state = numberStateFloat
case 'e', 'E':
l.offset++
switch l.peek() {
case '-', '+':
l.offset++
}
state = numberStateExpLead
default:
if isIdent(ch, false) {
l.offset++
return -l.offset
}
return l.offset
}
}
case numberStateExpLead, numberStateExp:
if ch := l.peek(); !isNumber(ch) {
if isIdent(ch, false) {
l.offset++
return -l.offset
}
if state == numberStateExpLead {
return -l.offset
}
return l.offset
}
l.offset++
state = numberStateExp
default:
panic(state)
}
}
}
func (l *lexer) validNumber() bool {
ch := l.peek()
switch ch {
case '+', '-':
l.offset++
ch = l.peek()
}
state := numberStateLead
if ch == '.' {
l.offset++
ch = l.peek()
state = numberStateFloat
}
return isNumber(ch) && l.scanNumber(state) == len(l.source)
}
func (l *lexer) scanString(start int) (int, string) {
var decode bool
var controls int
unquote := func(src string, quote bool) (string, error) {
if !decode {
if quote {
return src, nil
}
return src[1 : len(src)-1], nil
}
var buf []byte
if !quote && controls == 0 {
buf = []byte(src)
} else {
buf = quoteAndEscape(src, quote, controls)
}
if err := json.Unmarshal(buf, &src); err != nil {
return "", err
}
return src, nil
}
for i := l.offset; i < len(l.source); i++ {
ch := l.source[i]
switch ch {
case '\\':
if i++; i >= len(l.source) {
break
}
switch l.source[i] {
case 'u':
for j := 1; j <= 4; j++ {
if i+j >= len(l.source) || !isHex(l.source[i+j]) {
l.offset = i + j
l.token = l.source[i-1 : l.offset]
return tokInvalidEscapeSequence, ""
}
}
i += 4
fallthrough
case '"', '/', '\\', 'b', 'f', 'n', 'r', 't':
decode = true
case '(':
if !l.inString {
l.inString = true
return tokStringStart, ""
}
if i == l.offset+1 {
l.offset += 2
l.inString = false
return tokStringQuery, ""
}
l.offset = i - 1
l.token = l.source[start:l.offset]
str, err := unquote(l.token, true)
if err != nil {
return tokInvalid, ""
}
return tokString, str
default:
l.offset = i + 1
l.token = l.source[l.offset-2 : l.offset]
return tokInvalidEscapeSequence, ""
}
case '"':
if !l.inString {
l.offset = i + 1
l.token = l.source[start:l.offset]
str, err := unquote(l.token, false)
if err != nil {
return tokInvalid, ""
}
return tokString, str
}
if i > l.offset {
l.offset = i
l.token = l.source[start:l.offset]
str, err := unquote(l.token, true)
if err != nil {
return tokInvalid, ""
}
return tokString, str
}
l.inString = false
l.offset = i + 1
return tokStringEnd, ""
default:
if !decode {
decode = ch > '~'
}
if ch < ' ' { // ref: unquoteBytes in encoding/json
controls++
}
}
}
l.offset = len(l.source)
l.token = ""
return tokUnterminatedString, ""
}
func quoteAndEscape(src string, quote bool, controls int) []byte {
size := len(src) + controls*5
if quote {
size += 2
}
buf := make([]byte, size)
var j int
if quote {
buf[0] = '"'
buf[len(buf)-1] = '"'
j++
}
for i := range len(src) {
if ch := src[i]; ch < ' ' {
const hex = "0123456789abcdef"
copy(buf[j:], `\u00`)
buf[j+4] = hex[ch>>4]
buf[j+5] = hex[ch&0xF]
j += 6
} else {
buf[j] = ch
j++
}
}
return buf
}
// ParseError represents a description of a query parsing error.
type ParseError struct {
Offset int // the error occurred after reading Offset bytes
Token string // the Token that caused the error (may be empty)
tokenType int
}
func (err *ParseError) Error() string {
switch err.tokenType {
case eof:
return "unexpected EOF"
case tokInvalid:
return "invalid token " + jsonMarshal(err.Token)
case tokInvalidEscapeSequence:
return `invalid escape sequence "` + err.Token + `" in string literal`
case tokUnterminatedString:
return "unterminated string literal"
default:
return "unexpected token " + jsonMarshal(err.Token)
}
}
func (l *lexer) Error(string) {
offset, token := l.offset, l.token
if l.tokenType != eof && l.tokenType < utf8.RuneSelf {
token = string(rune(l.tokenType))
}
l.err = &ParseError{offset, token, l.tokenType}
}
func isWhite(ch byte) bool {
switch ch {
case '\t', '\n', '\r', ' ':
return true
default:
return false
}
}
func isIdent(ch byte, tail bool) bool {
return 'a' <= ch && ch <= 'z' ||
'A' <= ch && ch <= 'Z' || ch == '_' ||
tail && isNumber(ch)
}
func isHex(ch byte) bool {
return 'a' <= ch && ch <= 'f' ||
'A' <= ch && ch <= 'F' ||
isNumber(ch)
}
func isNumber(ch byte) bool {
return '0' <= ch && ch <= '9'
}