nmatrix 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.
- 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
|