de 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ nbproject*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7@de
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in de.gemspec
4
+ gemspec
data/README ADDED
@@ -0,0 +1,28 @@
1
+
2
+ De (Dynamic Expression) module provides means to build and evaluate
3
+ dynamic expression of arbitrary complecity and operands/operators nature.
4
+
5
+ Expression is considered as tree consisted of operands and operators.
6
+ Operater must have child nodes. Operand is terminal node.
7
+
8
+ Tree example:
9
+ (a + b) * c + d
10
+
11
+ - +
12
+ --- *
13
+ ----- +
14
+ ------- a
15
+ ------- b
16
+ --- d
17
+
18
+ Here "+" and "*" are operators,
19
+ "a", "b" and "d" are operands
20
+
21
+ Expression tree structure is built with the help of <rubytree> gem
22
+ (https://rubygems.org/gems/rubytree)
23
+
24
+ In addition to basic classes of Expression, Operand and Operator
25
+ module provides some extensions for different nature expressions:
26
+ - Boolean expressions
27
+ - Sunspot Solr search expressions
28
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/de.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "de/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "de"
7
+ s.version = De::VERSION
8
+ s.authors = ["Olga Gorun"]
9
+ s.email = ["ogorun@quicklizard.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Dynamic Expression}
12
+ s.description = %q{De (Dynamic Expression) module provides means to build and evaluate
13
+ dynamic expression of arbitrary complecity and operands/operators nature}
14
+
15
+ s.rubyforge_project = "de"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency 'rubytree', '~> 0.8.1'
23
+ s.add_dependency 'activesupport', '~> 2.3.10'
24
+ s.add_development_dependency 'rack-test'
25
+ end
26
+
@@ -0,0 +1,46 @@
1
+ #
2
+ # De::Boolean::Operand class
3
+ #
4
+ # De::Boolean module provides means for dynamic logical expression building/validation/evaluation
5
+ # It is built as extension of De module
6
+ #
7
+ # Boolean expression example:
8
+ #
9
+ # true AND (false OR true)
10
+ #
11
+
12
+ module De
13
+ module Boolean
14
+
15
+ # Marker module included to all module classes
16
+ # to be able to check that class is inside module
17
+ module Bn; end
18
+
19
+ #
20
+ # Class representing Boolean operand
21
+ #
22
+ class Operand < De::Operand
23
+ include Bn
24
+
25
+ #
26
+ # Checks content is boolean value
27
+ #
28
+ # === Output
29
+ #
30
+ # true|false
31
+ #
32
+ def valid?
33
+ content.is_a?(TrueClass) || content.is_a?(FalseClass)
34
+ end
35
+
36
+ #
37
+ # Returns value stored in content field if it is valid
38
+ # or raises exception
39
+ #
40
+ def evaluate
41
+ super
42
+ content
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,134 @@
1
+ #
2
+ # De::Boolean::Operator class
3
+ #
4
+ # De::Boolean module provides means for dynamic logical expression building/validation/evaluation
5
+ # It is built as extension of De module
6
+ #
7
+ # Boolean expression example:
8
+ #
9
+ # true AND (false OR true)
10
+ #
11
+ #
12
+
13
+ require 'de/symmetric_operator'
14
+
15
+ module De
16
+ module Boolean
17
+
18
+ # Marker module included to all module classes
19
+ # to be able to check that class is inside module
20
+ module Bn; end
21
+
22
+ #
23
+ # Class representing Boolean operator
24
+ # Base abstract class for concrete operators AND, OR, NOT
25
+ #
26
+ class BooleanOperator < Operator
27
+ include Bn
28
+ include De::SymmetricOperator
29
+
30
+ #
31
+ # Constructor. Prevents direct BooleanOperator class objects creation
32
+ #
33
+ # === Input
34
+ #
35
+ # name<String>
36
+ # operands<Array>:: (optional) array of Operand objects.
37
+ # If given they are added as children to current operator
38
+ #
39
+ def initialize(name, operands = nil)
40
+ raise Error::AbstractClassObjectCreationError if instance_of? BooleanOperator
41
+ super(name, operands)
42
+ end
43
+
44
+ #
45
+ # Adds boolean operator or operand as a child to boolean operator
46
+ #
47
+ def <<(obj)
48
+ raise Error::TypeError unless obj.is_a?(De::Boolean::Bn)
49
+ super(obj)
50
+ end
51
+
52
+ end
53
+
54
+ #
55
+ # Class representing boolean AND operator
56
+ #
57
+ class And < BooleanOperator
58
+
59
+ #
60
+ # Creates And object.
61
+ # Name includes word 'AND' with random number to avoid TreeNode problem
62
+ # when trying to add children with the same name to a node
63
+ #
64
+ # === Input
65
+ #
66
+ # operands<Array>:: (optional) array of Operand objects.
67
+ # If given they are added as children to current operator
68
+ #
69
+ def initialize(operands = nil)
70
+ super("AND-#{rand(1000)}", operands)
71
+ end
72
+
73
+ def evaluate
74
+ super
75
+ children.inject(true) {|result, el| result && el.evaluate}
76
+ end
77
+ end
78
+
79
+ #
80
+ # Class representing boolean OR operator
81
+ #
82
+ class Or < BooleanOperator
83
+
84
+ #
85
+ # Creates Or object.
86
+ # Name includes word 'AND' with random number to avoid TreeNode problem
87
+ # when trying to add children with the same name to a node
88
+ #
89
+ # === Input
90
+ #
91
+ # operands<Array>:: (optional) array of Operand objects.
92
+ # If given they are added as children to current operator
93
+ #
94
+ def initialize(operands = nil)
95
+ super("OR-#{rand(1000)}", operands)
96
+ end
97
+
98
+ def evaluate
99
+ super
100
+ children.inject(false) {|result, el| result || el.evaluate}
101
+ end
102
+ end
103
+
104
+ #
105
+ # Class representing boolean NOT operator
106
+ #
107
+ class Not < BooleanOperator
108
+
109
+ #
110
+ # Creates Not object.
111
+ # Name includes word 'AND' with random number to avoid TreeNode problem
112
+ # when trying to add children with the same name to a node
113
+ #
114
+ # === Input
115
+ #
116
+ # operand<De::Boolean::Operand>:: (optional) Operand object.
117
+ # If given it is added as child to current operator
118
+ #
119
+ def initialize(operand = nil)
120
+ super("NOT-#{rand(1000)}", operand ? [operand] : nil)
121
+ end
122
+
123
+ def <<(obj)
124
+ raise Error::ArgumentNumerError if has_children?
125
+ super(obj)
126
+ end
127
+
128
+ def evaluate
129
+ super
130
+ !first_child.evaluate
131
+ end
132
+ end
133
+ end
134
+ end
data/lib/de/boolean.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'de/boolean/operand'
2
+ require 'de/boolean/operator'
data/lib/de/de.rb ADDED
@@ -0,0 +1,210 @@
1
+ #
2
+ # De (Dynamic Expression) module provides means to build and evaluate
3
+ # dynamic expression of arbitrary complecity and operands/operators nature.
4
+ #
5
+ # Expression is considered as tree consisted of operands and operators.
6
+ # Operater must have child nodes. Operand is terminal node.
7
+ #
8
+ # Tree example:
9
+ # (a + b) * c + d
10
+ #
11
+ # - +
12
+ # --- *
13
+ # ----- +
14
+ # ------- a
15
+ # ------- b
16
+ # --- d
17
+ #
18
+ # Here "+" and "*" are operators,
19
+ # "a", "b" and "d" are operands
20
+ #
21
+ # Expression tree structure is built with the help of <rubytree> gem
22
+ # (https://rubygems.org/gems/rubytree)
23
+ #
24
+ # In addition to basic classes of Expression, Operand and Operator
25
+ # module provides some extensions for different nature expressions:
26
+ # - Boolean expressions
27
+ # - Sunspot Solr search expressions
28
+ #
29
+
30
+ require 'tree'
31
+ require 'de/error'
32
+
33
+ module De
34
+
35
+ #
36
+ # Base class of De module
37
+ # Represents tree of dynamic expression consisted of operators and operands
38
+ # Provides means for expression tree building, validation and evaluation
39
+ #
40
+ class Expression < Tree::TreeNode
41
+
42
+ #
43
+ # Adds operator or operand as a child to Expression
44
+ #
45
+ # === Input
46
+ #
47
+ # obj<Operator|Operand>:: object to be added as child one
48
+ #
49
+ # === Output
50
+ #
51
+ # <Operator|Operand>:: child object
52
+ #
53
+ def <<(obj)
54
+ raise Error::TypeError unless (obj.is_a?(Operator) or obj.is_a?(Operand))
55
+ super(obj)
56
+ end
57
+
58
+ #
59
+ # Checks expression validity
60
+ # Must be overriden by extending class
61
+ #
62
+ # === Input
63
+ #
64
+ # no input
65
+ #
66
+ # === Output
67
+ #
68
+ # true|false
69
+ #
70
+ def valid?
71
+ raise Error::MethodShouldBeOverridenByExtendingClassError
72
+ end
73
+
74
+ #
75
+ # Evaluates expression in case it is valid
76
+ # otherwise raises exception
77
+ # If expression valid returns true
78
+ # Must be overriden in extending class to do some usefull evaluation
79
+ #
80
+ # === Input
81
+ #
82
+ # no input
83
+ #
84
+ # === Output
85
+ #
86
+ # true
87
+ #
88
+ def evaluate
89
+ raise Error::InvalidExpressionError unless valid?
90
+ true
91
+ end
92
+
93
+ #
94
+ # Override eql? and hash for consistent behaviour in hashes and arrays
95
+ #
96
+
97
+ def eql?(obj)
98
+ self == obj
99
+ end
100
+
101
+ def hash
102
+ @content.hash
103
+ end
104
+
105
+ end
106
+
107
+ #
108
+ # Class representing operator concept -
109
+ # kind of expression that has children
110
+ #
111
+ # Class can be considered abstract.
112
+ # Its object creation is prevented by constructor.
113
+ # Extending classes must be implemented
114
+ # in order to work with this concept
115
+ #
116
+ class Operator < Expression
117
+
118
+ #
119
+ # Constructor. Prevents this class objects direct creation
120
+ #
121
+ # === Input
122
+ #
123
+ # name<String>
124
+ # operands<Array>:: (optional) array of Operand objects.
125
+ # If given they are added as children to current operator
126
+ #
127
+ def initialize(name, operands = nil)
128
+ raise Error::AbstractClassObjectCreationError if instance_of? Operator
129
+ super(name)
130
+
131
+ unless operands.nil?
132
+ raise Error::TypeError unless operands.is_a?(Array)
133
+ operands.each { |operand| self << operand }
134
+ end
135
+ end
136
+
137
+ #
138
+ # Checks expression validity
139
+ # Operator is valid if it has children and every one from them is valid
140
+ #
141
+ # === Input
142
+ #
143
+ # no input
144
+ #
145
+ # === Output
146
+ #
147
+ # true|false
148
+ #
149
+ def valid?
150
+ has_children? and children.inject(true) { |result, el| result and el.valid? }
151
+ end
152
+
153
+ #
154
+ # Equal operator override
155
+ # This basic implementation checks objects class, children number equality
156
+ # and children equality. Operands order is important
157
+ #
158
+ def ==(obj)
159
+ self.class.name == obj.class.name && children == obj.children
160
+ end
161
+
162
+ #
163
+ # Define hash function to get equal results for operators from the same class and with the equal children
164
+ #
165
+ def hash
166
+ [self.class.name, children.map { |el| el.hash}].hash
167
+ end
168
+ end
169
+
170
+ #
171
+ # Class representing operand concept -
172
+ # kind of expression without children.
173
+ #
174
+ # Class can be considered abstract.
175
+ # Its object creation is prevented by constructor.
176
+ # Extending classes must be implemented
177
+ # in order to work with this concept
178
+ #
179
+ class Operand < Expression
180
+ #
181
+ # Constructor. Prevents this class objects direct creation
182
+ #
183
+ # === Input
184
+ #
185
+ # name<String>
186
+ # content<Object>:: arbitrary content. Its interpritation is done by extending classes
187
+ #
188
+ def initialize(name, content)
189
+ raise Error::AbstractClassObjectCreationError if instance_of? Operand
190
+ super(name, content)
191
+ end
192
+
193
+ #
194
+ # Adding children to operand is prevented (raises exception)
195
+ #
196
+ def <<(obj)
197
+ raise Error::TypeError
198
+ end
199
+
200
+ #
201
+ # Evaluator must be overriden by extending classes (raises exception)
202
+ #
203
+ def evaluate
204
+ raise Error::MethodShouldBeOverridenByExtendingClassError if instance_of? Operand
205
+ super
206
+ end
207
+ end
208
+
209
+ end
210
+
data/lib/de/error.rb ADDED
@@ -0,0 +1,6 @@
1
+ module De
2
+ module Error
3
+ exceptions = %w[TypeError ArgumentNumerError InvalidExpressionError AbstractClassObjectCreationError MethodShouldBeOverridenByExtendingClassError]
4
+ exceptions.each { |e| const_set(e, Class.new(StandardError)) }
5
+ end
6
+ end