Class: SpectatorSport::SearchQuery::Lexer

Inherits:
Object
  • Object
show all
Defined in:
app/models/spectator_sport/search_query/lexer.rb

Overview

Turns a query string into a flat array of Tokens. Knows nothing about grammar/precedence - that's the Parser's job.

Terms combine by simple juxtaposition (implicit AND, like Gmail search), or with the explicit AND / OR keywords:

label:a label:b            => a AND b
label:a AND label:b        => a AND b (same as juxtaposition, just explicit)
label:a OR label:b         => a OR b
{label:a label:b}          => a OR b
{label:key:v1 label:key:v2} => (key:v1 OR key:v2)
-label:a                   => NOT a
-(label:a label:b)         => NOT (a AND b)
created:2026-01-31         => created on that day
created:>=2026-01-31       => created on or after that day (also <, >, <=; same for updated:)

A leading "-" only negates when directly (no space) followed by "(", "{", "label:", "created:", or "updated:", matching Gmail's minus-operator convention. A "-" with no valid target directly after it is a no-op and is silently dropped, rather than raising a syntax error.

A word immediately followed by ":" that isn't one of the recognized prefixes (e.g. a typo like "labe:foo") raises a specific "unknown keyword" error instead of a generic "unexpected input" one.

Defined Under Namespace

Classes: Token

Constant Summary collapse

LABEL_PREFIX =
/label:/i
DATE_PREFIX =
/(created|updated):/i
DATE_OPERATOR =
/>=|<=|>|</
NOT_PREFIX =
/-(?=[({]|label:|created:|updated:)/i
AND_KEYWORD =
/AND\b/i
OR_KEYWORD =
/OR\b/i
KEYWORD_LOOKALIKE =
/([A-Za-z_][A-Za-z0-9_]*):/
DANGLING_NOT =
/-/
BAREWORD =
/[^\s(){}:]+/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input) ⇒ Lexer

Returns a new instance of Lexer.



47
48
49
# File 'app/models/spectator_sport/search_query/lexer.rb', line 47

def initialize(input)
  @scanner = StringScanner.new(input.to_s)
end

Class Method Details

.tokenize(input) ⇒ Object



43
44
45
# File 'app/models/spectator_sport/search_query/lexer.rb', line 43

def self.tokenize(input)
  new(input).tokenize
end

Instance Method Details

#tokenizeObject



51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/spectator_sport/search_query/lexer.rb', line 51

def tokenize
  tokens = []
  loop do
    @scanner.skip(/\s+/)
    break if @scanner.eos?

    token = next_token
    tokens << token if token
  end
  tokens
end