2019-07-27 04:42:08 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class SearchQueryParser < Parslet::Parser
|
2023-09-08 16:30:32 +02:00
|
|
|
SUPPORTED_PREFIXES = %w(
|
|
|
|
has
|
|
|
|
is
|
|
|
|
language
|
|
|
|
from
|
|
|
|
before
|
|
|
|
after
|
|
|
|
during
|
|
|
|
in
|
|
|
|
).freeze
|
|
|
|
|
2023-09-01 11:21:47 +02:00
|
|
|
# Efficiently matches disjoint strings
|
|
|
|
class StrList < Parslet::Atoms::Base
|
|
|
|
attr_reader :strings
|
|
|
|
|
|
|
|
def initialize(strings)
|
|
|
|
super()
|
|
|
|
|
|
|
|
@strings = strings
|
|
|
|
@pattern = Regexp.union(strings)
|
|
|
|
@min_length = strings.map(&:length).min
|
|
|
|
end
|
|
|
|
|
|
|
|
def error_msgs
|
|
|
|
@error_msgs ||= {
|
|
|
|
premature: 'Premature end of input',
|
|
|
|
failed: "Expected any of #{strings.inspect}, but got ",
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def try(source, context, _consume_all)
|
|
|
|
match = source.match(@pattern)
|
|
|
|
return succ(source.consume(match)) unless match.nil?
|
|
|
|
|
|
|
|
# Input ending early:
|
|
|
|
return context.err(self, source, error_msgs[:premature]) if source.chars_left < @min_length
|
|
|
|
|
|
|
|
# Expected something, but got something else instead:
|
|
|
|
error_pos = source.pos
|
|
|
|
context.err_at(self, source, [error_msgs[:failed], source.consume(@len)], error_pos)
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s_inner(_prec)
|
|
|
|
"[#{strings.map { |str| "'#{str}'" }.join(',')}]"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-01 10:15:27 +02:00
|
|
|
rule(:term) { match('[^\s]').repeat(1).as(:term) }
|
2019-08-16 13:00:30 +02:00
|
|
|
rule(:colon) { str(':') }
|
|
|
|
rule(:space) { match('\s').repeat(1) }
|
|
|
|
rule(:operator) { (str('+') | str('-')).as(:operator) }
|
2023-09-08 16:30:32 +02:00
|
|
|
rule(:prefix_operator) { StrList.new(SUPPORTED_PREFIXES) }
|
2023-09-01 10:15:27 +02:00
|
|
|
rule(:prefix) { prefix_operator.as(:prefix_operator) >> colon }
|
|
|
|
rule(:phrase) do
|
|
|
|
(str('"') >> match('[^"]').repeat.as(:phrase) >> str('"')) |
|
|
|
|
(match('[“”„]') >> match('[^“”„]').repeat.as(:phrase) >> match('[“”„]')) |
|
|
|
|
(str('«') >> match('[^«»]').repeat.as(:phrase) >> str('»')) |
|
|
|
|
(str('「') >> match('[^「」]').repeat.as(:phrase) >> str('」')) |
|
|
|
|
(str('《') >> match('[^《》]').repeat.as(:phrase) >> str('》'))
|
|
|
|
end
|
|
|
|
rule(:clause) { (operator.maybe >> prefix.maybe.as(:prefix) >> (phrase | term)).as(:clause) }
|
2019-08-16 13:00:30 +02:00
|
|
|
rule(:query) { (clause >> space.maybe).repeat.as(:query) }
|
2019-07-27 04:42:08 +02:00
|
|
|
root(:query)
|
|
|
|
end
|