nmatrix 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +23 -0
- data/.gemtest +0 -0
- data/Gemfile +7 -0
- data/History.txt +6 -0
- data/LICENSE.txt +21 -0
- data/Manifest.txt +51 -0
- data/README.rdoc +63 -0
- data/Rakefile +154 -0
- data/ext/nmatrix/cblas.c +150 -0
- data/ext/nmatrix/dense.c +307 -0
- data/ext/nmatrix/dense/blas_header.template.c +52 -0
- data/ext/nmatrix/dense/elementwise.template.c +107 -0
- data/ext/nmatrix/dense/gemm.template.c +159 -0
- data/ext/nmatrix/dense/gemv.template.c +130 -0
- data/ext/nmatrix/dense/rationalmath.template.c +68 -0
- data/ext/nmatrix/depend +18 -0
- data/ext/nmatrix/extconf.rb +143 -0
- data/ext/nmatrix/generator.rb +594 -0
- data/ext/nmatrix/generator/syntax_tree.rb +481 -0
- data/ext/nmatrix/list.c +774 -0
- data/ext/nmatrix/nmatrix.c +1977 -0
- data/ext/nmatrix/nmatrix.h +912 -0
- data/ext/nmatrix/rational.c +98 -0
- data/ext/nmatrix/yale.c +726 -0
- data/ext/nmatrix/yale/complexmath.template.c +71 -0
- data/ext/nmatrix/yale/elementwise.template.c +46 -0
- data/ext/nmatrix/yale/elementwise_op.template.c +73 -0
- data/ext/nmatrix/yale/numbmm.template.c +94 -0
- data/ext/nmatrix/yale/smmp1.template.c +21 -0
- data/ext/nmatrix/yale/smmp1_header.template.c +38 -0
- data/ext/nmatrix/yale/smmp2.template.c +43 -0
- data/ext/nmatrix/yale/smmp2_header.template.c +46 -0
- data/ext/nmatrix/yale/sort_columns.template.c +56 -0
- data/ext/nmatrix/yale/symbmm.template.c +54 -0
- data/ext/nmatrix/yale/transp.template.c +68 -0
- data/lib/array.rb +67 -0
- data/lib/nmatrix.rb +263 -0
- data/lib/string.rb +65 -0
- data/spec/nmatrix_spec.rb +395 -0
- data/spec/nmatrix_yale_spec.rb +239 -0
- data/spec/nvector_spec.rb +43 -0
- data/spec/syntax_tree_spec.rb +46 -0
- metadata +150 -0
@@ -0,0 +1,481 @@
|
|
1
|
+
# = NMatrix
|
2
|
+
#
|
3
|
+
# A linear algebra library for scientific computation in Ruby.
|
4
|
+
# NMatrix is part of SciRuby.
|
5
|
+
#
|
6
|
+
# NMatrix was originally inspired by and derived from NArray, by
|
7
|
+
# Masahiro Tanaka: http://narray.rubyforge.org
|
8
|
+
#
|
9
|
+
# == Copyright Information
|
10
|
+
#
|
11
|
+
# SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
|
12
|
+
# NMatrix is Copyright (c) 2012, Ruby Science Foundation
|
13
|
+
#
|
14
|
+
# Please see LICENSE.txt for additional copyright notices.
|
15
|
+
#
|
16
|
+
# == Contributing
|
17
|
+
#
|
18
|
+
# By contributing source code to SciRuby, you agree to be bound by
|
19
|
+
# our Contributor Agreement:
|
20
|
+
#
|
21
|
+
# * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
|
22
|
+
#
|
23
|
+
# == syntax_tree.rb
|
24
|
+
#
|
25
|
+
# Syntax Tree for mathematical expression parsing. Produces the
|
26
|
+
# correct forms for each type in NMatrix. Used by the C templates
|
27
|
+
# in generator.rb.
|
28
|
+
#
|
29
|
+
|
30
|
+
class Identifier < String
|
31
|
+
def operate type=nil,exact_type=nil
|
32
|
+
[type == :value || type == :object && to_s == "0" ? "RUBY_ZERO" : to_s]
|
33
|
+
end
|
34
|
+
|
35
|
+
def depth; 0; end
|
36
|
+
|
37
|
+
# These are just dummy functions which alias to_s
|
38
|
+
|
39
|
+
def operate_complex exact_type=nil
|
40
|
+
[to_s]
|
41
|
+
end
|
42
|
+
|
43
|
+
def operate_rational exact_type=nil
|
44
|
+
[to_s]
|
45
|
+
end
|
46
|
+
|
47
|
+
def operate_value exact_type=nil
|
48
|
+
[to_s]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
class SyntaxTree
|
54
|
+
EQ = :'='
|
55
|
+
# Changing the order of these arrays will change the relative precedence of the operators.
|
56
|
+
ASSIGN_OPS = %w{+= -= *= /= %= &= ^= |= >>= <<=}.map { |o| o.intern }
|
57
|
+
COMP_OPS = %w{<= >= == != < >}.map { |o| o.intern }
|
58
|
+
TRANSITIVE_BINARY_OPS = %w{&& || & ~ + *}.map { |o| o.intern }
|
59
|
+
BINARY_OPS = %w{>> << && || & | ^ - + * / %}.map { |o| o.intern }
|
60
|
+
BITWISE_BINARY_OPS = %w{& | ^}.map { |o| o.intern }
|
61
|
+
UNARY_OPS = %w{~ - !}
|
62
|
+
DISALLOWED_OPS = %w{++ --}.map { |o| o.intern }
|
63
|
+
OPS = (%w{>> <<}.concat(ASSIGN_OPS + [EQ]).concat(COMP_OPS).concat(%w{- + * / % & | ^}).concat(UNARY_OPS)).map { |o| o.intern } # skipping: >> << && || & | ~
|
64
|
+
|
65
|
+
FLIP_OPS = {:'<=' => :'>=',
|
66
|
+
:'>=' => :'<=',
|
67
|
+
:'==' => :'==',
|
68
|
+
:'!=' => :'!=',
|
69
|
+
:'>' => :'<',
|
70
|
+
:'<' => :'>' }
|
71
|
+
|
72
|
+
# Generally this is only used internally. Instead, call SyntaxTree.parse(operation), which will
|
73
|
+
# in turn correctly call SyntaxTree::initialize.
|
74
|
+
def initialize op, left, right
|
75
|
+
if left == "0" && COMP_OPS.include?(op) # Flip certain operations so that the 0 is on the right.
|
76
|
+
@op = FLIP_OPS[op]
|
77
|
+
@left = right
|
78
|
+
@right = left
|
79
|
+
elsif left.is_a?(String) && left.size == 0
|
80
|
+
@op = op
|
81
|
+
@left = nil
|
82
|
+
@right = right
|
83
|
+
else
|
84
|
+
@op = op
|
85
|
+
@left = left
|
86
|
+
@right = right
|
87
|
+
end
|
88
|
+
end
|
89
|
+
attr_reader :op
|
90
|
+
attr_accessor :left, :right
|
91
|
+
|
92
|
+
|
93
|
+
def unary?
|
94
|
+
left.nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def is_boolean?
|
99
|
+
COMP_OPS.include?(op)
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
# Flip an operation, if possible. If not, do nothing.
|
104
|
+
def reverse!
|
105
|
+
if COMP_OPS.include?(op)
|
106
|
+
@op = FLIP_OPS[op]
|
107
|
+
tmp = @left
|
108
|
+
@left = @right
|
109
|
+
@right = tmp
|
110
|
+
elsif TRANSITIVE_BINARY_OPS.include?(op)
|
111
|
+
tmp = @left
|
112
|
+
@left = @right
|
113
|
+
@right = tmp
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Flip an operation
|
118
|
+
def reverse
|
119
|
+
self.dup.reverse!
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# Expand the syntax tree in place on ASSIGN_OPS. If @op is not one of the ASSIGN_OPS, do nothing.
|
124
|
+
def expand!
|
125
|
+
if ASSIGN_OPS.include?(@op)
|
126
|
+
old_op = @op
|
127
|
+
@op = EQ
|
128
|
+
old_right = @right
|
129
|
+
@right = SyntaxTree.new(old_op.to_s[0...old_op.size-1].intern, @left, old_right)
|
130
|
+
end
|
131
|
+
self
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# Copy and expand the syntax tree on ASSIGN_OPS. e.g., += becomes = and +
|
136
|
+
def expand
|
137
|
+
self.dup.expand!
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def depth
|
142
|
+
unary? ? right.depth : (left.depth > right.depth ? left.depth : right.depth) + 1
|
143
|
+
end
|
144
|
+
|
145
|
+
# Split the SyntaxTree into a whole bunch of simpler ones appropriate for doing struct-type operations.
|
146
|
+
def simplify_split! count=1
|
147
|
+
expand!
|
148
|
+
ary = [self]
|
149
|
+
if op == EQ
|
150
|
+
if depth <= 2
|
151
|
+
ary
|
152
|
+
else
|
153
|
+
ary
|
154
|
+
if right.left.is_a?(SyntaxTree)
|
155
|
+
old_right_left = right.left
|
156
|
+
right.left = Identifier.new("temp#{count}")
|
157
|
+
new_right_left = SyntaxTree.new(EQ, right.left, old_right_left)
|
158
|
+
count += 1
|
159
|
+
ary = ary.concat(new_right_left.simplify_split!(count))
|
160
|
+
end
|
161
|
+
|
162
|
+
if right.right.is_a?(SyntaxTree)
|
163
|
+
old_right_right = right.right
|
164
|
+
right.right = Identifier.new("temp#{count}")
|
165
|
+
new_right_right = SyntaxTree.new(EQ, right.right, old_right_right)
|
166
|
+
count += 1
|
167
|
+
ary = ary.concat(new_right_right.simplify_split!(count))
|
168
|
+
end
|
169
|
+
|
170
|
+
ary.reverse
|
171
|
+
end
|
172
|
+
else
|
173
|
+
ary
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def simplify_split count=1
|
178
|
+
dup.simplify_split! count
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
# Deep copy.
|
183
|
+
def dup
|
184
|
+
SyntaxTree.new(op, (unary? ? nil : left.dup), right.dup)
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
# Split tree into multiple simpler subtrees and figure out the appropriate operation on each of those.
|
189
|
+
#
|
190
|
+
# Only needed for rational and complex, which are structs and can't rely on basic operators like * and +.
|
191
|
+
def operate_on_subtrees type, exact_type
|
192
|
+
i = 0
|
193
|
+
best_type = begin
|
194
|
+
case type
|
195
|
+
when :rational
|
196
|
+
:r128
|
197
|
+
when :complex
|
198
|
+
:c128
|
199
|
+
when :object
|
200
|
+
:v
|
201
|
+
end
|
202
|
+
end
|
203
|
+
subtrees = simplify_split
|
204
|
+
subtrees.map do |subtree|
|
205
|
+
i += 1
|
206
|
+
i == subtrees.size ? subtree.send("operate_#{type}", exact_type) : subtree.send("operate_#{type}", best_type)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
def operate type, exact_type=nil
|
212
|
+
operations =
|
213
|
+
case type
|
214
|
+
when :int, :float
|
215
|
+
operate_simple(type, exact_type)
|
216
|
+
when :rational, :complex, :value
|
217
|
+
operate_on_subtrees(type, exact_type).flatten
|
218
|
+
else
|
219
|
+
raise ArgumentError, "undefined math expression type '#{type}'"
|
220
|
+
end
|
221
|
+
|
222
|
+
# Separate boolean operations by && or ; as appropriate for the type of operation.
|
223
|
+
# This is overly simplistic, and won't work for things like x = y && z; but, it will
|
224
|
+
# work for the majority of cases we need to deal with in our templates.
|
225
|
+
#COMP_OPS.include?(op) ? operations.join(" && ") : operations.join(";\n") + ";"
|
226
|
+
operations
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
protected
|
231
|
+
def is_expression_boolean? expr
|
232
|
+
expr =~ /^\(.*(==|!=|>=|<=| < | > |&&|\|\|)+.*\)$/ || expr =~ /^!/
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
def operate_simple type, exact_type
|
237
|
+
if unary?
|
238
|
+
if op == :'~' && type == :float
|
239
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on floats\")"]
|
240
|
+
else
|
241
|
+
["#{op}#{right.operate(type, exact_type).first}"]
|
242
|
+
end
|
243
|
+
elsif op == EQ
|
244
|
+
r = right.operate(type, exact_type).first
|
245
|
+
r =~ /^rb_raise\(rb_eSyntaxError/ ? [r] : ["#{left} = #{r}"]
|
246
|
+
elsif op == :'%' && type == :float
|
247
|
+
["fmod(#{left.operate(type, exact_type).first}, #{right.operate(type, exact_type).first})"]
|
248
|
+
elsif [:'&', :'|', :'^', :'<<', :'>>'].include?(op)
|
249
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on floats\")"]
|
250
|
+
else
|
251
|
+
["#{left.operate(type, exact_type).first} #{op} #{right.operate(type, exact_type).first}"]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Figure out what to do on the right side of the operation if we're dealing with Ruby objects and unary operators
|
256
|
+
def operate_value_right
|
257
|
+
if !right.is_a?(SyntaxTree) && right.to_i.to_s == right
|
258
|
+
unary? ? "INT2FIX(#{op}#{right})" : "INT2FIX(#{right})"
|
259
|
+
elsif right.is_a?(SyntaxTree) && right.unary? && right.right.to_i.to_s == right.right
|
260
|
+
"INT2FIX(#{op}#{right})"
|
261
|
+
else
|
262
|
+
right.operate_value.first
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def operate_value exact_type=nil # exact_type is not used.
|
267
|
+
if unary?
|
268
|
+
["rb_funcall(#{operate_value_right}, rb_intern(\"#{op}@\"), 0)"] # e.g., -@ for negative
|
269
|
+
else
|
270
|
+
case op
|
271
|
+
when EQ
|
272
|
+
["#{left} #{op} #{operate_value_right}"]
|
273
|
+
when *COMP_OPS
|
274
|
+
["rb_funcall(#{left.operate_value.first}, rb_intern(\"#{op}\"), 1, #{operate_value_right}) == Qtrue"]
|
275
|
+
else
|
276
|
+
["rb_funcall(#{left.operate_value.first}, rb_intern(\"#{op}\"), 1, #{operate_value_right})"]
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def operate_rational exact_type=:r128
|
282
|
+
if unary?
|
283
|
+
if op == :'-'
|
284
|
+
["#{exact_type}_negate(#{right}.n, #{right}.d)"]
|
285
|
+
elsif op == :'~'
|
286
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
|
287
|
+
elsif op == :'!'
|
288
|
+
["#{exact_type}_bang(#{right}.n, #{right}.d)"]
|
289
|
+
else
|
290
|
+
raise NotImplementedError, "unhandled unary operation #{op} on complex and 0"
|
291
|
+
end
|
292
|
+
elsif right == "0"
|
293
|
+
if op == EQ
|
294
|
+
["#{left}.n = 0", " #{left}.d = 1"]
|
295
|
+
elsif COMP_OPS.include?(op)
|
296
|
+
["#{left}.n #{op} 0"]
|
297
|
+
else
|
298
|
+
raise NotImplementedError, "unhandled operation #{op} on rational and 0"
|
299
|
+
end
|
300
|
+
elsif right == "1"
|
301
|
+
if op == EQ
|
302
|
+
["#{left}.n = #{left}.d = 1"]
|
303
|
+
elsif COMP_OPS.include?(op)
|
304
|
+
["#{left}.n #{op} #{left}.d"]
|
305
|
+
elsif [:'+', :'-'].include?(op)
|
306
|
+
["#{exact_type}_addsub(#{left}.n, #{left}.d, #{left}.d, #{left}.d, '#{op}')"]
|
307
|
+
else
|
308
|
+
raise NotImplementedError, "unhandled operation #{op} on rational and 1"
|
309
|
+
end
|
310
|
+
else
|
311
|
+
if op == EQ
|
312
|
+
r = right.operate_rational(exact_type).first
|
313
|
+
if r =~ /^rb_raise\(rb_eSyntaxError/
|
314
|
+
[r]
|
315
|
+
elsif is_expression_boolean?(r) # x = boolean: can't assign directly.
|
316
|
+
["#{left} = BOOL2#{exact_type.to_s.upcase}(#{r})"]
|
317
|
+
else
|
318
|
+
["#{left} = #{right.operate_rational(exact_type).first}"]
|
319
|
+
end
|
320
|
+
elsif op == :'=='
|
321
|
+
["(#{left}.n == #{right}.n && #{left}.d == #{right}.d)"]
|
322
|
+
elsif COMP_OPS.include?(op)
|
323
|
+
["(#{left}.n / (double)(#{left}.d) #{op} #{right}.n / (double)(#{right}.d))"] # TODO: Is there a faster way?
|
324
|
+
elsif [:'+', :'-'].include?(op)
|
325
|
+
["#{exact_type}_addsub(#{left}.n, #{left}.d, #{right}.n, #{right}.d, '#{op}')"]
|
326
|
+
elsif [:'*', :'/'].include?(op)
|
327
|
+
["#{exact_type}_muldiv(#{left}.n, #{left}.d, #{right}.n, #{right}.d, '#{op}')"]
|
328
|
+
elsif op == :'%'
|
329
|
+
["#{exact_type}_mod(#{left}.n, #{left}.d, #{right}.n, #{right}.d)"]
|
330
|
+
elsif op == :'<<'
|
331
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
|
332
|
+
elsif op == :'>>'
|
333
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
|
334
|
+
elsif BITWISE_BINARY_OPS.include?(op)
|
335
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
|
336
|
+
else
|
337
|
+
raise NotImplementedError, "unhandled operation #{op} on rationals"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
|
343
|
+
def operate_complex_boolean_helper exact_type, real_comp, join = :'&&', imag_comp=nil
|
344
|
+
imag_comp ||= real_comp
|
345
|
+
["(#{left.operate_complex(exact_type).first}.r #{real_comp} #{right.operate_complex(exact_type).first}.r && #{left.operate_complex(exact_type).first}.i #{imag_comp} #{right.operate_complex(exact_type).first}.i)"]
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
def operate_complex exact_type=:c128
|
350
|
+
if unary?
|
351
|
+
if op == :'-'
|
352
|
+
["#{exact_type}_negate(#{right}.r, #{right}.i)"]
|
353
|
+
elsif op == :'~'
|
354
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on complex numbers\")"]
|
355
|
+
elsif op == :'!'
|
356
|
+
["#{exact_type}_bang(#{right}.r, #{right}.i)"]
|
357
|
+
else
|
358
|
+
raise NotImplementedError, "unhandled unary operation #{op} on complex and 0"
|
359
|
+
end
|
360
|
+
elsif right == "0"
|
361
|
+
if op == EQ
|
362
|
+
["#{left}.r = 0", " #{left}.i = 0"]
|
363
|
+
elsif op == :'=='
|
364
|
+
["(#{left}.r == 0 && #{left}.i == 0)"]
|
365
|
+
elsif op == :'!='
|
366
|
+
["(#{left}.r != 0 || #{left}.i != 0)"]
|
367
|
+
elsif op == :'>'
|
368
|
+
["(#{left}.r > 0 && #{left}.i > 0)"]
|
369
|
+
elsif op == :'<'
|
370
|
+
["(#{left}.r < 0 && #{left}.i < 0)"]
|
371
|
+
elsif op == :'>='
|
372
|
+
["(!(#{left}.r < 0 || #{left}.i < 0)"]
|
373
|
+
elsif op == :'<='
|
374
|
+
["(!(#{left}.r > 0 || #{left}.i > 0)"]
|
375
|
+
else
|
376
|
+
raise NotImplementedError, "unhandled operation #{op} on complex and 0"
|
377
|
+
end
|
378
|
+
else
|
379
|
+
if op == EQ
|
380
|
+
r = right.operate_complex(exact_type).first
|
381
|
+
if r =~ /^rb_raise\(rb_eSyntaxError/
|
382
|
+
[r]
|
383
|
+
elsif is_expression_boolean?(r) # x = boolean: can't assign directly.
|
384
|
+
["#{left} = BOOL2#{exact_type.to_s.upcase}(#{r})"]
|
385
|
+
else
|
386
|
+
["#{left} = #{r}"]
|
387
|
+
end
|
388
|
+
elsif op == :'=='
|
389
|
+
operate_complex_boolean_helper(exact_type, op)
|
390
|
+
elsif op == :'!='
|
391
|
+
operate_complex_boolean_helper(exact_type, op, :'||')
|
392
|
+
elsif op == :'>'
|
393
|
+
operate_complex_boolean_helper(exact_type, op)
|
394
|
+
elsif op == :'<'
|
395
|
+
operate_complex_boolean_helper(exact_type, op)
|
396
|
+
elsif op == :'>='
|
397
|
+
operate_complex_boolean_helper(exact_type, op)
|
398
|
+
elsif op == :'<='
|
399
|
+
operate_complex_boolean_helper(exact_type, op)
|
400
|
+
elsif op == :'+'
|
401
|
+
["#{exact_type}_add(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
|
402
|
+
elsif op == :'-'
|
403
|
+
["#{exact_type}_sub(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
|
404
|
+
elsif op == :'*'
|
405
|
+
["#{exact_type}_mul(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
|
406
|
+
elsif op == :'/'
|
407
|
+
["#{exact_type}_div(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
|
408
|
+
elsif op == :'%'
|
409
|
+
["#{exact_type}_mod(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
|
410
|
+
elsif [:'&', :'|', :'^', :'<<', :'>>'].include?(op)
|
411
|
+
["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on complex numbers\")"]
|
412
|
+
elsif op == :'!'
|
413
|
+
["0"]
|
414
|
+
else
|
415
|
+
STDERR.puts "WARNING: unhandled operation #{op} on complex numbers"
|
416
|
+
["0"]
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
public
|
422
|
+
class << self
|
423
|
+
|
424
|
+
# Parse an operation template of some kind into a SyntaxTree or, if no operation present,
|
425
|
+
# into an Identifier (basically a string)
|
426
|
+
def parse str
|
427
|
+
SyntaxTree::OPS.each do |op_symbol|
|
428
|
+
op = op_symbol.to_s
|
429
|
+
pos = str.index(op, 1)
|
430
|
+
|
431
|
+
while !pos.nil? # Need to look for each operator multiple times in a line
|
432
|
+
|
433
|
+
raise(ArgumentError, "disallowed operation '#{op}' in expression") if DISALLOWED_OPS.include?(op_symbol)
|
434
|
+
|
435
|
+
# Is the operator contained within brackets?
|
436
|
+
lb = str.rindex('[', pos)
|
437
|
+
unless lb.nil?
|
438
|
+
rb = str.rindex(']', pos) # Perhaps that bracket is terminated?
|
439
|
+
rb = str.index(']', pos+op.size) if rb.nil? || rb < lb # still have to worry; try looking after the operator
|
440
|
+
|
441
|
+
if BINARY_OPS.include?(op_symbol) && !rb.nil? && lb < pos && rb >= pos
|
442
|
+
# Operator IS within brackets. Let's start searching again after the brackets.
|
443
|
+
#STDERR.puts "operator #{op_symbol} was found, but within brackets (#{lb}, #{pos}, #{rb}) '#{str}'"
|
444
|
+
pos = str.index(op, rb+1)
|
445
|
+
next
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# If we get this far, the operator was not found within brackets.
|
450
|
+
|
451
|
+
# Split around the operator
|
452
|
+
left = str[0...pos].strip
|
453
|
+
right = str[pos+op.size...str.size].strip
|
454
|
+
|
455
|
+
# We don't want to process x <= y as 'x <' = 'y', but as 'x' <= 'y':
|
456
|
+
if op_symbol == EQ
|
457
|
+
if SyntaxTree::OPS.include?((left[-1]+'=').intern)
|
458
|
+
pos = str.index(op, pos+1)
|
459
|
+
next
|
460
|
+
elsif SyntaxTree::OPS.include?(('=' + right[0]).intern) # Same for ==
|
461
|
+
pos = str.index(op, pos+2)
|
462
|
+
next
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
STDERR.puts "operator #{op_symbol} was found (#{pos}); parsing '#{left}' and '#{right}'"
|
467
|
+
return SyntaxTree.new(op_symbol, SyntaxTree.parse(left), SyntaxTree.parse(right))
|
468
|
+
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
if str =~ /[~\-!]/
|
473
|
+
STDERR.puts "unary operator #{str[0]} was found (0); making identifier of #{str[1...str.size]}"
|
474
|
+
SyntaxTree.new(str[0].intern, "", Identifier.new(str[1...str.size]))
|
475
|
+
else
|
476
|
+
STDERR.puts "Making identifier out of #{str}"
|
477
|
+
Identifier.new(str)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|