ddql 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module DDQL
2
+ class ListOperator < Operator
3
+ def initialize(symbol, name, type, ordinal)
4
+ super(symbol, name, type, 4, false, :boolean, ordinal)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'agg_operator'
2
+
3
+ module DDQL
4
+ class LookupOperator < AggOperator
5
+ def initialize
6
+ super("LOOKUP BY", "Lookup", type: :infix, return_type: :string)
7
+ end
8
+
9
+ def parse(parser, token, expression: nil)
10
+ precedence = self.precedence
11
+ precedence -= 1 if right?
12
+ next_expression = parser.parse(precedence: precedence)
13
+ op_expression = token.as_hash
14
+
15
+ {
16
+ left: expression,
17
+ op: op_expression[:op],
18
+ right: next_expression.delete(:left) || next_expression,
19
+ }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,173 @@
1
+ module DDQL
2
+ class Operator
3
+ attr_reader :associativity, :symbol, :name, :type, :precedence, :return_type, :ordinal
4
+
5
+ def initialize(symbol, name, type, precedence, right, return_type, ordinal=0)
6
+ @symbol = symbol
7
+ @name = name
8
+ @type = type
9
+ @precedence = precedence
10
+ @associativity = right ? :right : :left
11
+ @return_type = return_type
12
+ @ordinal = ordinal
13
+ end
14
+
15
+ def boolean?
16
+ return_type == :boolean
17
+ end
18
+
19
+ def comparison?
20
+ (infix? || postfix?) && boolean?
21
+ end
22
+
23
+ def complex_comparison?
24
+ comparison? && !simple_comparison?
25
+ end
26
+
27
+ def infix?
28
+ @name == :infixoperator || @type == :infix
29
+ end
30
+
31
+ def left?
32
+ !right?
33
+ end
34
+
35
+ def math?
36
+ math_op?(symbol)
37
+ end
38
+
39
+ def parse(parser, token, expression: nil)
40
+ case type
41
+ when :infix; parse_infix(parser, token, expression)
42
+ when :prefix; parse_prefix(parser, token, expression)
43
+ when :postfix; parse_postfix(parser, token, expression)
44
+ else
45
+ raise "unsupported operator type[#{type}]"
46
+ end
47
+ end
48
+
49
+ def pattern
50
+ symbol
51
+ end
52
+
53
+ def postfix?
54
+ @name == :postfixoperator || @type == :postfix
55
+ end
56
+
57
+ def prefix?
58
+ @name == :prefixoperator || @type == :prefix
59
+ end
60
+
61
+ def register(hash)
62
+ hash[symbol] = self
63
+ hash
64
+ end
65
+
66
+ def right?
67
+ @associativity == :right
68
+ end
69
+
70
+ def simple_comparison?
71
+ case symbol
72
+ when "==", "=", "!=", "<=", "<", ">=", ">"
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ def type?(incoming)
80
+ type == incoming
81
+ end
82
+
83
+ private
84
+
85
+ def boolean_stmt(left, op, right)
86
+ # the following avoids unnecessarily deeply nested statements
87
+ while left.key?(:lstatement) && !left.key?(:rstatement)
88
+ left = left[:lstatement]
89
+ end
90
+ while right.key?(:lstatement) && !right.key?(:rstatement)
91
+ right = right[:lstatement]
92
+ end
93
+ {
94
+ lstatement: left,
95
+ boolean_operator: op[:op],
96
+ rstatement: right,
97
+ }
98
+ end
99
+
100
+ def math_op?(str)
101
+ case str
102
+ when '+', '-', '*', '/', '%', '^'
103
+ true
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ def merged_negation(left, right, op)
110
+ needs_statement_wrapper = lambda do |expr|
111
+ (expr.key?(:lstatement) && expr.key?(:op_not)) || !expr.key?(:lstatement)
112
+ end
113
+
114
+ left = {lstatement: left} if needs_statement_wrapper[left]
115
+ right = {rstatement: right} if needs_statement_wrapper[right]
116
+ left.merge(boolean_operator: op[:op]).merge(right)
117
+ end
118
+
119
+ def parse_infix(parser, token, expression)
120
+ precedence = self.precedence
121
+ precedence -= 1 if right?
122
+ next_expression = parser.parse(precedence: precedence)
123
+ op_expression = token.as_hash
124
+
125
+ if token.and? || token.or?
126
+ return boolean_stmt(expression, op_expression, next_expression)
127
+ end
128
+
129
+ if expression.key?(:op_not) || next_expression.key?(:op_not)
130
+ return merged_negation(expression, next_expression, op_expression)
131
+ end
132
+
133
+ if next_expression&.key?(:lstatement) && expression&.key?(:lstatement)
134
+ left = expression.merge(boolean_operator: op_expression[:op], rstatement: next_expression[:lstatement])
135
+ return next_expression.key?(:rstatement) ?
136
+ left.merge(
137
+ boolean_operator: next_expression[:boolean_operator][:op],
138
+ rstatement: next_expression[:rstatement]
139
+ ) :
140
+ left
141
+ end
142
+
143
+ expression = {left: expression} unless redundant?(expression, :left)
144
+ next_expression = {right: next_expression} unless redundant?(next_expression, :right)
145
+ expression.merge(op_expression).merge(next_expression)
146
+ end
147
+
148
+ def parse_postfix(parser, token, expression)
149
+ op_expression = token.as_hash
150
+ if expression && !expression.empty?
151
+ expression = {left: expression} unless expression.key?(:left)
152
+ expression.merge(op_expression)
153
+ else
154
+ op_expression
155
+ end
156
+ end
157
+
158
+ def parse_prefix(parser, token, _expression)
159
+ op_expression = token.type.as_hash(token.data)
160
+ next_expression = parser.parse(precedence: precedence)
161
+ if next_expression.key?(:lstatement) && !next_expression.key?(:rstatement)
162
+ next_expression = next_expression[:lstatement]
163
+ elsif next_expression.key?(:rstatement) && !next_expression.key?(:lstatement)
164
+ next_expression = next_expression[:rstatement]
165
+ end
166
+ op_expression[:op].merge(next_expression)
167
+ end
168
+
169
+ def redundant?(expression, *keys)
170
+ expression.keys == keys
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,113 @@
1
+ require 'singleton'
2
+
3
+ require_relative 'operator'
4
+ require_relative 'agg_operator'
5
+ require_relative 'coalesce_operator'
6
+ require_relative 'infix_float_map_operator'
7
+ require_relative 'infix_string_map_operator'
8
+ require_relative 'list_operator'
9
+ require_relative 'lookup_operator'
10
+ require_relative 'postfix_null_type_operator'
11
+
12
+ module DDQL
13
+ class Operators
14
+ include Singleton
15
+
16
+ attr_reader :cache
17
+
18
+ def self.float_map_ops
19
+ instance.cache.select { |_, v| v.is_a? InfixFloatMapOperator }
20
+ end
21
+
22
+ def self.operator_regex(operator_type)
23
+ ops = instance.cache.select { |k, v| v.type?(operator_type) }
24
+
25
+ if ops.empty?
26
+ # if there are no registered operators of the given type use negative
27
+ # lookahead to generate a regex pattern that can never be matched
28
+ return /(?!x)x/
29
+ else
30
+ ops.keys[1..-1].inject(ops.keys.first) { |a, e| Regexp.union(a, ops[e].pattern) }
31
+ end
32
+ end
33
+
34
+ def initialize
35
+ @cache = build_cache
36
+ end
37
+
38
+ private
39
+ def build_cache
40
+ cache = {}
41
+ Operator.new("==", "Double Equals", :infix, 3, false, :boolean, 0).register(cache)
42
+ Operator.new("=", "Single Equals", :infix, 3, false, :boolean, 0).register(cache)
43
+ Operator.new("!=", "Not Equals", :infix, 3, false, :boolean, 1).register(cache)
44
+ Operator.new("<=", "Less Than or Equals", :infix, 4, false, :boolean, 4).register(cache)
45
+ Operator.new("<", "Less Than", :infix, 4, false, :boolean, 2).register(cache)
46
+ Operator.new(">=", "Greater Than or Equals", :infix, 4, false, :boolean, 5).register(cache)
47
+ Operator.new(">", "Greater Than", :infix, 4, false, :boolean, 3).register(cache)
48
+ # Operator.new("GROUP BY", "Group By", :infix, 7, false, :double).register(cache)
49
+ Operator.new("CTN", "Contains", :infix, 4, false, :boolean, 6).register(cache)
50
+ Operator.new("STW", "Starts With", :infix, 4, false, :boolean, 7).register(cache)
51
+ Operator.new("IN", "In Any", :infix, 4, false, :boolean, 8).register(cache)
52
+ Operator.new("LCTN", "Contains", :infix, 4, false, :boolean, 6).register(cache)
53
+ Operator.new("ON", "On Date", :infix, 4, false, :boolean, 0).register(cache)
54
+ Operator.new("EPST", "On Or After Date", :infix, 4, false, :boolean, 1).register(cache)
55
+ Operator.new("PST", "After Date", :infix, 4, false, :boolean, 1).register(cache)
56
+ Operator.new("EPRE", "On Or Before Date", :infix, 4, false, :boolean, 1).register(cache)
57
+ Operator.new("PRE", "Before Date", :infix, 4, false, :boolean, 0).register(cache)
58
+ Operator.new("OR", "Or", :infix, 1, false, :boolean).register(cache)
59
+ Operator.new("AND", "And", :infix, 2, false, :boolean).register(cache)
60
+ Operator.new("IS NULL", "Is Null", :postfix, 9, false, :boolean, 0).register(cache)
61
+ Operator.new("IS NOT NULL", "Is Not Null", :postfix, 9, false, :boolean, 1).register(cache)
62
+ Operator.new("+", "Plus", :infix, 5, false, :double).register(cache)
63
+ Operator.new("-", "Minus", :infix, 5, false, :double).register(cache)
64
+ Operator.new("*", "Multiplied By", :infix, 6, false, :double).register(cache)
65
+ Operator.new("/", "Divided By", :infix, 6, false, :double).register(cache)
66
+ Operator.new("%", "Modulus", :infix, 6, false, :integer).register(cache)
67
+ Operator.new("^", "To the Power of", :infix, 7, true, :double).register(cache)
68
+ AggOperator.new("EXISTS", "Exists", return_type: :boolean).register(cache)
69
+ AggOperator.new("CNT", "Count", return_type: :integer).register(cache)
70
+ AggOperator.new("AVG", "Mean", return_type: :double).register(cache)
71
+ AggOperator.new("MED", "Median", return_type: :double).register(cache)
72
+ AggOperator.new("MAX", "Max", return_type: :double).register(cache)
73
+ AggOperator.new("MIN", "Min", return_type: :double).register(cache)
74
+ AggOperator.new("SUM", "Sum", return_type: :double).register(cache)
75
+ AggOperator.new("MERGE", "Merge", return_type: :string_list).register(cache)
76
+ LookupOperator.new.register(cache)
77
+ CoalesceOperator.new.register(cache)
78
+ # PNT & RATIO don't work and aren't used in DD PROD as of 2020-07-24
79
+ # AggOperator.new("PNT", "Percent", return_type: :double).register(cache)
80
+ # AggOperator.new("RATIO", "Ratio", return_type: :double).register(cache)
81
+ InfixStringMapOperator.new("ANY_MAP", "Has Any", 0).register(cache)
82
+ InfixStringMapOperator.new("ALL_MAP", "Has All", 1).register(cache)
83
+ InfixStringMapOperator.new("NONE_MAP", "Has None", 2).register(cache)
84
+ InfixFloatMapOperator.new("ANY_GREATER_THAN_FLOAT_MAP", "Has Any Greater Than", 1, :any, :gt).register(cache)
85
+ InfixFloatMapOperator.new("ALL_GREATER_THAN_FLOAT_MAP", "Has All Greater Than", 2, :all, :gt).register(cache)
86
+ InfixFloatMapOperator.new("NONE_GREATER_THAN_FLOAT_MAP", "Has None Greater Than", 3, :none, :gt).register(cache)
87
+ InfixFloatMapOperator.new("ANY_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Greater Than Or Equal To", 4, :any, :ge).register(cache)
88
+ InfixFloatMapOperator.new("ALL_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has All Greater Than Or Equal To", 5, :all, :ge).register(cache)
89
+ InfixFloatMapOperator.new("NONE_GREATER_THAN_OR_EQUAL_FLOAT_MAP", "Has None Greater Than Or Equal To", 6, :none, :ge).register(cache)
90
+ InfixFloatMapOperator.new("ANY_LESS_THAN_FLOAT_MAP", "Has Any Less Than", 7, :any, :lt).register(cache)
91
+ InfixFloatMapOperator.new("ALL_LESS_THAN_FLOAT_MAP", "Has All Less Than", 8, :all, :lt).register(cache)
92
+ InfixFloatMapOperator.new("NONE_LESS_THAN_FLOAT_MAP", "Has None Less Than", 9, :none, :lt).register(cache)
93
+ InfixFloatMapOperator.new("ANY_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has Any Less Than Or Equal To", 10, :any, :le).register(cache)
94
+ InfixFloatMapOperator.new("ALL_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has All Less Than Or Equal To", 11, :all, :le).register(cache)
95
+ InfixFloatMapOperator.new("NONE_LESS_THAN_OR_EQUAL_FLOAT_MAP", "Has None Less Than Or Equal To", 12, :none, :le).register(cache)
96
+ InfixFloatMapOperator.new("ANY_EQUAL_FLOAT_MAP", "Has Any Equal To", 13, :any, :eq).register(cache)
97
+ InfixFloatMapOperator.new("ALL_EQUAL_FLOAT_MAP", "Has All Equal To", 14, :all, :eq).register(cache)
98
+ InfixFloatMapOperator.new("NONE_EQUAL_FLOAT_MAP", "Has None Equal To", 15, :none, :eq).register(cache)
99
+ ListOperator.new("ANY", "Has Any", :infix, 0).register(cache)
100
+ ListOperator.new("ALL", "Has All", :infix, 1).register(cache)
101
+ ListOperator.new("NONE", "Has None", :infix, 2).register(cache)
102
+ ListOperator.new("EMPTY", "Is Empty", :postfix, 3).register(cache)
103
+ PostfixNullTypeOperator.new("NO_INFORMATION", :NoInformation, 99).register(cache)
104
+ PostfixNullTypeOperator.new("NOT_APPLICABLE", :NotApplicable, 101).register(cache)
105
+ PostfixNullTypeOperator.new("NOT_COLLECTED", :NotCollected, 103).register(cache)
106
+ PostfixNullTypeOperator.new("NOT_DISCLOSED", :NotDisclosed, 100).register(cache)
107
+ PostfixNullTypeOperator.new("NOT_MEANINGFUL", :NotMeaningful, 102).register(cache)
108
+ Operator.new("NOT", "Not", :prefix, 9, false, :boolean).register(cache)
109
+ Operator.new("YES", "Is True", :postfix, 9, false, :boolean, 0).register(cache)
110
+ Operator.new("NO", "Is False", :postfix, 9, false, :boolean, 1).register(cache)
111
+ end
112
+ end
113
+ end
@@ -1,282 +1,61 @@
1
1
  module DDQL
