omghax-einstein 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.
@@ -0,0 +1,155 @@
1
+ require "einstein/visitors"
2
+
3
+ module Einstein
4
+ module Nodes
5
+ # Base class for all Einstein nodes.
6
+ class Node
7
+ include Einstein::Visitors
8
+
9
+ # Initializes a new instance of this node with the given +value+.
10
+ def initialize(value)
11
+ @value = value
12
+ end
13
+
14
+ # The value of this node.
15
+ attr_accessor :value
16
+
17
+ # Implements the visitor pattern by calling a method named
18
+ # visit_SomeNode when visiting a class named SomeNode. This way we
19
+ # separate the tree traversal logic from the nodes themselves, which
20
+ # makes it easier to modify the visitors, or to add new ones.
21
+ def accept(visitor, &block)
22
+ klass = self.class.ancestors.find do |ancestor|
23
+ visitor.respond_to?("visit_#{ancestor.name.split(/::/)[-1]}")
24
+ end
25
+
26
+ if klass
27
+ visitor.send("visit_#{klass.name.split(/::/)[-1]}", self, &block)
28
+ else
29
+ raise "No visitor for '#{self.class}'"
30
+ end
31
+ end
32
+
33
+ # Evaluate this node against the given +scope+. Returns a numeric value
34
+ # calculated by walking the AST with an instance of EvaluateVisitor.
35
+ def evaluate(scope = {})
36
+ EvaluateVisitor.new(scope).accept(self)
37
+ end
38
+
39
+ # Performs a "pretty print" of this node.
40
+ def to_s
41
+ PrettyPrintVisitor.new.accept(self)
42
+ end
43
+
44
+ # Also use #to_s for inspecting a node in IRB.
45
+ alias_method :inspect, :to_s
46
+
47
+ # Returns this node as an s-expression. Built by walking the AST with
48
+ # an instance of SexpVisitor.
49
+ def to_sexp
50
+ SexpVisitor.new.accept(self)
51
+ end
52
+ end
53
+
54
+ # Node representing an entire Einstein statement. This is the root-level
55
+ # node of the parser. This node's +value+ is the tree of expressions that
56
+ # make up a single logical statement.
57
+ class StatementNode < Node
58
+ end
59
+
60
+ # Node representing a number. This is a terminal node.
61
+ class NumberNode < Node
62
+ end
63
+
64
+ # Node representing a unary + (plus) operation. Not to be confused with
65
+ # the addition operator, this node is generated when you specify a number
66
+ # with an explicit positive sign.
67
+ #
68
+ # Example:
69
+ # # This would generate a UnaryPlusNode with the value 1.25.
70
+ # +1.25
71
+ class UnaryPlusNode < Node
72
+ end
73
+
74
+ # Node representing a unary - (minus) operation. Not to be confused with
75
+ # the subtraction operator, this node is generated when you specify a
76
+ # negative number.
77
+ #
78
+ # Example:
79
+ # # This would generate a UnaryMinusNode with the value 3.3.
80
+ # -3.3
81
+ class UnaryMinusNode < Node
82
+ end
83
+
84
+ # Node representing a bitwise NOT operation.
85
+ class BitwiseNotNode < Node
86
+ end
87
+
88
+ # Base class for all binary nodes. Binary nodes operate on two values.
89
+ class BinaryNode < Node
90
+ # Initializes a new instance of this node with the given +left+ and
91
+ # +right+ values.
92
+ def initialize(left, right)
93
+ super(right)
94
+ @left = left
95
+ end
96
+
97
+ # The secondary value given to #initialize.
98
+ attr_reader :left
99
+ end
100
+
101
+ # Node representing an exponential raise. This node's +left+ value is the
102
+ # base, and the +right+ value is the exponent.
103
+ class ExponentNode < BinaryNode
104
+ end
105
+
106
+ # Node representing multiplication of two values.
107
+ class MultiplyNode < BinaryNode
108
+ end
109
+
110
+ # Node representing division of two values.
111
+ class DivideNode < BinaryNode
112
+ end
113
+
114
+ # Node representing modulus of two values.
115
+ class ModulusNode < BinaryNode
116
+ end
117
+
118
+ # Node representing addition of two nodes.
119
+ class AddNode < BinaryNode
120
+ end
121
+
122
+ # Node representing subtraction of two nodes.
123
+ class SubtractNode < BinaryNode
124
+ end
125
+
126
+ # Node representing an LSHIFT (left shift) operation. This node's +left+
127
+ # value is the number to be shifted, and the +right+ value is the number
128
+ # of bits to shift the value by.
129
+ class LeftShiftNode < BinaryNode
130
+ end
131
+
132
+ # Node representing an RSHIFT (right shift) operation. This node's +left+
133
+ # value is the number to be shifted, and the +right+ value is the number
134
+ # of bits to shift the value by.
135
+ class RightShiftNode < BinaryNode
136
+ end
137
+
138
+ # Node representing a bitwise AND operation.
139
+ class BitwiseAndNode < BinaryNode
140
+ end
141
+
142
+ # Node representing a bitwise XOR operation.
143
+ class BitwiseXorNode < BinaryNode
144
+ end
145
+
146
+ # Node representing a bitwise OR operation.
147
+ class BitwiseOrNode < BinaryNode
148
+ end
149
+
150
+ # Node representing a variable lookup. This node's +value+ is the
151
+ # variable's name, as a string.
152
+ class ResolveNode < Node
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,60 @@
1
+ require "einstein/generated_parser"
2
+ require "einstein/tokenizer"
3
+
4
+ module Einstein
5
+ class Parser < GeneratedParser
6
+ # Tokenizer instance that is shared between instances of Parser.
7
+ TOKENIZER = Tokenizer.new
8
+
9
+ def initialize
10
+ @tokens = []
11
+ @logger = nil
12
+ @terminator = false
13
+ end
14
+
15
+ # Logger object to receive debugging information when a parsing error
16
+ # occurs. If this is nil, no information will be output.
17
+ attr_accessor :logger
18
+
19
+ # Parses the given +expression+ using TOKENIZER, and returns an instance
20
+ # of StatementNode, which represents an AST. You can take this AST and
21
+ # perform evaluations on it using #evaluate, or transform it into an
22
+ # s-expression using #to_sexp.
23
+ #
24
+ # Example:
25
+ # # Parse the expression "x + 3".
26
+ # ast = Einstein::Parser.new.parse("x + 3")
27
+ #
28
+ # # Evaluate the expression with a given scope.
29
+ # ast.evaluate(:x => 5) # => 8
30
+ #
31
+ # # Return the expression as an s-expression.
32
+ # ast.to_sexp # => [:add, [:resolve, "x"], [:lit, 3]]
33
+ def parse(expression)
34
+ @tokens = TOKENIZER.tokenize(expression)
35
+ @position = 0
36
+ StatementNode.new(do_parse)
37
+ end
38
+
39
+ private
40
+
41
+ def on_error(error_token_id, error_value, value_stack)
42
+ if logger
43
+ logger.error(token_to_str(error_token_id))
44
+ logger.error("error value: #{error_value}")
45
+ logger.error("error stack: #{value_stack.inspect}")
46
+ end
47
+ super
48
+ end
49
+
50
+ # Used by Racc::Parser to step through tokens.
51
+ def next_token
52
+ begin
53
+ return [false, false] if @position >= @tokens.length
54
+ n_token = @tokens[@position]
55
+ @position += 1
56
+ end while [:COMMENT, :WS].include?(n_token[0])
57
+ n_token
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,112 @@
1
+ module Einstein
2
+ class Token
3
+ # The default transformer used by instances of Token when no block is
4
+ # given to #initialize.
5
+ DEFAULT_TRANSFORMER = lambda { |name, value| [name, value] }
6
+
7
+ def initialize(name, value, &transformer)
8
+ @name = name
9
+ @value = value
10
+ @transformer = transformer || DEFAULT_TRANSFORMER
11
+ end
12
+
13
+ # This token's name (eg. :NUMBER, :IDENT).
14
+ attr_accessor :name
15
+
16
+ # This token's value (eg. 1, "x").
17
+ attr_accessor :value
18
+
19
+ # The block given to this token's #initialize method. This block is
20
+ # called by #to_racc_token.
21
+ attr_accessor :transformer
22
+
23
+ # Converts this token to a format that Racc expects. This is an array of
24
+ # the format [name, value].
25
+ #
26
+ # Examples:
27
+ # [:NUMBER, 2.20]
28
+ # [:IDENT, "x"]
29
+ def to_racc_token
30
+ @transformer.call(name, value)
31
+ end
32
+ end
33
+
34
+ class Lexeme
35
+ def initialize(name, pattern, &block)
36
+ @name = name
37
+ @pattern = pattern
38
+ @block = block
39
+ end
40
+
41
+ def match(string)
42
+ match = @pattern.match(string)
43
+ return Token.new(@name, match.to_s, &@block) if match
44
+ match
45
+ end
46
+ end
47
+
48
+ class Tokenizer
49
+ LITERALS = {
50
+ # Punctuators
51
+ "**" => :RAISE,
52
+ "<<" => :LSHIFT,
53
+ ">>" => :RSHIFT
54
+ }
55
+
56
+ def initialize(&block)
57
+ @lexemes = []
58
+
59
+ token(:COMMENT, /\A\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m)
60
+
61
+ # A regexp to match floating point literals (but not integer literals).
62
+ token(:NUMBER, /\A\d+\.\d*(?:[eE][-+]?\d+)?|\A\d+(?:\.\d*)?[eE][-+]?\d+|\A\.\d+(?:[eE][-+]?\d+)?/m) do |type, value|
63
+ value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/
64
+ value.gsub!(/\.$/, '.0') if value =~ /\.$/
65
+ value.gsub!(/^\./, '0.') if value =~ /^\./
66
+ [type, eval(value)]
67
+ end
68
+ token(:NUMBER, /\A0[bBxX][\da-fA-F]+|\A0[0-7]*|\A\d+/) do |type, value|
69
+ [type, eval(value)]
70
+ end
71
+
72
+ token(:LITERALS,
73
+ Regexp.new(LITERALS.keys.sort_by { |x| x.length }.reverse.map { |x| "\\A#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|')
74
+ )) do |type, value|
75
+ [LITERALS[value], value]
76
+ end
77
+
78
+ token(:IDENT, /\A(\w|\$)+/)
79
+
80
+ token(:WS, /\A[\s\r\n]*/m)
81
+
82
+ token(:SINGLE_CHAR, /\A./) do |type, value|
83
+ [value, value]
84
+ end
85
+ end
86
+
87
+ def tokenize(string)
88
+ tokens = []
89
+ while string.length > 0
90
+ longest_token = nil
91
+
92
+ @lexemes.each { |lexeme|
93
+ match = lexeme.match(string)
94
+ next if match.nil?
95
+ longest_token = match if longest_token.nil?
96
+ next if longest_token.value.length >= match.value.length
97
+ longest_token = match
98
+ }
99
+
100
+ string = string.slice(Range.new(longest_token.value.length, -1))
101
+ tokens << longest_token
102
+ end
103
+ tokens.map { |x| x.to_racc_token }
104
+ end
105
+
106
+ private
107
+
108
+ def token(name, pattern = nil, &block)
109
+ @lexemes << Lexeme.new(name, pattern, &block)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,9 @@
1
+ module Einstein #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,291 @@
1
+ module Einstein
2
+ module Visitors
3
+ class Visitor
4
+ TERMINAL_NODES = %w{
5
+ Number Resolve
6
+ }
7
+ SINGLE_VALUE_NODES = %w{
8
+ BitwiseNot Statement UnaryMinus UnaryPlus
9
+ }
10
+ BINARY_NODES = %w{
11
+ Add BitwiseAnd BitwiseOr BitwiseXor Divide Exponent LeftShift Modulus
12
+ Multiply RightShift Subtract
13
+ }
14
+
15
+ def accept(target)
16
+ target.accept(self)
17
+ end
18
+
19
+ TERMINAL_NODES.each do |type|
20
+ define_method("visit_#{type}Node") { |o| }
21
+ end
22
+
23
+ SINGLE_VALUE_NODES.each do |type|
24
+ define_method("visit_#{type}Node") do |o|
25
+ o.value.accept(self)
26
+ end
27
+ end
28
+
29
+ BINARY_NODES.each do |type|
30
+ define_method("visit_#{type}Node") do |o|
31
+ [o.left && o.left.accept(self), o.value && o.value.accept(self)]
32
+ end
33
+ end
34
+ end
35
+
36
+ # This visitor walks the AST and evaluates the values of the nodes.
37
+ class EvaluateVisitor < Visitor
38
+ # Initialize a new instance of this visitor with the given +scope+, as a
39
+ # hash. This +scope+ should provide a mapping of variable names to
40
+ # values.
41
+ def initialize(scope)
42
+ super()
43
+
44
+ # Convert the scope hash keys from symbols to strings.
45
+ @scope = scope.inject({}) do |hash, (key, value)|
46
+ hash[key.to_s] = value
47
+ hash
48
+ end
49
+ end
50
+
51
+ # Returns the value of +o+.
52
+ def visit_NumberNode(o)
53
+ o.value
54
+ end
55
+
56
+ # Returns the value of +o+.
57
+ def visit_UnaryPlusNode(o)
58
+ o.value.accept(self)
59
+ end
60
+
61
+ # Returns the negated value of +o+.
62
+ def visit_UnaryMinusNode(o)
63
+ -o.value.accept(self)
64
+ end
65
+
66
+ # Raises the left value of +o+ by the right value of +o+.
67
+ def visit_ExponentNode(o)
68
+ o.left.accept(self) ** o.value.accept(self)
69
+ end
70
+
71
+ # Multiplies the left and right values of +o+.
72
+ def visit_MultiplyNode(o)
73
+ o.left.accept(self) * o.value.accept(self)
74
+ end
75
+
76
+ # Divides the left value of +o+ by the right value of +o+. Raises
77
+ # ZeroDivisionError if the right value of +o+ is zero.
78
+ def visit_DivideNode(o)
79
+ dividend = o.value.accept(self)
80
+ raise ZeroDivisionError, "divided by zero" if dividend == 0
81
+ o.left.accept(self) / dividend
82
+ end
83
+
84
+ # Performs a modulus operation for the left and right values of +o+.
85
+ def visit_ModulusNode(o)
86
+ o.left.accept(self) % o.value.accept(self)
87
+ end
88
+
89
+ # Adds the left and right values of +o+.
90
+ def visit_AddNode(o)
91
+ o.left.accept(self) + o.value.accept(self)
92
+ end
93
+
94
+ # Subtracts the right value of +o+ by the left value of +o+.
95
+ def visit_SubtractNode(o)
96
+ o.left.accept(self) - o.value.accept(self)
97
+ end
98
+
99
+ # Performs a bitwise AND with the left and right values of +o+.
100
+ def visit_BitwiseAndNode(o)
101
+ o.left.accept(self) & o.value.accept(self)
102
+ end
103
+
104
+ # Performs a bitwise XOR with the left and right values of +o+.
105
+ def visit_BitwiseXorNode(o)
106
+ o.left.accept(self) ^ o.value.accept(self)
107
+ end
108
+
109
+ # Performs a bitwise OR with the left and right values of +o+.
110
+ def visit_BitwiseOrNode(o)
111
+ o.left.accept(self) | o.value.accept(self)
112
+ end
113
+
114
+ # Performs a lookup for the value of +o+ inside this visitor's scope.
115
+ # Raises ResolveError if the variable is not in scope.
116
+ def visit_ResolveNode(o)
117
+ raise ResolveError, "undefined variable: #{o.value}" unless @scope.has_key?(o.value)
118
+ @scope[o.value]
119
+ end
120
+ end
121
+
122
+ # This visitor walks the AST and builds a "pretty print" of the values.
123
+ # This means that it returns an unambiguous string representation of the
124
+ # tree. All binary expressions are wrapped in parentheses. This visitor
125
+ # is used when calling #inspect on a node.
126
+ class PrettyPrintVisitor < Visitor
127
+ # Example: 4
128
+ def visit_NumberNode(o)
129
+ o.value.inspect
130
+ end
131
+
132
+ # Example: +4
133
+ def visit_UnaryPlusNode(o)
134
+ "+#{o.value.accept(self)}"
135
+ end
136
+
137
+ # Example: -4
138
+ def visit_UnaryMinusNode(o)
139
+ "-#{o.value.accept(self)}"
140
+ end
141
+
142
+ # Example: ~4
143
+ def visit_BitwiseNotNode(o)
144
+ "~#{o.value.accept(self)}"
145
+ end
146
+
147
+ # Example: (2 ** 3)
148
+ def visit_ExponentNode(o)
149
+ "(#{o.left.accept(self)} ** #{o.value.accept(self)})"
150
+ end
151
+
152
+ # Example: (2 * 3)
153
+ def visit_MultiplyNode(o)
154
+ "(#{o.left.accept(self)} * #{o.value.accept(self)})"
155
+ end
156
+
157
+ # Example: (6 / 3)
158
+ def visit_DivideNode(o)
159
+ "(#{o.left.accept(self)} / #{o.value.accept(self)})"
160
+ end
161
+
162
+ # Example: (7 % 3)
163
+ def visit_ModulusNode(o)
164
+ "(#{o.left.accept(self)} % #{o.value.accept(self)})"
165
+ end
166
+
167
+ # Example: (5 + 8)
168
+ def visit_AddNode(o)
169
+ "(#{o.left.accept(self)} + #{o.value.accept(self)})"
170
+ end
171
+
172
+ # Example: (6 - 3)
173
+ def visit_SubtractNode(o)
174
+ "(#{o.left.accept(self)} - #{o.value.accept(self)})"
175
+ end
176
+
177
+ # Example: (8 << 2)
178
+ def visit_LeftShiftNode(o)
179
+ "(#{o.left.accept(self)} << #{o.value.accept(self)})"
180
+ end
181
+
182
+ # Example: (8 >> 2)
183
+ def visit_RightShiftNode(o)
184
+ "(#{o.left.accept(self)} >> #{o.value.accept(self)})"
185
+ end
186
+
187
+ # Example: (4 & 16)
188
+ def visit_BitwiseAndNode(o)
189
+ "(#{o.left.accept(self)} & #{o.value.accept(self)})"
190
+ end
191
+
192
+ # Example: (4 ^ 6)
193
+ def visit_BitwiseXorNode(o)
194
+ "(#{o.left.accept(self)} ^ #{o.value.accept(self)})"
195
+ end
196
+
197
+ # Example: (4 | 6)
198
+ def visit_BitwiseOrNode(o)
199
+ "(#{o.left.accept(self)} | #{o.value.accept(self)})"
200
+ end
201
+
202
+ # Example: x
203
+ def visit_ResolveNode(o)
204
+ o.value
205
+ end
206
+ end
207
+
208
+ # This visitor walks the AST and returns an s-expression.
209
+ class SexpVisitor < Visitor
210
+ # Example: [:lit, 3]
211
+ def visit_NumberNode(o)
212
+ [:lit, o.value]
213
+ end
214
+
215
+ # Example: [:u_plus, [:lit, 3]]
216
+ def visit_UnaryPlusNode(o)
217
+ [:u_plus, super]
218
+ end
219
+
220
+ # Example: [:u_minus, [:lit, 3]]
221
+ def visit_UnaryMinusNode(o)
222
+ [:u_minus, super]
223
+ end
224
+
225
+ # Example: [:bitwise_not, [:lit, 3]]
226
+ def visit_BitwiseNotNode(o)
227
+ [:bitwise_not, super]
228
+ end
229
+
230
+ # Example: [:raise, [:lit, 2], [:lit, 3]]
231
+ def visit_ExponentNode(o)
232
+ [:raise, *super]
233
+ end
234
+
235
+ # Example: [:multiply, [:lit, 2], [:lit, 3]]
236
+ def visit_MultiplyNode(o)
237
+ [:multiply, *super]
238
+ end
239
+
240
+ # Example: [:divide, [:lit, 4], [:lit, 2]]
241
+ def visit_DivideNode(o)
242
+ [:divide, *super]
243
+ end
244
+
245
+ # Example: [:modulus, [:lit, 3], [:lit, 5]]
246
+ def visit_ModulusNode(o)
247
+ [:modulus, *super]
248
+ end
249
+
250
+ # Example: [:add, [:lit, 2], [:lit, 2]]
251
+ def visit_AddNode(o)
252
+ [:add, *super]
253
+ end
254
+
255
+ # Example: [:subtract, [:lit, 5], [:lit, 2]]
256
+ def visit_SubtractNode(o)
257
+ [:subtract, *super]
258
+ end
259
+
260
+ # Example: [:lshift, [:lit, 2], [:lit, 3]]
261
+ def visit_LeftShiftNode(o)
262
+ [:lshift, *super]
263
+ end
264
+
265
+ # Example: [:rshift, [:lit, 8], [:lit, 2]]
266
+ def visit_RightShiftNode(o)
267
+ [:rshift, *super]
268
+ end
269
+
270
+ # Example: [:bitwise_and, [:lit, 4], [:lit, 2]]
271
+ def visit_BitwiseAndNode(o)
272
+ [:bitwise_and, *super]
273
+ end
274
+
275
+ # Example: [:bitwise_xor, [:lit, 4], [:lit, 2]]
276
+ def visit_BitwiseXorNode(o)
277
+ [:bitwise_xor, *super]
278
+ end
279
+
280
+ # Example: [:bitwise_or, [:lit, 4], [:lit, 2]]
281
+ def visit_BitwiseOrNode(o)
282
+ [:bitwise_or, *super]
283
+ end
284
+
285
+ # Example: [:resolve, "x"]
286
+ def visit_ResolveNode(o)
287
+ [:resolve, o.value]
288
+ end
289
+ end
290
+ end
291
+ end
data/lib/einstein.rb ADDED
@@ -0,0 +1,56 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require "einstein/parser"
4
+
5
+ # == Basic Usage
6
+ #
7
+ # If you're only interested in evaluating expressions, you can simply call
8
+ # Einstein.evaluate:
9
+ #
10
+ # Einstein.evaluate("1 + 2 + 3") # => 6
11
+ #
12
+ # You can use variables as well, but you must supply their values:
13
+ #
14
+ # Einstein.evaluate("x * 4", :x => 5) # => 20
15
+ # Einstein.evaluate("x + y + z", :x => 3, :y => 4, :z => 5) # => 12
16
+ #
17
+ # == Prepared Statements
18
+ #
19
+ # If you're going to be performing the same operation over a large set of
20
+ # different values, and performance is a concern, you can have Einstein parse
21
+ # the expression once beforehand, and return a statement object (technically,
22
+ # an instance of Einstein::Nodes::StatementNode). You can then pass your
23
+ # values directly to this statement for much faster processing, rather than
24
+ # making Einstein parse the same formula over and over again each time.
25
+ #
26
+ # # This will return a prepared statement.
27
+ # stmt = Einstein.parse("x + y")
28
+ #
29
+ # # You can then evaluate this statement against different inputs.
30
+ # stmt.evaluate(:x => 1, :y => 2) # => 3
31
+ # stmt.evaluate(:x => 25, :y => 30) # => 55
32
+ module Einstein
33
+ # Base class for all Einstein errors.
34
+ class Error < StandardError
35
+ end
36
+
37
+ # Raised when a variable's name cannot be resolved.
38
+ class ResolveError < Error
39
+ end
40
+
41
+ # Raised when division by zero occurs.
42
+ class ZeroDivisionError < Error
43
+ end
44
+
45
+ # Parse the given +expression+ and return the AST as
46
+ def self.parse(expression)
47
+ Parser.new.parse(expression)
48
+ end
49
+
50
+ # Evaluate the given +expression+ with the given +scope+. Any variables
51
+ # used by the +expression+, but undeclared in the +scope+, will cause a
52
+ # Einstein::ResolveError to be raised.
53
+ def self.evaluate(expression, scope = {})
54
+ parse(expression).evaluate(scope)
55
+ end
56
+ end