rudelo 0.1.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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +60 -0
- data/Rakefile +1 -0
- data/SET_LOGIC_MATCHER.md +100 -0
- data/lib/rudelo/matchers/set_logic.rb +68 -0
- data/lib/rudelo/parsers/set_logic_parser.rb +88 -0
- data/lib/rudelo/parsers/set_logic_transform.rb +160 -0
- data/lib/rudelo/parsers/set_value_parser.rb +68 -0
- data/lib/rudelo/parsers/set_value_transform.rb +10 -0
- data/lib/rudelo/version.rb +3 -0
- data/lib/rudelo.rb +6 -0
- data/rudelo.gemspec +26 -0
- data/spec/matchers/set_logic_spec.rb +59 -0
- data/spec/parsers/set_logic_parser_spec.rb +147 -0
- data/spec/parsers/set_logic_transform_spec.rb +269 -0
- data/spec/parsers/set_value_parser_spec.rb +89 -0
- data/spec/parsers/set_value_transform_spec.rb +21 -0
- data/spec/spec_helper.rb +17 -0
- metadata +160 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Michael Johnston
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Rudelo
|
2
|
+
|
3
|
+
Provides Rufus::Decision::Matchers::SetLogic, for use with [rufus-decision][1]
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
In Gemfile:
|
8
|
+
|
9
|
+
# until 1.4.0 is published
|
10
|
+
gem 'rufus-decision', git: 'https://github.com/jmettraux/rufus-decision.git'
|
11
|
+
|
12
|
+
gem 'rudelo', git: "git://github.com/lastobelus/rudelo.git"
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
TABLE = Rufus::Decision::Table.new(%{
|
21
|
+
in:group, out:situation
|
22
|
+
$(bob jeff mary alice ralph) & $in #= 2, company
|
23
|
+
$(bob jeff mary alice ralph) & $in same-as $in #= 3, crowd
|
24
|
+
$(bob jeff mary alice ralph) >= $in #> 3, exclusive-party
|
25
|
+
$(bob jeff mary alice ralph) & $in < $in #> 5, PARTY!
|
26
|
+
$(bob jeff mary alice ralph) & $in < $in #> 3, party
|
27
|
+
})
|
28
|
+
TABLE.matchers.unshift Rudelo::Matchers::SetLogic.new
|
29
|
+
|
30
|
+
table.transform({'group' => "bob alice"})
|
31
|
+
#=> {'group' => "bob alice", 'situation' => "company"}
|
32
|
+
|
33
|
+
table.transform({'group' => "bob alice jeff"})
|
34
|
+
#=> {'group' => "bob alice jeff", 'situation' => "crowd"})
|
35
|
+
|
36
|
+
table.transform({'group' => "bob alice jeff ralph"})
|
37
|
+
#=> {'group' => "bob alice jeff ralph", 'situation' => "exclusive-party"}
|
38
|
+
|
39
|
+
table.transform({'group' => "bob alice jeff don"})
|
40
|
+
#=> {'group' => "bob alice jeff don", 'situation' => "party"}
|
41
|
+
|
42
|
+
table.transform({'group' => "bob alice jeff mary ralph don"})
|
43
|
+
#=> {'group' => "bob alice jeff mary ralph don", 'situation' => "PARTY!"}
|
44
|
+
|
45
|
+
table.transform({'group' => "bob alice jeff mary don bev"})
|
46
|
+
#=> {'group' => "bob alice jeff mary don bev", 'situation' => "PARTY!"}
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create new Pull Request
|
57
|
+
|
58
|
+
[1]: https://github.com/jmettraux/rufus-decision
|
59
|
+
[2]: https://github.com/lastobelus/rufus-decision/tree/pluggable_matchers
|
60
|
+
[3]: https://github.com/lastobelus/rufus-decision
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Set Logic Matcher
|
2
|
+
|
3
|
+
The SetLogic matcher allows a decision table cell to match based on set logic between the decision table and the corresponding entry in the hash being transformed.
|
4
|
+
|
5
|
+
|
6
|
+
## Set Conversion on input hash
|
7
|
+
|
8
|
+
When a decision table cell contains a set expression, the corresponding value in the input is first converted to a set according to the following rules:
|
9
|
+
|
10
|
+
$(bob, mary) => Set["bob", "mary"]
|
11
|
+
$(bob mary, jeff) => Set["bob mary", "jeff"]
|
12
|
+
$('bob rob', jeff) => Set["bob rob", "jeff"]
|
13
|
+
${r: ruby_code} => eval ruby code, ignoring unless it returns a set
|
14
|
+
${other_column} => apply set conversion to other_column
|
15
|
+
$(${c1}, ${c2}) => Set["c1 contents", "c2 contents"]
|
16
|
+
bob, mary => Set["bob", "mary"]
|
17
|
+
bob mary => Set["bob mary"]
|
18
|
+
'bob, mary', jeff => Set["bob, mary", "jeff"]
|
19
|
+
|
20
|
+
## Decision Table Cell Syntax
|
21
|
+
|
22
|
+
1. The SetLogic matcher applies to any cell matching the regex:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
/^\$\((.*)\)/
|
26
|
+
```
|
27
|
+
|
28
|
+
### Set Values
|
29
|
+
|
30
|
+
set logic expressions can contain Sets expressed in the following ways:
|
31
|
+
|
32
|
+
$in => the hash element with this column name, with set conversion applied
|
33
|
+
$(${c1}) => the hash element named c1, with set conversion applied
|
34
|
+
$(${in:c1}) => the row element named in:c1, assumed to be Decision Table Cell Syntax
|
35
|
+
$(${out:c1}) => the row element named out:c1, with set conversion applied
|
36
|
+
$() => empty set
|
37
|
+
$(bob, mary) => Set["bob", "mary"]
|
38
|
+
$(bob mary, jeff) => Set["bob mary", "jeff"]
|
39
|
+
$('bob rob', jeff) => Set["bob rob", "jeff"]
|
40
|
+
$(*) => The universal set
|
41
|
+
$(r: ruby_code) => eval ruby code, ignoring unless it returns a set
|
42
|
+
|
43
|
+
### Set Expressions
|
44
|
+
Set expressions can use the following operators:
|
45
|
+
|
46
|
+
#### Cardinality Operators
|
47
|
+
|
48
|
+
#=
|
49
|
+
cardinality-equals => size of set ==
|
50
|
+
#<
|
51
|
+
cardinality-less-than => size of set is less than X
|
52
|
+
#>
|
53
|
+
cardinality-greater-than => cardinality >
|
54
|
+
|
55
|
+
#### Set Construction Operators
|
56
|
+
|
57
|
+
&|intersection => intersection of sets
|
58
|
+
+|union => union of sets
|
59
|
+
-|difference => difference of sets
|
60
|
+
^|exclusive => (set1 + set2) - (set1 & set2)
|
61
|
+
|
62
|
+
#### Set Logic Operators
|
63
|
+
|
64
|
+
<|proper_subset => proper_subset of sets
|
65
|
+
>|proper_superset => proper_superset of sets
|
66
|
+
<=|subset => subset of sets
|
67
|
+
>=|superset => superset of sets
|
68
|
+
=|same-as => equivalent sets
|
69
|
+
|
70
|
+
special case: if the cell contains only a set, it is equivalent to
|
71
|
+
$in <= $(cell)
|
72
|
+
|
73
|
+
#### Set Expression Rules
|
74
|
+
|
75
|
+
* set expressions are evaluated left to right. There is no grouping; multiple columns in the decision table can be used to accomplish precedence
|
76
|
+
* cardinality operators must always be the last operator if present
|
77
|
+
* a cell can contain only one cardinality operator; use multiple cells for logic
|
78
|
+
* set construction operators may not follow set logic operators
|
79
|
+
* when a cardinality operator follows a logic operator, the sense is (set expression) && (cardinality expression applied to rightmost set in set expression).
|
80
|
+
* when a cardinality operator follows a (series of) set construction operator(s) it applies to the final constructed set
|
81
|
+
* if a cell contains a cardinality operator it matches the input if the cardinality expression is true
|
82
|
+
* if a cell contains a set logic operator it matches the input if the set logic expression is true
|
83
|
+
* if a cell contains set construction operators only it matches the input if the set expression results in a non-empty set
|
84
|
+
* if a cell contains no operators it matches the input if it is a superset of the corresponding input value, ie, it is equivalent to
|
85
|
+
|
86
|
+
$in <= $(cell)
|
87
|
+
|
88
|
+
## Examples
|
89
|
+
|
90
|
+
$(bob jeff mary) & $in #= 2
|
91
|
+
=> does not match (bob, jeff, mary)
|
92
|
+
=> matches (bob, mary) or (jeff, mary) etc.
|
93
|
+
=> does not match (bob) or (jeff) or (mary)
|
94
|
+
|
95
|
+
$(bob, jeff, mary)
|
96
|
+
=> matches (bob)
|
97
|
+
=> matches (jeff, mary)
|
98
|
+
=> matches (bob, jeff, mary)
|
99
|
+
=> does not match (ralph, bob)
|
100
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013, Michael Johnston, lastobelus@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'rufus/decision/matcher'
|
25
|
+
require 'rudelo/parsers/set_logic_parser'
|
26
|
+
require 'rudelo/parsers/set_logic_transform'
|
27
|
+
|
28
|
+
module Rudelo
|
29
|
+
module Matchers
|
30
|
+
class SetLogic < Rufus::Decision::Matcher
|
31
|
+
|
32
|
+
|
33
|
+
def matches?(cell, value)
|
34
|
+
in_set = value_transform.apply(value_parser.parse(value))
|
35
|
+
ast(cell).eval(in_set)
|
36
|
+
end
|
37
|
+
|
38
|
+
def cell_substitution?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def asts
|
43
|
+
@asts ||= {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def logic_parser
|
47
|
+
@logic_parser ||= Rudelo::Parsers::SetLogicParser.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def logic_transform
|
51
|
+
@logic_transform ||= Rudelo::Parsers::SetLogicTransform.new
|
52
|
+
end
|
53
|
+
|
54
|
+
def value_parser
|
55
|
+
@value_parser ||= Rudelo::Parsers::SetValueParser.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def value_transform
|
59
|
+
@value_transform ||= Rudelo::Parsers::SetLogicTransform.new
|
60
|
+
end
|
61
|
+
|
62
|
+
def ast(cell)
|
63
|
+
asts[cell] ||= logic_transform.apply(logic_parser.parse(cell))
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
require 'rudelo/parsers/set_value_parser'
|
3
|
+
|
4
|
+
module Rudelo
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
module Atom
|
8
|
+
include Parslet
|
9
|
+
rule(:digit) { match("[0-9]") }
|
10
|
+
|
11
|
+
rule(:integer) do
|
12
|
+
str("-").maybe >> match("[1-9]") >> digit.repeat
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class SetLogicParser < Parslet::Parser
|
17
|
+
include Space
|
18
|
+
include Set
|
19
|
+
include Atom
|
20
|
+
|
21
|
+
rule(:in_set) { str("$in").as(:in_set)}
|
22
|
+
rule(:set ) { explicit_set | in_set }
|
23
|
+
|
24
|
+
######## Cardinality Expressions ########
|
25
|
+
rule(:cardinality_eq) { str('#=') | str('cardinality-equals') }
|
26
|
+
rule(:cardinality_gt) { str('#>') | str('cardinality-greater-than') }
|
27
|
+
rule(:cardinality_lt) { str('#<') | str('cardinality-less-than') }
|
28
|
+
rule(:cardinality_operator) {
|
29
|
+
cardinality_eq | cardinality_gt | cardinality_lt
|
30
|
+
}
|
31
|
+
rule(:cardinality_expression) {(
|
32
|
+
cardinality_operator.as(:op) >>
|
33
|
+
space? >>
|
34
|
+
integer.as(:qty)
|
35
|
+
).as(:cardinality_expression)
|
36
|
+
}
|
37
|
+
|
38
|
+
######## Set Construction Expressions ########
|
39
|
+
|
40
|
+
rule(:set_construction_operator){
|
41
|
+
spaced_op?('&') | spaced_op('intersection') |
|
42
|
+
spaced_op?('+') | spaced_op('union') |
|
43
|
+
spaced_op?('-') | spaced_op('difference') |
|
44
|
+
spaced_op?('^') | spaced_op('exclusive')
|
45
|
+
}
|
46
|
+
rule(:set_op){
|
47
|
+
(set_construction_operator.as(:left) >> set.as(:right)).as(:set_op)
|
48
|
+
}
|
49
|
+
rule(:set_construction_expression){
|
50
|
+
(set.as(:left) >> set_op.repeat(1).as(:right)).as(:set_construction_expression)
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
######## Set Logic Expressions ########
|
55
|
+
rule(:set_expression) { set_construction_expression | set }
|
56
|
+
rule(:set_logic_operator){
|
57
|
+
spaced_op?('<', '=') | spaced_op('proper-subset') |
|
58
|
+
spaced_op?('>', '=') | spaced_op('proper-superset') |
|
59
|
+
spaced_op?('<=') | spaced_op('subset') |
|
60
|
+
spaced_op?('>=') | spaced_op('superset') |
|
61
|
+
spaced_op?('=') | spaced_op('same-as')
|
62
|
+
}
|
63
|
+
rule(:set_logic_expression){
|
64
|
+
(set_expression.as(:left) >> set_logic_operator >> set.as(:right)).as(:set_logic_expression)
|
65
|
+
}
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
rule(:match_expression) { space? >> (
|
71
|
+
|
72
|
+
set_logic_expression.as(:left) >>
|
73
|
+
(space >> cardinality_expression.as(:right)).maybe |
|
74
|
+
|
75
|
+
set_construction_expression.as(:left) >>
|
76
|
+
(space >> cardinality_expression.as(:right)).maybe |
|
77
|
+
|
78
|
+
cardinality_expression.as(:right)
|
79
|
+
|
80
|
+
).as(:match_expression) |
|
81
|
+
|
82
|
+
explicit_set.as(:superset_match_expression) >> space? }
|
83
|
+
|
84
|
+
root(:match_expression)
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'rudelo/parsers/set_value_transform'
|
2
|
+
|
3
|
+
class Set
|
4
|
+
def eval
|
5
|
+
self
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Rudelo
|
10
|
+
module Parsers
|
11
|
+
|
12
|
+
|
13
|
+
class SetLogicTransform < Rudelo::Parsers::SetValueTransform
|
14
|
+
|
15
|
+
|
16
|
+
attr_accessor :in_set, :set_value
|
17
|
+
def initialize(in_set=::Set.new, &block)
|
18
|
+
@in_set = in_set.dup # the dup is vital because we are going to be pointery later
|
19
|
+
@set_value = SetValueTransform.new
|
20
|
+
super()
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply(obj, context=nil)
|
24
|
+
super @set_value.apply(obj, context), {in_set: @in_set}
|
25
|
+
end
|
26
|
+
|
27
|
+
SetLogicExpr = Struct.new(:left, :op, :right) {
|
28
|
+
def eval
|
29
|
+
left.eval.send(op, right)
|
30
|
+
end
|
31
|
+
def set
|
32
|
+
right
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
SetConstructionExpr = Struct.new(:left, :right) {
|
37
|
+
def eval
|
38
|
+
right.reduce(left){|left, set_op| set_op.eval(left) }
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
SetOp = Struct.new(:op, :right) {
|
43
|
+
def eval(set)
|
44
|
+
set.send(op, right)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
CardinalityExpr = Struct.new(:op, :qty) {
|
49
|
+
def eval(set)
|
50
|
+
set.size.send(op, qty)
|
51
|
+
end
|
52
|
+
def empty?
|
53
|
+
false
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
class EmptyExpr
|
58
|
+
def eval(arg=nil)
|
59
|
+
true
|
60
|
+
end
|
61
|
+
def empty?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
MatchExpr = Struct.new(:left, :right, :in_set) {
|
67
|
+
def eval(in_set_override=nil)
|
68
|
+
# I've always wondered what these replace methods were for
|
69
|
+
# and now I know. To make references more pointery.
|
70
|
+
# The purpose of this is so we can construct a transform
|
71
|
+
# once and use it with different values for in_set.
|
72
|
+
in_set.replace(in_set_override) unless in_set_override.nil?
|
73
|
+
lvalue = left.eval
|
74
|
+
case lvalue
|
75
|
+
when ::Set
|
76
|
+
right.empty? ? (lvalue.size > 0) : right.eval(lvalue)
|
77
|
+
when ::TrueClass, ::FalseClass
|
78
|
+
lvalue && right.eval(left.set)
|
79
|
+
else
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
}
|
84
|
+
|
85
|
+
rule(cardinality_expression: subtree(:expr)){
|
86
|
+
CardinalityExpr.new(expr[:op], expr[:qty])
|
87
|
+
}
|
88
|
+
|
89
|
+
def self.translate_op(op)
|
90
|
+
case op
|
91
|
+
when '#=', 'cardinality-equals',
|
92
|
+
'same-as', '='; :"=="
|
93
|
+
when '#<','cardinality-less-than'; :<
|
94
|
+
when '<','proper-subset'; :proper_subset?
|
95
|
+
when '#>','cardinality-greater-than'; :>
|
96
|
+
when '>', 'proper-superset'; :proper_superset?
|
97
|
+
when '<=', 'subset'; :subset?
|
98
|
+
when '>=', 'superset'; :superset?
|
99
|
+
when 'intersection'; :&
|
100
|
+
when 'union'; :+
|
101
|
+
when 'difference'; :-
|
102
|
+
when 'exclusive'; :'^'
|
103
|
+
else
|
104
|
+
op.to_sym
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
rule(op: simple(:op)){
|
109
|
+
SetLogicTransform.translate_op(op)
|
110
|
+
}
|
111
|
+
|
112
|
+
rule(qty: simple(:qty)){ qty.to_i }
|
113
|
+
|
114
|
+
rule(op: simple(:op), qty: simple(:qty)){
|
115
|
+
{op: SetLogicTransform.translate_op(op), qty: qty.to_i}
|
116
|
+
}
|
117
|
+
|
118
|
+
rule(in_set: simple(:x)){ in_set }
|
119
|
+
|
120
|
+
rule(set_logic_expression: subtree(:expr)){
|
121
|
+
SetLogicExpr.new(
|
122
|
+
expr[:left],
|
123
|
+
SetLogicTransform.translate_op(expr[:op]),
|
124
|
+
expr[:right]
|
125
|
+
)
|
126
|
+
}
|
127
|
+
|
128
|
+
rule(set_construction_expression: subtree(:expr)){
|
129
|
+
SetConstructionExpr.new(
|
130
|
+
expr[:left],
|
131
|
+
expr[:right]
|
132
|
+
)
|
133
|
+
}
|
134
|
+
|
135
|
+
rule(set_op: subtree(:expr)){
|
136
|
+
SetOp.new(
|
137
|
+
expr[:left],
|
138
|
+
expr[:right]
|
139
|
+
)
|
140
|
+
}
|
141
|
+
|
142
|
+
rule(match_expression: subtree(:expr)){
|
143
|
+
MatchExpr.new(
|
144
|
+
expr[:left] || in_set,
|
145
|
+
expr[:right] || EmptyExpr.new,
|
146
|
+
in_set
|
147
|
+
)
|
148
|
+
}
|
149
|
+
|
150
|
+
rule(superset_match_expression: simple(:set)){
|
151
|
+
MatchExpr.new(
|
152
|
+
SetLogicExpr.new(in_set, :subset?, set),
|
153
|
+
EmptyExpr.new,
|
154
|
+
in_set
|
155
|
+
)
|
156
|
+
}
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
module Rudelo
|
4
|
+
module Parsers
|
5
|
+
|
6
|
+
module Space
|
7
|
+
include Parslet
|
8
|
+
rule(:space) { match["\t "].repeat(1) }
|
9
|
+
rule(:space?) { space.maybe }
|
10
|
+
def spaced_op(s)
|
11
|
+
space >> str(s).as(:op) >> space
|
12
|
+
end
|
13
|
+
def spaced_op?(s, protect=nil)
|
14
|
+
if protect
|
15
|
+
# this is necessary when some ops are substrings of
|
16
|
+
# other ops. if you have '>' and '>=', use
|
17
|
+
# spaced_op?('>', '=') for '>'
|
18
|
+
space? >> str(protect).absent? >> str(s).as(:op) >> space? >> str(protect).absent?
|
19
|
+
else
|
20
|
+
space? >> str(s).as(:op) >> space?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Set
|
26
|
+
include Parslet
|
27
|
+
|
28
|
+
rule(:comma) { str(',') }
|
29
|
+
rule(:comma?) { str(',').maybe }
|
30
|
+
rule(:quote) { match '"' }
|
31
|
+
rule(:open_set) { str('$(') }
|
32
|
+
rule(:close_set) { str(')') }
|
33
|
+
|
34
|
+
rule(:unquoted_element) {
|
35
|
+
(close_set.absent? >> space.absent? >> comma.absent? >> any).
|
36
|
+
repeat(1).as(:element) }
|
37
|
+
rule(:quoted_element) {
|
38
|
+
(quote >>
|
39
|
+
(quote.absent? >> any).repeat.
|
40
|
+
as(:element) >>
|
41
|
+
quote)}
|
42
|
+
rule(:element) { quoted_element | unquoted_element }
|
43
|
+
rule(:element_delimiter) { (comma | space) >> space? }
|
44
|
+
|
45
|
+
|
46
|
+
rule(:bare_element_list) {
|
47
|
+
(element >> (element_delimiter >> element).repeat).
|
48
|
+
as(:element_list)
|
49
|
+
}
|
50
|
+
|
51
|
+
rule(:explicit_set) {
|
52
|
+
open_set >> space? >>
|
53
|
+
bare_element_list >>
|
54
|
+
space? >>
|
55
|
+
close_set
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
class SetValueParser < Parslet::Parser
|
60
|
+
include Space
|
61
|
+
include Set
|
62
|
+
|
63
|
+
rule(:set_value) { explicit_set | bare_element_list }
|
64
|
+
root(:set_value)
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/rudelo.rb
ADDED
data/rudelo.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rudelo/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rudelo"
|
8
|
+
spec.version = Rudelo::VERSION
|
9
|
+
spec.authors = ["Michael Johnston"]
|
10
|
+
spec.email = ["lastobelus@mac.com"]
|
11
|
+
spec.description = %q{Set Logic Matcher for rufus-decision}
|
12
|
+
spec.summary = %q{Set Logic Matcher for rufus-decision}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rufus-decision", "~> 1.4"
|
22
|
+
spec.add_dependency "parslet"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
end
|