2
- class Parser < Parslet::Parser
3
- # comparison constants
4
- rule(:eq) { (str('==') | str('=')) }
5
- rule(:ge) { str('>=') }
6
- rule(:gt) { str('>') }
7
- rule(:is) { str('IS') }
8
- rule(:isnt) { str('IS NOT') }
9
- rule(:le) { str('<=') }
10
- rule(:lt) { str('<') }
11
- rule(:ne) { str('!=') }
12
- rule(:no) { str('NO') }
13
- rule(:null) { str('NULL') }
14
- rule(:yes) { str('YES') }
15
-
16
- # constants
17
- rule(:comma) { str(',') }
18
- rule(:digit) { match('[0-9]') }
19
- rule(:group_by) { str('GROUP BY') }
20
- rule(:spaces) { match('\s').repeat(1) }
21
- rule(:spaces?) { spaces.maybe }
22
- rule(:tick) { str("'") }
23
- rule(:string) { (tick >> (tick.absent? >> any).repeat.as(:string) >> tick) }
24
-
25
- # brackets
26
- rule(:lbracket) { str('[') }
27
- rule(:lparen) { spaces? >> str('(') >> spaces? }
28
- rule(:lsquiggly) { spaces? >> str('{') >> spaces? }
29
- rule(:rbracket) { str(']') }
30
- rule(:rparen) { spaces? >> str(')') >> spaces? }
31
- rule(:rsquiggly) { spaces? >> str('}') >> spaces? }
32
-
33
- # math ops
34
- rule(:math_add) { str('+').as(:op_add) }
35
- rule(:math_subtract) { str('-').as(:op_subtract) }
36
- rule(:math_multiply) { str('*').as(:op_multiply) }
37
- rule(:math_divide) { str('/').as(:op_divide) }
38
- rule(:math_mod) { str('%').as(:op_mod) }
39
- rule(:math_power) { str('^').as(:op_power) }
40
- rule(:math_operation) do
41
- math_add | math_subtract | math_multiply | math_divide | math_mod | math_power
42
- end
43
-
44
- # boolean operators
45
- rule(:bool_not) { spaces? >> str('NOT').as(:op_not) >> spaces? }
46
- rule(:or_operator) { spaces? >> str('OR').as(:op_or) >> spaces? }
47
- rule(:and_operator) { spaces? >> str('AND').as(:op_and) >> spaces? }
48
-
49
- # string operators
50
- rule(:ctn_operator) { spaces? >> (str('CTN') | str('LCTN')).as(:op_ctn) >> spaces? }
51
- rule(:stw_operator) { spaces? >> str('STW').as(:op_stw) >> spaces? }
52
-
53
- # date operators
54
- rule(:date_on_operator) { spaces? >> str('ON').as(:op_date_on) >> spaces? }
55
- rule(:date_after_operator) { spaces? >> str('PST').as(:op_date_after) >> spaces? }
56
- rule(:date_after_operator_or_on) { spaces? >> str('EPST').as(:op_date_after_or_on) >> spaces? }
57
- rule(:date_before_operator) { spaces? >> str('PRE').as(:op_date_before) >> spaces? }
58
- rule(:date_before_operator_or_on) { spaces? >> str('EPRE').as(:op_date_before_or_on) >> spaces? }
59
-
60
- # currency support
61
- rule(:currency_code) { match('[A-Z]').repeat(3) }
62
- rule(:currency) do
63
- tick.maybe >>
64
- (
65
- tick.absent? >>
66
- currency_code.as(:currency_code) >>
67
- str(':') >>
68
- (float_numeric | int_numeric).as(:currency_value)
69
- ) >>
70
- tick.maybe
71
- end
72
-
73
- # numbers
74
- rule(:float_numeric) do
75
- (str('-').maybe >> (
76
- str('0') | (match('[1-9]') >> digit.repeat)
77
- ) >> (
78
- str('.') >> digit.repeat(1)
79
- ) >> (
80
- match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)
81
- ).maybe
82
- ).as(:float)
83
- end
84
- rule(:float) do
85
- (tick.maybe >> (tick.absent? >> float_numeric >> tick)) | float_numeric
86
- end
87
- rule(:int_numeric) { (str('-').maybe >> digit.repeat(1)).as(:int) }
88
- rule(:int) do
89
- (tick.maybe >> (tick.absent? >> int_numeric >> tick)) | int_numeric
90
- end
91
-
92
- # special markers
93
- rule(:current_date) { str('$CURRENT_DATE').as(:current_date) }
94
- rule(:current_quarter) { str('$CURRENT_QUARTER').as(:current_quarter) }
95
- rule(:current_year) { str('$CURRENT_YEAR').as(:current_year) }
96
- rule(:relative_end_date) { str('$END_DATE').as(:relative_end_date) }
97
- rule(:relative_end_quarter) { str('$END_QUARTER').as(:relative_end_quarter) }
98
- rule(:relative_end_year) { str('$END_YEAR').as(:relative_end_year) }
99
- rule(:relative_start_date) { str('$START_DATE').as(:relative_start_date) }
100
- rule(:relative_start_quarter) { str('$START_QUARTER').as(:relative_start_quarter) }
101
- rule(:relative_start_year) { str('$START_YEAR').as(:relative_start_year) }
102
- rule(:special_marker) do
103
- (
104
- current_year | current_date | current_quarter |
105
- relative_end_date | relative_end_quarter | relative_end_year |
106
- relative_start_date | relative_start_quarter | relative_start_year
107
- ).as(:special_marker)
108
- end
109
-
110
- # nulls
111
- rule(:null_no_information) { str('NO_INFORMATION') }
112
- rule(:null_not_applicable) { str('NOT_APPLICABLE') }
113
- rule(:null_not_collected) { str('NOT_COLLECTED') }
114
- rule(:null_not_disclosed) { str('NOT_DISCLOSED') }
115
- rule(:null_not_meaningful) { str('NOT_MEANINGFUL') }
116
- rule(:null_value_type) do
117
- (
118
- null_no_information |
119
- null_not_applicable |
120
- null_not_collected |
121
- null_not_disclosed |
122
- null_not_meaningful
123
- ).as(:null_value_type)
124
- end
125
-
126
- # array operators
127
- rule(:arrays_all_operator) { str('ALL').as(:op_all) }
128
- rule(:arrays_any_operator) { str('ANY').as(:op_any) }
129
- rule(:arrays_in_operator) { str('IN').as(:op_in) }
130
- rule(:arrays_none_operator) { str('NONE').as(:op_none) }
131
- rule(:arrays_operator) do
132
- arrays_all_operator | arrays_any_operator | arrays_in_operator | arrays_none_operator
2
+ class Parser
3
+ class ParseException < StandardError
133
4
  end
