feel 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/lib/feel/feel.treetop +91 -41
- data/lib/feel/nodes.rb +100 -59
- data/lib/feel/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e3c6cadc65effe99f563dcd6ceca765770c00205f0b5236795da8a59e18d39b
|
|
4
|
+
data.tar.gz: a3989725d4ea3f5d13319afd0576b332d7372bf17621c874985746fdffc09027
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8591d4a8b94eb173eb1b05e3d739dfccc23eabf40cabd9e98c0e5216cefd59e83deb6080d8c62288f2170254c21b7ea27a91cd858defe6a8ac2e7de4fea49b7e
|
|
7
|
+
data.tar.gz: 68c18813491cce3e569e864afe54b674b869c2c7fa9b616deb59ca1e39698c5c0cad1f813ad57eadf06df11787ead485bd261396d38bf0ac9c0aeaa65bff4018
|
data/lib/feel/feel.treetop
CHANGED
|
@@ -51,15 +51,91 @@ grammar FEEL
|
|
|
51
51
|
# 4.d arithmetic negation ;
|
|
52
52
|
#
|
|
53
53
|
rule arithmetic_expression
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
multiplication /
|
|
57
|
-
division /
|
|
54
|
+
additive_operation /
|
|
55
|
+
multiplicative_operation /
|
|
58
56
|
exponentiation /
|
|
59
57
|
arithmetic_negation /
|
|
60
58
|
bracketed_arithmetic_expression
|
|
61
59
|
end
|
|
62
60
|
|
|
61
|
+
# Precedence hierarchy used within operator rules (low to high):
|
|
62
|
+
# additive > multiplicative > exponential > atom
|
|
63
|
+
|
|
64
|
+
rule additive_expression
|
|
65
|
+
additive_operation /
|
|
66
|
+
multiplicative_expression
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
rule multiplicative_expression
|
|
70
|
+
multiplicative_operation /
|
|
71
|
+
exponential_expression
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# 21. addition = expression , "+" , expression ;
|
|
76
|
+
# 22. subtraction = expression , "-" , expression ;
|
|
77
|
+
#
|
|
78
|
+
rule additive_operation
|
|
79
|
+
head:multiplicative_expression tail:(__ op:("+" / "-") __ multiplicative_expression)+ {
|
|
80
|
+
def eval(context={})
|
|
81
|
+
result = head.eval(context)
|
|
82
|
+
tail.elements.each do |e|
|
|
83
|
+
operand = e.multiplicative_expression.eval(context)
|
|
84
|
+
if e.op.text_value == "+"
|
|
85
|
+
return nil if result.nil? || operand.nil?
|
|
86
|
+
result = result + operand
|
|
87
|
+
else
|
|
88
|
+
return nil if result.nil? || operand.nil?
|
|
89
|
+
result = result - operand
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
result
|
|
93
|
+
end
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#
|
|
98
|
+
# 23. multiplication = expression , "\*" , expression ;
|
|
99
|
+
# 24. division = expression , "/" , expression ;
|
|
100
|
+
#
|
|
101
|
+
rule multiplicative_operation
|
|
102
|
+
head:exponential_expression tail:(__ op:("*" !"*" / "/") __ exponential_expression)+ {
|
|
103
|
+
def eval(context={})
|
|
104
|
+
result = head.eval(context)
|
|
105
|
+
tail.elements.each do |e|
|
|
106
|
+
operand = e.exponential_expression.eval(context)
|
|
107
|
+
if e.op.text_value.start_with?("*")
|
|
108
|
+
return nil if result.nil? || operand.nil?
|
|
109
|
+
result = result * operand
|
|
110
|
+
else
|
|
111
|
+
return nil if result.nil? || operand.nil?
|
|
112
|
+
result = result / operand
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
result
|
|
116
|
+
end
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
rule exponential_expression
|
|
121
|
+
exponentiation /
|
|
122
|
+
arithmetic_atom
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
rule arithmetic_atom
|
|
126
|
+
arithmetic_negation /
|
|
127
|
+
bracketed_additive_expression /
|
|
128
|
+
simple_value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
rule bracketed_additive_expression
|
|
132
|
+
"(" __ additive_expression __ ")" {
|
|
133
|
+
def eval(context={})
|
|
134
|
+
additive_expression.eval(context)
|
|
135
|
+
end
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
63
139
|
rule bracketed_arithmetic_expression
|
|
64
140
|
"(" __ arithmetic_expression __ ")" {
|
|
65
141
|
def eval(context={})
|
|
@@ -219,49 +295,16 @@ grammar FEEL
|
|
|
219
295
|
head:name tail:(__ "." __ name)* <QualifiedName>
|
|
220
296
|
end
|
|
221
297
|
|
|
222
|
-
#
|
|
223
|
-
# 21. addition = expression , "+" , expression ;
|
|
224
|
-
#
|
|
225
|
-
rule addition
|
|
226
|
-
head:non_recursive_simple_expression_for_arithmetic_expression __ "+" __ tail:expression <Addition>
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
rule non_recursive_simple_expression_for_arithmetic_expression
|
|
230
|
-
bracketed_arithmetic_expression /
|
|
231
|
-
simple_value
|
|
232
|
-
end
|
|
233
|
-
|
|
234
298
|
rule non_recursive_simple_expression_for_comparison
|
|
235
299
|
arithmetic_expression /
|
|
236
300
|
simple_value
|
|
237
301
|
end
|
|
238
302
|
|
|
239
|
-
#
|
|
240
|
-
# 22. subtraction = expression , "-" , expression ;
|
|
241
|
-
#
|
|
242
|
-
rule subtraction
|
|
243
|
-
head:non_recursive_simple_expression_for_arithmetic_expression __ "-" __ tail:expression <Subtraction>
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
#
|
|
247
|
-
# 23. multiplication = expression , "\*" , expression ;
|
|
248
|
-
#
|
|
249
|
-
rule multiplication
|
|
250
|
-
head:non_recursive_simple_expression_for_arithmetic_expression __ "*" __ tail:expression <Multiplication>
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
#
|
|
254
|
-
# 24. division = expression , "/" , expression ;
|
|
255
|
-
#
|
|
256
|
-
rule division
|
|
257
|
-
head:non_recursive_simple_expression_for_arithmetic_expression __ "/" __ tail:expression <Division>
|
|
258
|
-
end
|
|
259
|
-
|
|
260
303
|
#
|
|
261
304
|
# 25. exponentiation = expression, "\*\*", expression ;
|
|
262
305
|
#
|
|
263
306
|
rule exponentiation
|
|
264
|
-
head:
|
|
307
|
+
head:arithmetic_atom __ "**" __ tail:exponential_expression <Exponentiation>
|
|
265
308
|
end
|
|
266
309
|
|
|
267
310
|
#
|
|
@@ -270,7 +313,9 @@ grammar FEEL
|
|
|
270
313
|
rule arithmetic_negation
|
|
271
314
|
"-" __ "(" __ expr:expression __ ")" {
|
|
272
315
|
def eval(context={})
|
|
273
|
-
|
|
316
|
+
val = expr.eval(context)
|
|
317
|
+
return nil if val.nil?
|
|
318
|
+
-val
|
|
274
319
|
end
|
|
275
320
|
}
|
|
276
321
|
end
|
|
@@ -370,9 +415,14 @@ grammar FEEL
|
|
|
370
415
|
numeric_literal /
|
|
371
416
|
string_literal /
|
|
372
417
|
boolean_literal /
|
|
418
|
+
at_literal /
|
|
373
419
|
date_time_literal
|
|
374
420
|
end
|
|
375
421
|
|
|
422
|
+
rule at_literal
|
|
423
|
+
"@" string_literal <AtLiteral>
|
|
424
|
+
end
|
|
425
|
+
|
|
376
426
|
#
|
|
377
427
|
# 35. string literal = '"' , { character - ('"' | vertical space) }, '"' ;
|
|
378
428
|
#
|
|
@@ -453,7 +503,7 @@ grammar FEEL
|
|
|
453
503
|
# 40. function invocation = expression , parameters ;
|
|
454
504
|
#
|
|
455
505
|
rule function_invocation
|
|
456
|
-
fn_name:(!reserved_word qualified_name) __ "(" __ params:(positional_parameters)? __ ")" <FunctionInvocation>
|
|
506
|
+
fn_name:(!reserved_word qualified_name) __ "(" __ params:(positional_parameters)? __ ")" property:(__ "." __ name)? <FunctionInvocation>
|
|
457
507
|
end
|
|
458
508
|
|
|
459
509
|
#
|
|
@@ -623,7 +673,7 @@ grammar FEEL
|
|
|
623
673
|
# 62. date time literal = ( "date" | "time" | "date and time" | "duration" ) , "(" , string literal , ")" ;
|
|
624
674
|
#
|
|
625
675
|
rule date_time_literal
|
|
626
|
-
keyword:date_time_keyword __ "(" __ head:expression __ tail:("," __ expression)* __ ")" <DateTimeLiteral>
|
|
676
|
+
keyword:date_time_keyword __ "(" __ head:expression __ tail:("," __ expression)* __ ")" property:(__ "." __ name)? <DateTimeLiteral>
|
|
627
677
|
end
|
|
628
678
|
|
|
629
679
|
rule date_time_keyword
|
data/lib/feel/nodes.rb
CHANGED
|
@@ -19,6 +19,51 @@ module FEEL
|
|
|
19
19
|
qualified_names.to_a
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def access_property(result, property_name)
|
|
23
|
+
case result
|
|
24
|
+
when DateTime
|
|
25
|
+
case property_name
|
|
26
|
+
when "year" then result.year
|
|
27
|
+
when "month" then result.month
|
|
28
|
+
when "day" then result.day
|
|
29
|
+
when "weekday" then result.cwday
|
|
30
|
+
when "hour" then result.hour
|
|
31
|
+
when "minute" then result.min
|
|
32
|
+
when "second" then result.sec
|
|
33
|
+
end
|
|
34
|
+
when Date
|
|
35
|
+
case property_name
|
|
36
|
+
when "year" then result.year
|
|
37
|
+
when "month" then result.month
|
|
38
|
+
when "day" then result.day
|
|
39
|
+
when "weekday" then result.cwday
|
|
40
|
+
end
|
|
41
|
+
when Time
|
|
42
|
+
case property_name
|
|
43
|
+
when "hour" then result.hour
|
|
44
|
+
when "minute" then result.min
|
|
45
|
+
when "second" then result.sec
|
|
46
|
+
when "time offset" then result.utc_offset
|
|
47
|
+
when "timezone" then result.zone
|
|
48
|
+
end
|
|
49
|
+
when ActiveSupport::Duration
|
|
50
|
+
case property_name
|
|
51
|
+
when "years" then result.parts[:years] || 0
|
|
52
|
+
when "months" then result.parts[:months] || 0
|
|
53
|
+
when "days" then result.parts[:days] || 0
|
|
54
|
+
when "hours" then result.parts[:hours] || 0
|
|
55
|
+
when "minutes" then result.parts[:minutes] || 0
|
|
56
|
+
when "seconds" then result.parts[:seconds] || 0
|
|
57
|
+
end
|
|
58
|
+
when Hash
|
|
59
|
+
if result.key?(property_name.to_sym)
|
|
60
|
+
result[property_name.to_sym]
|
|
61
|
+
else
|
|
62
|
+
result[property_name]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
22
67
|
def raise_evaluation_error(missing_name, ctx = {})
|
|
23
68
|
names = qualified_names_in_context(ctx)
|
|
24
69
|
checker = DidYouMean::SpellChecker.new(dictionary: names)
|
|
@@ -84,13 +129,13 @@ module FEEL
|
|
|
84
129
|
|
|
85
130
|
case operator
|
|
86
131
|
when "<"
|
|
87
|
-
->(input) { input < endpoint }
|
|
132
|
+
->(input) { input.nil? || endpoint.nil? ? nil : input < endpoint }
|
|
88
133
|
when "<="
|
|
89
|
-
->(input) { input <= endpoint }
|
|
134
|
+
->(input) { input.nil? || endpoint.nil? ? nil : input <= endpoint }
|
|
90
135
|
when ">"
|
|
91
|
-
->(input) { input > endpoint }
|
|
136
|
+
->(input) { input.nil? || endpoint.nil? ? nil : input > endpoint }
|
|
92
137
|
when ">="
|
|
93
|
-
->(input) { input >= endpoint }
|
|
138
|
+
->(input) { input.nil? || endpoint.nil? ? nil : input >= endpoint }
|
|
94
139
|
else
|
|
95
140
|
->(input) { input == endpoint }
|
|
96
141
|
end
|
|
@@ -112,16 +157,17 @@ module FEEL
|
|
|
112
157
|
finish = end_token.text_value
|
|
113
158
|
first_val = first.eval(context)
|
|
114
159
|
second_val = second.eval(context)
|
|
160
|
+
return ->(_input) { nil } if first_val.nil? || second_val.nil?
|
|
115
161
|
|
|
116
162
|
case [start, finish]
|
|
117
163
|
when ["(", ")"]
|
|
118
|
-
->(input) { first_val < input && input < second_val }
|
|
164
|
+
->(input) { input.nil? ? nil : first_val < input && input < second_val }
|
|
119
165
|
when ["[", "]"]
|
|
120
|
-
->(input) { first_val <= input && input <= second_val }
|
|
166
|
+
->(input) { input.nil? ? nil : first_val <= input && input <= second_val }
|
|
121
167
|
when ["(", "]"]
|
|
122
|
-
->(input) { first_val < input && input <= second_val }
|
|
168
|
+
->(input) { input.nil? ? nil : first_val < input && input <= second_val }
|
|
123
169
|
when ["[", ")"]
|
|
124
|
-
->(input) { first_val <= input && input < second_val }
|
|
170
|
+
->(input) { input.nil? ? nil : first_val <= input && input < second_val }
|
|
125
171
|
end
|
|
126
172
|
end
|
|
127
173
|
end
|
|
@@ -232,11 +278,15 @@ module FEEL
|
|
|
232
278
|
initial_value = context_get(context, head_name)
|
|
233
279
|
|
|
234
280
|
# Process each segment in the tail, evaluating names to handle backticks
|
|
235
|
-
tail.elements.inject(initial_value) do |
|
|
236
|
-
return nil unless
|
|
281
|
+
tail.elements.inject(initial_value) do |value, element|
|
|
282
|
+
return nil unless value
|
|
237
283
|
|
|
238
284
|
key = element.name.eval(context)
|
|
239
|
-
|
|
285
|
+
if value.respond_to?(:key?)
|
|
286
|
+
context_get(value, key, root: context)
|
|
287
|
+
else
|
|
288
|
+
access_property(value, key)
|
|
289
|
+
end
|
|
240
290
|
end
|
|
241
291
|
end
|
|
242
292
|
end
|
|
@@ -258,50 +308,14 @@ module FEEL
|
|
|
258
308
|
end
|
|
259
309
|
|
|
260
310
|
#
|
|
261
|
-
#
|
|
311
|
+
# 25. exponentiation = expression, "\*\*", expression ;
|
|
262
312
|
#
|
|
263
|
-
class
|
|
313
|
+
class Exponentiation < Node
|
|
264
314
|
def eval(context = {})
|
|
265
315
|
head_val = head.eval(context)
|
|
266
316
|
tail_val = tail.eval(context)
|
|
267
317
|
return nil if head_val.nil? || tail_val.nil?
|
|
268
|
-
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
#
|
|
273
|
-
# 22. subtraction = expression , "-" , expression ;
|
|
274
|
-
#
|
|
275
|
-
class Subtraction < Node
|
|
276
|
-
def eval(context = {})
|
|
277
|
-
head.eval(context) - tail.eval(context)
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
#
|
|
282
|
-
# 23. multiplication = expression , "\*" , expression ;
|
|
283
|
-
#
|
|
284
|
-
class Multiplication < Node
|
|
285
|
-
def eval(context = {})
|
|
286
|
-
head.eval(context) * tail.eval(context)
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
#
|
|
291
|
-
# 24. division = expression , "/" , expression ;
|
|
292
|
-
#
|
|
293
|
-
class Division < Node
|
|
294
|
-
def eval(context = {})
|
|
295
|
-
head.eval(context) / tail.eval(context)
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
#
|
|
300
|
-
# 25. exponentiation = expression, "\*\*", expression ;
|
|
301
|
-
#
|
|
302
|
-
class Exponentiation < Node
|
|
303
|
-
def eval(context = {})
|
|
304
|
-
head.eval(context) ** tail.eval(context)
|
|
318
|
+
head_val ** tail_val
|
|
305
319
|
end
|
|
306
320
|
end
|
|
307
321
|
|
|
@@ -427,6 +441,23 @@ module FEEL
|
|
|
427
441
|
end
|
|
428
442
|
end
|
|
429
443
|
|
|
444
|
+
class AtLiteral < Node
|
|
445
|
+
def eval(_context = {})
|
|
446
|
+
value = string_literal.eval
|
|
447
|
+
return nil if value.nil?
|
|
448
|
+
case value
|
|
449
|
+
when /\AP/
|
|
450
|
+
ActiveSupport::Duration.parse(value)
|
|
451
|
+
when /\A\d{4}-\d{2}-\d{2}T/
|
|
452
|
+
DateTime.parse(value)
|
|
453
|
+
when /\A\d{4}-\d{2}-\d{2}\z/
|
|
454
|
+
Date.parse(value)
|
|
455
|
+
when /\A\d{2}:\d{2}/
|
|
456
|
+
Time.parse(value)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
430
461
|
#
|
|
431
462
|
# 38. digit = [0-9] ;
|
|
432
463
|
#
|
|
@@ -449,7 +480,11 @@ module FEEL
|
|
|
449
480
|
|
|
450
481
|
args = params.present? ? params.eval(context) : []
|
|
451
482
|
|
|
452
|
-
fn.call(*args)
|
|
483
|
+
result = fn.call(*args)
|
|
484
|
+
if defined?(property) && property.present?
|
|
485
|
+
result = access_property(result, property.name.eval(context))
|
|
486
|
+
end
|
|
487
|
+
result
|
|
453
488
|
end
|
|
454
489
|
end
|
|
455
490
|
|
|
@@ -549,13 +584,14 @@ module FEEL
|
|
|
549
584
|
#
|
|
550
585
|
class Comparison < Node
|
|
551
586
|
def eval(context = {})
|
|
587
|
+
left_val = left.eval(context)
|
|
588
|
+
right_val = right.eval(context)
|
|
552
589
|
case operator.text_value
|
|
553
|
-
when "<"
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
when "
|
|
557
|
-
when "
|
|
558
|
-
when "=" then left.eval(context) == right.eval(context)
|
|
590
|
+
when "<", "<=", ">=", ">"
|
|
591
|
+
return nil if left_val.nil? || right_val.nil?
|
|
592
|
+
left_val.send(operator.text_value, right_val)
|
|
593
|
+
when "!=" then left_val != right_val
|
|
594
|
+
when "=" then left_val == right_val
|
|
559
595
|
end
|
|
560
596
|
end
|
|
561
597
|
end
|
|
@@ -686,7 +722,7 @@ module FEEL
|
|
|
686
722
|
class ContextEntryList < Node
|
|
687
723
|
def eval(context = {})
|
|
688
724
|
context_entries.inject({}) do |hash, entry|
|
|
689
|
-
hash.merge(entry.eval(context))
|
|
725
|
+
hash.merge(entry.eval(context.merge(hash)))
|
|
690
726
|
end
|
|
691
727
|
end
|
|
692
728
|
|
|
@@ -717,7 +753,7 @@ module FEEL
|
|
|
717
753
|
return nil if head_val.nil?
|
|
718
754
|
return head_val if head_val.is_a?(ActiveSupport::Duration) || head_val.is_a?(DateTime) || head_val.is_a?(Date) || head_val.is_a?(Time)
|
|
719
755
|
|
|
720
|
-
case keyword.text_value
|
|
756
|
+
result = case keyword.text_value
|
|
721
757
|
when "date and time"
|
|
722
758
|
DateTime.parse(head_val)
|
|
723
759
|
when "date"
|
|
@@ -731,6 +767,11 @@ module FEEL
|
|
|
731
767
|
ActiveSupport::Duration.parse(head_val)
|
|
732
768
|
end
|
|
733
769
|
end
|
|
770
|
+
|
|
771
|
+
if defined?(property) && property.present?
|
|
772
|
+
result = access_property(result, property.name.eval(context))
|
|
773
|
+
end
|
|
774
|
+
result
|
|
734
775
|
end
|
|
735
776
|
|
|
736
777
|
def duration_range(start_date, end_date)
|
data/lib/feel/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: feel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Connected Bits
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-03-16 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activemodel
|