spot_feel 0.0.1

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,659 @@
1
+ grammar SpotFeel
2
+
3
+ rule start
4
+ expression_or_tests
5
+ end
6
+
7
+ rule expression_or_tests
8
+ simple_expression /
9
+ simple_unary_tests
10
+ end
11
+
12
+ #
13
+ # 1. expression =
14
+ # 1.a textual expression |
15
+ # 1.b boxed expression ;
16
+ #
17
+ rule expression
18
+ simple_expression
19
+ end
20
+
21
+ rule bracketed_expression
22
+ "(" __ expression __ ")" {
23
+ def eval(context={})
24
+ expression.eval(context)
25
+ end
26
+ }
27
+ end
28
+
29
+ #
30
+ # 2. textual expression =
31
+ # 2.a function definition | for expression | if expression | quantified expression |
32
+ # 2.b disjunction |
33
+ # 2.c conjunction |
34
+ # 2.d comparison |
35
+ # 2.e arithmetic expression |
36
+ # 2.f instance of |
37
+ # 2.g path expression |
38
+ # 2.h filter expression | function invocation |
39
+ # 2.i literal | simple positive unary test | name | "(" , textual expression , ")" ;
40
+ #
41
+
42
+ #
43
+ # 3. textual expressions = textual expression , { "," , textual expression } ;
44
+ #
45
+
46
+ #
47
+ # 4. arithmetic expression =
48
+ # 4.a addition | subtraction |
49
+ # 4.b multiplication | division |
50
+ # 4.c exponentiation |
51
+ # 4.d arithmetic negation ;
52
+ #
53
+ rule arithmetic_expression
54
+ addition /
55
+ subtraction /
56
+ multiplication /
57
+ division /
58
+ exponentiation /
59
+ arithmetic_negation /
60
+ bracketed_arithmetic_expression
61
+ end
62
+
63
+ rule bracketed_arithmetic_expression
64
+ "(" __ arithmetic_expression __ ")" {
65
+ def eval(context={})
66
+ arithmetic_expression.eval(context)
67
+ end
68
+ }
69
+ end
70
+
71
+ #
72
+ # 5. simple expression = arithmetic expression | simple value ;
73
+ #
74
+ # Note: added boxed_expression here to support list and context
75
+ #
76
+ rule simple_expression
77
+ expanded_simple_expression /
78
+ arithmetic_expression /
79
+ comparison /
80
+ simple_value /
81
+ bracketed_expression
82
+ end
83
+
84
+ #
85
+ # NOTE: these rules go beyond S-FEEL spec
86
+ #
87
+ rule expanded_simple_expression
88
+ if_expression /
89
+ comparison /
90
+ boxed_expression
91
+ end
92
+
93
+ #
94
+ # 6. simple expressions = simple expression , { "," , simple expression } ;
95
+ #
96
+ rule simple_expressions
97
+ expr:simple_expression more_exprs:(__ "," __ simple_expression)* <SimpleExpressions>
98
+ end
99
+
100
+ #
101
+ # 7. simple positive unary test =
102
+ # 7.a [ "<" | "<=" | ">" | ">=" ] , endpoint |
103
+ # 7.b interval ;
104
+ #
105
+ rule simple_positive_unary_test
106
+ head:((unary_operator / "not") __)? tail:endpoint <SimplePositiveUnaryTest> /
107
+ interval
108
+ end
109
+
110
+ rule unary_operator
111
+ "<=" /
112
+ ">=" /
113
+ "<" /
114
+ ">"
115
+ end
116
+
117
+ #
118
+ # 8. interval = ( open interval start | closed interval start ) , endpoint , ".." , endpoint , ( open interval end | closed interval end ) ;
119
+ #
120
+ rule interval
121
+ start_token:("(" / "]") __ first:endpoint __ ".." __ second:endpoint __ end_token:(")" / "[") <Interval> /
122
+ start_token:"[" __ first:endpoint __ ".." __ second:endpoint __ end_token:"]" <Interval>
123
+ end
124
+
125
+ #
126
+ # 9. open interval start = "(" | "]" ;
127
+ #
128
+ rule open_interval_start
129
+ "(" <OpenIntervalStart> /
130
+ "]" <OpenIntervalStart>
131
+ end
132
+
133
+ #
134
+ # 10. closed interval start = "[" ;
135
+ #
136
+ rule closed_interval_start
137
+ "[" <ClosedIntervalStart>
138
+ end
139
+
140
+ #
141
+ # 11. open interval end = ")" | "[" ;
142
+ #
143
+ rule open_interval_end
144
+ ")" <OpenIntervalEnd> /
145
+ "[" <OpenIntervalEnd>
146
+ end
147
+
148
+ #
149
+ # 12. closed interval end = "]" ;
150
+ #
151
+ rule closed_interval_end
152
+ "]" <ClosedIntervalEnd>
153
+ end
154
+
155
+ #
156
+ # 13. simple positive unary tests = simple positive unary test , { "," , simple positive unary test } ;
157
+ #
158
+ rule simple_positive_unary_tests
159
+ test:simple_positive_unary_test more_tests:(__ "," __ simple_positive_unary_test)* <SimplePositiveUnaryTests>
160
+ end
161
+
162
+ #
163
+ # 14. simple unary tests =
164
+ # 14.a simple positive unary tests |
165
+ # 14.b "not", "(", simple positive unary tests, ")" |
166
+ # 14.c "-";
167
+ #
168
+ rule simple_unary_tests
169
+ expr:simple_positive_unary_tests <SimpleUnaryTests> /
170
+ negate:"not" __ "(" __ expr:simple_positive_unary_tests __ ")" <SimpleUnaryTests> /
171
+ "-" <SimpleUnaryTests>
172
+ end
173
+
174
+ #
175
+ # 15. positive unary test = simple positive unary test | "null" ;
176
+ #
177
+ rule positive_unary_test
178
+ simple_positive_unary_test /
179
+ null_literal
180
+ end
181
+
182
+ #
183
+ # 16. positive unary tests = positive unary test , { "," , positive unary test } ;
184
+ #
185
+ rule positive_unary_tests
186
+ test:positive_unary_test more_tests:(__ "," __ positive_unary_test)* <PositiveUnaryTests>
187
+ end
188
+
189
+ #
190
+ # 17. unary tests =
191
+ # 17.a positive unary tests |
192
+ # 17.b "not", " (", positive unary tests, ")" |
193
+ # 17.c "-"
194
+ #
195
+
196
+ #
197
+ # 18. endpoint = simple value ;
198
+ #
199
+ rule endpoint
200
+ arithmetic_expression /
201
+ simple_value
202
+ end
203
+
204
+ #
205
+ # 19. simple value = qualified name | simple literal ;
206
+ #
207
+ # Note: dmn-eval = simple literal | qualified name | function invocation
208
+ #
209
+ rule simple_value
210
+ literal /
211
+ function_invocation /
212
+ qualified_name
213
+ end
214
+
215
+ #
216
+ # 20. qualified name = name , { "." , name } ;
217
+ #
218
+ rule qualified_name
219
+ head:name tail:(__ "." __ name)* <QualifiedName>
220
+ end
221
+
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
+ rule non_recursive_simple_expression_for_comparison
235
+ arithmetic_expression /
236
+ simple_value
237
+ end
238
+
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
+ #
261
+ # 25. exponentiation = expression, "\*\*", expression ;
262
+ #
263
+ rule exponentiation
264
+ head:non_recursive_simple_expression_for_arithmetic_expression __ "**" __ tail:expression <Exponentiation>
265
+ end
266
+
267
+ #
268
+ # 26. arithmetic negation = "-" , expression ;
269
+ #
270
+ rule arithmetic_negation
271
+ "not" __ "(" __ expr:expression __ ")" {
272
+ def eval(context={})
273
+ -expr.eval(context)
274
+ end
275
+ }
276
+ end
277
+
278
+ #
279
+ # 27. name = name start , { name part | additional name symbols } ;
280
+ #
281
+ rule name
282
+ head:name_start tail:(__ name_part)* <Name>
283
+ end
284
+
285
+ rule reserved_word
286
+ keyword /
287
+ date_time_keyword /
288
+ null_literal /
289
+ boolean_literal
290
+ end
291
+
292
+ #
293
+ # 28. name start = name start char, { name part char } ;
294
+ #
295
+ rule name_start
296
+ head:name_start_char tail:(name_part_char)* {
297
+ def eval(context={})
298
+ head + tail.map{|t| t[1]}.join("")
299
+ end
300
+ }
301
+ end
302
+
303
+ #
304
+ # 29. name part = name part char , { name part char } ;
305
+ #
306
+ rule name_part
307
+ head:name_part_char tail:(__ name_part_char)* {
308
+ def eval(context={})
309
+ head + tail.map{|t| t[1]}.join("")
310
+ end
311
+ }
312
+ end
313
+
314
+ #
315
+ # 30. name start char = "?" | [A-Z] | "\_" | [a-z] | [\uC0-\uD6] | [\uD8-\uF6] | [\uF8-\u2FF] | [\u370-\u37D] | [\u37F-\u1FFF] | [\u200C-\u200D] | [\u2070-\u218F] | [\u2C00-\u2FEF] | [\u3001-\uD7FF] | [\uF900-\uFDCF] | [\uFDF0-\uFFFD] | [\u10000-\uEFFFF] ;
316
+ #
317
+ rule name_start_char
318
+ [A-Z] / [a-z] / "_" / name_start_unicode_char
319
+ end
320
+
321
+ rule name_start_unicode_char
322
+ [\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]
323
+ end
324
+
325
+ #
326
+ # 31. name part char = name start char | digit | \uB7 | [\u0300-\u036F] | [\u203F-\u2040] ;
327
+ #
328
+ rule name_part_char
329
+ name_start_char /
330
+ digit /
331
+ name_part_unicode_char /
332
+ "'"
333
+ end
334
+
335
+ rule name_part_unicode_char
336
+ [\u0B70\u0300-\u036F\u203F-\u2040]
337
+ end
338
+
339
+ #
340
+ # 32. additional name symbols = "." | "/" | "-" | "’" | "+" | "\*" ;
341
+ #
342
+ rule additional_name_symbols
343
+ "." / "/" / "-" / "'" / "+" / "*"
344
+ end
345
+
346
+ #
347
+ # 33. literal = simple literal | "null" ;
348
+ #
349
+ rule literal
350
+ simple_literal /
351
+ null_literal
352
+ end
353
+
354
+ #
355
+ # 34. simple literal = numeric literal | string literal | boolean literal | date time literal ;
356
+ #
357
+ rule simple_literal
358
+ numeric_literal /
359
+ string_literal /
360
+ boolean_literal /
361
+ date_time_literal
362
+ end
363
+
364
+ #
365
+ # 35. string literal = '"' , { character – ('"' | vertical space) }, '"' ;
366
+ #
367
+ rule string_literal
368
+ '"' chars:double_string_character* '"' <StringLiteral> /
369
+ "'" chars:single_string_character* "'" <StringLiteral>
370
+ end
371
+
372
+ rule double_string_character
373
+ !('"' / "\\" / line_terminator) source_character /
374
+ "\\" escape_sequence /
375
+ line_continuation
376
+ end
377
+
378
+ rule single_string_character
379
+ !("'" / "\\" / line_terminator) source_character /
380
+ "\\" escape_sequence /
381
+ line_continuation
382
+ end
383
+
384
+ rule source_character
385
+ .
386
+ end
387
+
388
+ rule line_continuation
389
+ "\\" __ line_terminator_sequence __
390
+ end
391
+
392
+ rule escape_sequence
393
+ character_escape_sequence
394
+ end
395
+
396
+ rule character_escape_sequence
397
+ single_escape_character
398
+ end
399
+
400
+ rule single_escape_character
401
+ "'" / '"' / "\\"
402
+ end
403
+
404
+ rule line_terminator
405
+ line_terminator_sequence
406
+ end
407
+
408
+ rule line_terminator_sequence
409
+ "\n" / "\r" / "\r\n"
410
+ end
411
+
412
+ #
413
+ # 36. Boolean literal = "true" | "false" ;
414
+ #
415
+ rule boolean_literal
416
+ "true" <BooleanLiteral> /
417
+ "false" <BooleanLiteral>
418
+ end
419
+
420
+ #
421
+ # 37. numeric literal = [ "-" ] , ( digits , [ ".", digits ] | "." , digits ) ;
422
+ #
423
+ rule numeric_literal
424
+ '-'? digits ('.' digits)? <NumericLiteral>
425
+ end
426
+
427
+ rule null_literal
428
+ "null" <NullLiteral>
429
+ end
430
+
431
+ #
432
+ # 38. digit = [0-9] ;
433
+ #
434
+ rule digit
435
+ [0-9]
436
+ end
437
+
438
+ #
439
+ # 39. digits = digit , {digit} ;
440
+ #
441
+ rule digits
442
+ digit+
443
+ end
444
+
445
+ #
446
+ # 40. function invocation = expression , parameters ;
447
+ #
448
+ rule function_invocation
449
+ fn_name:(!reserved_word qualified_name) __ "(" __ params:(positional_parameters)? __ ")" <FunctionInvocation>
450
+ end
451
+
452
+ #
453
+ # 41. parameters = "(" , ( named parameters | positional parameters ) , ")" ;
454
+ #
455
+
456
+ #
457
+ # 42. named parameters = parameter name , ":" , expression , { "," , parameter name , ":" , expression } ;
458
+ #
459
+
460
+ #
461
+ # 43. parameter name = name ;
462
+ #
463
+
464
+ #
465
+ # 44. positional parameters = [ expression , { "," , expression } ] ;
466
+ #
467
+ rule positional_parameters
468
+ expression __ more_expressions:(__ "," __ expression)* <PositionalParameters>
469
+ end
470
+
471
+ #
472
+ # 45. path expression = expression , "." , name ;
473
+ #
474
+ rule path_expression
475
+ expression __ "." __ name:name <PathExpression>
476
+ end
477
+
478
+ #
479
+ # 46. for expression = "for" , name , "in" , expression { "," , name , "in" , expression } , "return" , expression ;
480
+ #
481
+ rule for_expression
482
+ "for" __ name:name __ "in" __ expression __ more_for_expressions:(__ "," __ name:name __ "in" __ expression)* __ "return" __ return_expression:expression <ForExpression>
483
+ end
484
+
485
+ #
486
+ # 47. if expression = "if" , expression , "then" , expression , "else" expression ;
487
+ #
488
+ rule if_expression
489
+ "if" __ condition:expression __ "then" __ true_case:expression __ "else" __ false_case:expression <IfExpression>
490
+ end
491
+
492
+ #
493
+ # 48. quantified expression = ("some" | "every") , name , "in" , expression , { name , "in" , expression } , "satisfies" , expression ;
494
+ #
495
+ rule quantified_expression
496
+ quantifier:("some" / "every") __ name:name __ "in" __ expression __ more_quantifiers:(__ name:name __ "in" __ expression)* __ "satisfies" __ satisfies:expression <QuantifiedExpression>
497
+ end
498
+
499
+ #
500
+ # 49. disjunction = expression , "or" , expression ;
501
+ #
502
+ rule disjunction
503
+ head:expression tail:(__ "or" __ expression)+ <Disjunction>
504
+ end
505
+
506
+ #
507
+ # 50. conjunction = expression , "and" , expression ;
508
+ #
509
+ rule conjunction
510
+ head:expression tail:(__ "and" __ expression)+ <Conjunction>
511
+ end
512
+
513
+ #
514
+ # 51. comparison =
515
+ # 51.a expression , ( "=" | "!=" | "<" | "<=" | ">" | ">=" ) , expression |
516
+ # 51.b expression , "between" , expression , "and" , expression |
517
+ # 51.c expression , "in" , positive unary test ;
518
+ # 51.d expression , "in" , " (", positive unary tests, ")" ;
519
+ #
520
+ rule comparison
521
+ left:non_recursive_simple_expression_for_comparison __ operator:comparision_operator __ right:expression <Comparison>
522
+ end
523
+
524
+ rule non_recursive_simple_expression_for_comparison
525
+ arithmetic_expression /
526
+ simple_value
527
+ end
528
+
529
+ #
530
+ # 52. filter expression = expression , "[" , expression , "]" ;
531
+ #
532
+ rule filter_expression
533
+ expression __ "[" __ filter:expression __ "]" <FilterExpression>
534
+ end
535
+
536
+ rule comparision_operator
537
+ "=" / "!=" / "<=" / ">=" / "<" / ">"
538
+ end
539
+
540
+ #
541
+ # 53. instance of = expression , "instance" , "of" , type ;
542
+ #
543
+ rule instance_of
544
+ expression __ "instance" __ "of" __ type <InstanceOf>
545
+ end
546
+
547
+ #
548
+ # 54. type = qualified name ;
549
+ #
550
+ rule type
551
+ qualified_name
552
+ end
553
+
554
+ #
555
+ # 55. boxed expression = list | function definition | context ;
556
+ #
557
+ # Note: function definition not supported yet
558
+ #
559
+ rule boxed_expression
560
+ list /
561
+ context
562
+ end
563
+
564
+ #
565
+ # 56. list = "[" [ expression , { "," , expression } ] , "]" ;
566
+ #
567
+ rule list
568
+ '[' __ list_entries __ ']' <List>
569
+ /
570
+ '[]' <List>
571
+ end
572
+
573
+ rule list_entries
574
+ expression more_expressions:(__ ',' __ expression)* <ListEntries>
575
+ end
576
+
577
+ #
578
+ # 57. function definition = "function" , "(" , [ formal parameter { "," , formal parameter } ] , ")" , [ "external" ] , expression ;
579
+ #
580
+ rule function_definition
581
+ "function" __ "(" __ formal_parameters:(formal_parameter_list)? __ ")" __ external:(__ "external")? __ expression <FunctionDefinition>
582
+ end
583
+
584
+ #
585
+ # 58. formal parameter = parameter name ;
586
+ #
587
+ rule formal_parameter
588
+ parameter_name:name <FormalParameter>
589
+ end
590
+
591
+ #
592
+ # 59. context = "{" , [context entry , { "," , context entry } ] , "}" ;
593
+ #
594
+ rule context
595
+ '{' __ entries:(context_entry_list)? __ '}' <Context>
596
+ /
597
+ '{}' <Context>
598
+ end
599
+
600
+ rule context_entry_list
601
+ context_entry tail:(__ ',' __ context_entry)* ','? <ContextEntryList>
602
+ end
603
+
604
+ #
605
+ # 60. context entry = key , ":" , expression ;
606
+ #
607
+ rule context_entry
608
+ context_key:expression __ ':' __ context_value:expression <ContextEntry>
609
+ end
610
+
611
+ #
612
+ # 61. key = name | string literal ;
613
+ #
614
+
615
+ #
616
+ # 62. date time literal = ( "date" | "time" | "date and time" | "duration" ) , "(" , string literal , ")" ;
617
+ #
618
+ rule date_time_literal
619
+ keyword:date_time_keyword __ "(" __ head:expression __ tail:("," __ expression)* __ ")" <DateTimeLiteral>
620
+ end
621
+
622
+ rule date_time_keyword
623
+ "date and time" /
624
+ "time" /
625
+ "date" /
626
+ "duration"
627
+ end
628
+
629
+ rule keyword
630
+ true_token /
631
+ false_token /
632
+ null_token /
633
+ not_token
634
+ end
635
+
636
+ rule not_token
637
+ "not"
638
+ end
639
+
640
+ rule true_token
641
+ "true" / "TRUE" / "True" !name_part_char
642
+ end
643
+
644
+ rule false_token
645
+ "false" / "FALSE" / "False" !name_part_char
646
+ end
647
+
648
+ rule null_token
649
+ "null" !name_part_char
650
+ end
651
+
652
+ rule __
653
+ white_space*
654
+ end
655
+
656
+ rule white_space
657
+ " " / "\t" / "\n" / "\r"
658
+ end
659
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpotFeel
4
+ VERSION = '0.0.1'
5
+ end
data/lib/spot_feel.rb ADDED
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spot_feel/version"
4
+
5
+ require "awesome_print"
6
+
7
+ require "active_support"
8
+ require "active_support/duration"
9
+ require "active_support/time"
10
+ require "active_support/core_ext/hash"
11
+ require "active_support/core_ext/object/json"
12
+ require "active_support/configurable"
13
+
14
+ require "treetop"
15
+ require "xmlhasher"
16
+
17
+ require "spot_feel/configuration"
18
+ require "spot_feel/nodes"
19
+ require "spot_feel/parser"
20
+
21
+ require "spot_feel/dmn"
22
+
23
+ module SpotFeel
24
+ class SyntaxError < StandardError; end
25
+ class EvaluationError < StandardError; end
26
+
27
+ def self.evaluate(expression_text, variables: {})
28
+ literal_expression = Dmn::LiteralExpression.new(text: expression_text)
29
+ raise SyntaxError, "Expression is not valid" unless literal_expression.valid?
30
+ literal_expression.evaluate(variables)
31
+ end
32
+
33
+ def self.test(input, unary_tests_text, variables: {})
34
+ unary_tests = Dmn::UnaryTests.new(text: unary_tests_text)
35
+ raise SyntaxError, "Unary tests are not valid" unless unary_tests.valid?
36
+ unary_tests.test(input, variables)
37
+ end
38
+
39
+ def self.decide(decision_id, definitions: nil, definitions_json: nil, definitions_xml: nil, variables: {})
40
+ if definitions_xml.present?
41
+ definitions = Dmn::Definitions.from_xml(definitions_xml)
42
+ elsif definitions_json.present?
43
+ definitions = Dmn::Definitions.from_json(definitions_json)
44
+ end
45
+ definitions.evaluate(decision_id, variables: variables)
46
+ end
47
+
48
+ def self.definitions_from_xml(xml)
49
+ Dmn::Definitions.from_xml(xml)
50
+ end
51
+
52
+ def self.definitions_from_json(json)
53
+ Dmn::Definitions.from_json(json)
54
+ end
55
+
56
+ def self.config
57
+ @config ||= Configuration.new
58
+ end
59
+
60
+ def self.configure
61
+ yield(config)
62
+ end
63
+ end