134
5
 
135
- # comparison operators
136
- rule(:boolean_operator) { or_operator | and_operator }
137
- rule(:date_rel_operator) { date_on_operator | date_after_operator | date_after_operator_or_on | date_before_operator | date_before_operator_or_on }
138
- rule(:eq_operator) { eq.as(:op_eq) }
139
- rule(:ge_operator) { ge.as(:op_ge) }
140
- rule(:gt_operator) { gt.as(:op_gt) }
141
- rule(:is_not_null_operator) { (isnt >> spaces >> null).as(:op_is_not_null) }
142
- rule(:is_null_operator) { (is >> spaces >> null).as(:op_is_null) }
143
- rule(:is_operator) { is.as(:op_is) }
144
- rule(:le_operator) { le.as(:op_le) }
145
- rule(:lt_operator) { lt.as(:op_lt) }
146
- rule(:ne_operator) { ne.as(:op_ne) }
147
- rule(:no_operator) { no.as(:op_no) }
148
- rule(:relational_operator) { eq_operator | le_operator | ge_operator | lt_operator | gt_operator | ne_operator }
149
- rule(:string_rel_operator) { ctn_operator | stw_operator }
150
- rule(:yes_operator) { yes.as(:op_yes) }
151
-
152
- # aggregations
153
- rule(:avg_operator) { str('AVG').as(:op_avg) }
154
- rule(:cnt_operator) { str('CNT').as(:op_cnt) }
155
- rule(:ext_operator) { str('EXISTS').as(:op_exist) }
156
- rule(:max_operator) { str('MAX').as(:op_max) }
157
- rule(:med_operator) { str('MED').as(:op_med) }
158
- rule(:min_operator) { str('MIN').as(:op_min) }
159
- rule(:sum_operator) { str('SUM').as(:op_sum) }
160
- rule(:grouping_operator) { (max_operator | min_operator).as(:agg) }
161
- rule(:agg_operator) do
162
- (avg_operator | cnt_operator | ext_operator | med_operator | sum_operator).as(:agg)
163
- end
164
- rule(:aggregation_operator) do
165
- # Examples:
166
- # CNT {type: IssuerCase, fields: [], expression: ([CaseFlag] == 'Amber' AND [CaseFlag] == 'RED' AND [CaseFlag] == 'Green')} >= '0'
167
- # CNT {type: IssuerPerson, expression: [ipAssociationType] == 'Director'}
168
- (agg_operator >>
169
- lsquiggly >>
170
- (rsquiggly.absent? >> any).repeat(1).as(:sub_query_info) >>
171
- rsquiggly >>
172
- relational_operator.as(:op) >>
173
- value.as(:right)
174
- ) | (agg_operator >>
175
- lsquiggly >>
176
- (rsquiggly.absent? >> any).repeat(1).as(:sub_query_info) >>
177
- rsquiggly
178
- ) | (grouping_operator >>
179
- lsquiggly >>
180
- (rsquiggly.absent? >> any).repeat(1).as(:sub_query_info) >>
181
- rsquiggly >>
182
- spaces? >>
183
- group_by >>
184
- spaces? >>
185
- factor.as(:group_by)
186
- )
6
+ class NoTokenException < ParseException
7
+ def initialize
8
+ super 'No token found - ensure literals are surrounded by single quotes'
9
+ end
187
10
  end
