Class: Metasql::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/metasql/parser.rb

Overview

Parser for Metabase flavored query.

Constant Summary collapse

TOKENS =
[
  { token: :optional_begin, pattern: '[[' },
  { token: :optional_end,   pattern: ']]' },
  { token: :param_begin,    pattern: /(.*?)({{(?!{))(.*)/m },
  { token: :param_end,      pattern: '}}' }
].map(&:freeze).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(query:) ⇒ Parser

Returns a new instance of Parser.



22
23
24
# File 'lib/metasql/parser.rb', line 22

def initialize(query:)
  @query = query
end

Instance Attribute Details

#queryObject (readonly)

Returns the value of attribute query.



20
21
22
# File 'lib/metasql/parser.rb', line 20

def query
  @query
end

Class Method Details

.parse(query) ⇒ Object

Generate new Metasql::Query from supplied string.



12
13
14
15
16
17
18
# File 'lib/metasql/parser.rb', line 12

def self.parse(query)
  parser = new(query: query)
  tokenized = parser.tokenize
  parsed = parser.parse_tokens(tokens: tokenized)

  Query.new(parsed: parsed)
end

Instance Method Details

#parse_tokens(tokens:, depth: 0) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/metasql/parser.rb', line 46

def parse_tokens(tokens:, depth: 0)
  acc = []
  remains = tokens

  loop do
    token, *remains = remains

    if token.nil?
      if depth.positive?
        raise Metasql::InvalidQueryError,
              "Invalid query: found '[[' or '{{' with no matching ']]' or '}}'"
      end

      break acc
    end

    if %i[optional_begin param_begin].include?(token)
      parsed, remains = parse_tokens(tokens: remains, depth: depth + 1)
      acc << send(token.to_s.split('_').first, parsed)

      next [acc, remains]
    end

    if %i[optional_end param_end].include?(token)
      acc << { optional_end: ']]', param_end: '}}' }[token] unless depth.positive?

      break [acc, remains]
    end

    acc << token
  end
end

#tokenizeObject



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/metasql/parser.rb', line 26

def tokenize
  TOKENS.inject([query]) do |strs, token_definition|
    strs.map do |s|
      next [s] unless s.is_a?(String)

      acc = []
      target = s
      loop do
        break acc if target.nil?

        result = split_on_token(target: target, token: token_definition)
        break acc << target unless result.compact.size == 3

        acc.concat(result[0..1])
        target = result[2]
      end
    end.flatten
  end
end