ddql 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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