188
11
 
189
- # sub-queries
190
- rule(:sub_query_type) do
191
- spaces? >> str('type:') >> spaces? >> (str('IssuerCase') | str('IssuerPerson') | str('Issuer')).as(:type) >> spaces?
192
- end
193
- rule(:sub_query_fields) do
194
- spaces? >> str('fields:') >> spaces? >> (factor | (lbracket >> spaces?.as(:factor) >> rbracket)).as(:fields) >> spaces?
195
- end
196
- rule(:sub_query_qualifier) do
197
- spaces? >> str('qualifier:') >> spaces? >> string.as(:assoc_qualifier)
198
- end
199
- rule(:sub_query_expression) do
200
- spaces? >> str('expression:') >> spaces? >> top.as(:expression)
201
- end
202
- rule(:sub_query_info_parsing) do
203
- (sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> sub_query_expression >> comma >> sub_query_fields) |
204
- (sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> sub_query_expression >> comma.absent?) |
205
- (sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> (sub_query_fields >> comma).maybe >> sub_query_expression) |
206
- (sub_query_fields >> comma >> sub_query_type >> (comma >> sub_query_qualifier).maybe >> comma >> sub_query_expression) |
207
- (sub_query_fields >> comma >> sub_query_expression >> comma >> sub_query_type >> (comma >> sub_query_qualifier).maybe) |
208
- (sub_query_expression >> comma >> sub_query_type >> (comma >> sub_query_qualifier).maybe >> (comma >> sub_query_fields).maybe) |
209
- (sub_query_expression >> comma >> (sub_query_fields >> comma).maybe >> sub_query_type >> (comma >> sub_query_qualifier).maybe) |
210
- (sub_query_type >> comma >> sub_query_fields) | # for MIN/MAX {...} GROUP BY [...]
211
- (sub_query_fields >> comma >> sub_query_type) # for MIN/MAX {...} GROUP BY [...]
12
+ def initialize(expression)
13
+ @expression = expression
14
+ @tokens = Lexer.lex(expression)
15
+ @depth = 0
212
16
  end
213
17
 
214
- # higher-level definitions
215
- rule(:operator) do
216
- (relational_operator | arrays_operator).as(:op)
18
+ def consume(token_type)
19
+ token = @tokens.poll
20
+ if token.nil? || token_type != token.type
21
+ message = "Expected token[#{token_type.name}], got #{token.nil? ? 'nil' : "[#{token.type.name}]"}\n"
22
+ if token
23
+ message += " #{@expression}\n"
24
+ message += " #{' ' * token.location}^"
25
+ end
26
+ raise ParseException, message
27
+ end
28
+ token
217
29
  end
218
30
 
219
- rule(:factor) do
220
- lbracket >> (
221
- match('[_0-9a-zA-Z&]').repeat(1).as(:factor)
222
- ) >> rbracket
223
- end
31
+ def parse(precedence: 0)
32
+ @depth += 1
33
+ token = @tokens.poll
34
+ raise NoTokenException if token.nil?
224
35
 
225
- rule(:val) do
226
- (currency | float | int | special_marker | string)
227
- end
36
+ expression = token.parse(self)
37
+ while precedence < next_precedence
38
+ token = @tokens.poll
39
+ expression = token.parse(self, expression: expression)
40
+ end
41
+ @depth -= 1
228
42
 
229
- rule(:value) do
230
- spaces? >> (val | factor) >> spaces?
231
- end
232
-
233
- rule(:math_operand) do
234
- spaces? >> (special_marker | float | int | factor | aggregation_operator) >> spaces?
235
- end
236
-
237
- rule(:equation) do
238
- (math_operand.as(:left) >> spaces? >> math_operation >> spaces? >> math_operand.as(:right)) |
239
- (math_operand.as(:left) >> spaces? >> math_operation >> spaces? >> equation.as(:right)) #|
240
- end
241
-
242
- rule(:factor_comparison) do
243
- (factor.as(:left) >> spaces >> is_not_null_operator.as(:op)) |
244
- (factor.as(:left) >> spaces >> is_null_operator.as(:op)) |
245
- (factor.as(:left) >> spaces >> is_operator.as(:op) >> spaces >> null_value_type.as(:right)) |
246
- (value.as(:left) >> operator >> value.as(:right)) |
247
- (factor.as(:left) >> (string_rel_operator | date_rel_operator).as(:op) >> string.as(:right)) |
248
- (factor.as(:left) >> spaces? >> (no_operator | yes_operator).as(:yes_no_op))
249
- end
43
+ if @depth == 0 && !peek.nil?
44
+ raise ParseException, "Unable to fully parse expression; token[#{peek}], possible cause: invalid operator"
45
+ end
250
46
 
251
- rule(:entity) do
252
- equation |
253
- sub_query_info_parsing |
254
- (
255
- bool_not.maybe >> (
256
- (lparen >> entity >> rparen) |
257
- aggregation_operator |
258
- (factor_comparison) |
259
- factor.as(:value_of) |
260
- special_marker |
261
- top
262
- )
263
- )
47
+ expression
264
48
  end
265
49
 
266
- rule(:statement) do
267
- spaces? >> (
268
- (lparen >> spaces? >> (top | entity | factor) >> spaces? >> rparen) |
269
- entity
270
- ) >> spaces?
50
+ def peek
51
+ @tokens.peek
271
52
  end
272
53
 
273
- rule(:top) do
274
- (statement.as(:lstatement) >> (boolean_operator.as(:boolean_operator) >> top.as(:rstatement))) |
275
- (statement.as(:lstatement) >> (math_operation.as(:math_operation) >> top.as(:rstatement))) |
276
- (statement.as(:lstatement) >> (operator.as(:operator) >> top.as(:rstatement))) |
277
- statement
54
+ private
55
+ def next_precedence
56
+ token = peek
57
+ return 0 if token.nil?
58
+ Operators.instance.cache[token.op_data]&.precedence || 0
278
59
  end
279
-
280
- root(:top)
281
60
  end
282
- end
61
+ end