rucas 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/History.txt +2 -0
- data/Manifest.txt +14 -0
- data/README.txt +108 -0
- data/README.txt.erb +157 -0
- data/Rakefile +35 -0
- data/bin/rucas +69 -0
- data/lib/rucas.rb +48 -0
- data/lib/rucas/extensions.rb +99 -0
- data/lib/rucas/rewrite.rb +181 -0
- data/lib/rucas/simplify.rb +102 -0
- data/lib/rucas/symbolic.rb +238 -0
- data/lib/rucas/utility.rb +11 -0
- data/test/test_rucas.rb +216 -0
- metadata +89 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'rucas/symbolic'
|
2
|
+
|
3
|
+
module Rucas
|
4
|
+
module Extensions
|
5
|
+
#
|
6
|
+
# Avoid collisions with other patches; this maps operators (+,-, etc.) to
|
7
|
+
# normal method names, so they can be called without the additional overhead
|
8
|
+
# of Kernel#send.
|
9
|
+
#
|
10
|
+
PATCH_HASH = Hash.new {|h,k| k} # return key by default
|
11
|
+
PATCH_HASH[:+] = :add
|
12
|
+
PATCH_HASH[:-] = :sub
|
13
|
+
PATCH_HASH[:*] = :mul
|
14
|
+
PATCH_HASH[:/] = :div
|
15
|
+
PATCH_HASH[:**] = :pow
|
16
|
+
PATCH_HASH[:=~] = :eqs
|
17
|
+
PATCH_HASH[:<] = :lt
|
18
|
+
PATCH_HASH[:<=] = :lte
|
19
|
+
PATCH_HASH[:>] = :gt
|
20
|
+
PATCH_HASH[:>=] = :gte
|
21
|
+
PATCH_HASH[:&] = :and
|
22
|
+
PATCH_HASH[:|] = :or
|
23
|
+
raise "PATCH_HASH does not match BINARY_OPS" unless
|
24
|
+
Symbolic::BINARY_OPS.keys.all?{|k| PATCH_HASH.member?(k)}
|
25
|
+
|
26
|
+
# See PATCH_HASH.
|
27
|
+
def self.without_rucas_name original
|
28
|
+
"#{PATCH_HASH[original]}__without_rucas"
|
29
|
+
end
|
30
|
+
|
31
|
+
# See PATCH_HASH.
|
32
|
+
def self.with_rucas_name original
|
33
|
+
"#{PATCH_HASH[original]}__with_rucas"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add rucas methods constant classes, but don't alias_method them yet; that
|
37
|
+
# is handled in the apply and unapply methods.
|
38
|
+
for c in Symbolic::CONST_CLASSES
|
39
|
+
for op in Symbolic::BINARY_OPS.keys
|
40
|
+
c.class_eval %Q{
|
41
|
+
def #{with_rucas_name(op)} rhs
|
42
|
+
if rhs.is_a?(Rucas::Symbolic::Expr)
|
43
|
+
Rucas::Symbolic::ConstExpr.new(self) #{op} rhs
|
44
|
+
else
|
45
|
+
self.#{without_rucas_name(op)}(rhs)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Try to convert to ConstExpr if can't find method (e.g. +simplify+).
|
52
|
+
c.module_eval do
|
53
|
+
def method_missing_with_rucas method, *args, &block
|
54
|
+
method_missing_without_rucas(method *args, &block)
|
55
|
+
rescue NoMethodError
|
56
|
+
error = $!
|
57
|
+
begin
|
58
|
+
return Symbolic::Expr.make(self).send(method, *args, &block)
|
59
|
+
rescue NoMethodError
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
raise error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Patch system classes to make constants work with vars (e.g. 0 + x).
|
68
|
+
def self.apply
|
69
|
+
for c in Symbolic::CONST_CLASSES
|
70
|
+
for op in Symbolic::BINARY_OPS.keys
|
71
|
+
c.class_eval %Q{
|
72
|
+
alias_method :#{without_rucas_name(op)}, :#{op}
|
73
|
+
alias_method :#{op}, :#{with_rucas_name(op)}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
c.module_eval do
|
77
|
+
alias_method :method_missing_without_rucas, :method_missing
|
78
|
+
alias_method :method_missing, :method_missing_with_rucas
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get rid of patches.
|
84
|
+
def self.unapply
|
85
|
+
for c in Symbolic::CONST_CLASSES
|
86
|
+
for op in Symbolic::BINARY_OPS.keys
|
87
|
+
c.class_eval %Q{
|
88
|
+
alias_method :#{with_rucas_name(op)}, :#{op}
|
89
|
+
alias_method :#{op}, :#{without_rucas_name(op)}
|
90
|
+
}
|
91
|
+
end
|
92
|
+
c.module_eval do
|
93
|
+
alias_method :method_missing_with_rucas, :method_missing
|
94
|
+
alias_method :method_missing, :method_missing_without_rucas
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'rucas/symbolic'
|
2
|
+
require 'facets/dictionary'
|
3
|
+
|
4
|
+
module Rucas
|
5
|
+
#
|
6
|
+
# Tools for recursively rewriting expressions based on rules.
|
7
|
+
#
|
8
|
+
module Rewrite
|
9
|
+
#
|
10
|
+
# Construct an ordered hash of rewrite rules.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# my_rules = make_rules {
|
14
|
+
# var :x
|
15
|
+
# rule x + 0 => x
|
16
|
+
# rule 0 + x => x
|
17
|
+
# }
|
18
|
+
#
|
19
|
+
def self.make_rules &block
|
20
|
+
s = Scope.new
|
21
|
+
class <<s
|
22
|
+
def dict
|
23
|
+
@dict ||= Dictionary[]
|
24
|
+
end
|
25
|
+
def rule rule
|
26
|
+
raise unless rule.size == 1
|
27
|
+
dict[rule.keys.first] = Symbolic::Expr.make(rule.values.first)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
s.rucas(&block)
|
31
|
+
s.dict
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Symbolic
|
36
|
+
module Expr
|
37
|
+
#
|
38
|
+
# If this expression matches +pattern+, return +output+ (with appropriate
|
39
|
+
# bindings of variables in pattern applied to output); otherwise, return
|
40
|
+
# self (or an object == to self).
|
41
|
+
#
|
42
|
+
def rewrite pattern, output
|
43
|
+
bindings = pattern.match(self)
|
44
|
+
return output.with(bindings) if bindings
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Non-recursive matching. Here, self is a pattern and +expr+ is the input
|
50
|
+
# to be matched against this pattern. Returns bindings for the free
|
51
|
+
# variables in self that make the match succeed, or nil if there is no
|
52
|
+
# such match.
|
53
|
+
#
|
54
|
+
def match expr, bindings = {}
|
55
|
+
nil # see subclasses
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# If this expression involves only constants, evaluate it and return the
|
60
|
+
# result; if it is not, return nil.
|
61
|
+
#
|
62
|
+
def value
|
63
|
+
nil # see subclasses
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module OpExpr
|
68
|
+
def rewrite pattern, output
|
69
|
+
# Rewrite children first.
|
70
|
+
new_children = self.children.map{|c| c.rewrite(pattern, output)}
|
71
|
+
new_self = self.class.new(*new_children)
|
72
|
+
# See if it's a constant.
|
73
|
+
v = new_self.value
|
74
|
+
if v
|
75
|
+
Symbolic::Expr.make(v)
|
76
|
+
else
|
77
|
+
# It's not a constant; try to rewrite using the rule.
|
78
|
+
bindings = pattern.match(new_self)
|
79
|
+
return output.with(bindings) if bindings
|
80
|
+
new_self
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def with bindings
|
85
|
+
self.class.new(*self.children.map{|c| c.with(bindings)})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ConstExpr
|
90
|
+
def match expr, bindings = {}
|
91
|
+
return bindings if self == expr
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def with bindings
|
96
|
+
self
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class VarExpr
|
101
|
+
def match expr, bindings = {}
|
102
|
+
binding = bindings[self]
|
103
|
+
return bindings.merge(self => expr) if !binding
|
104
|
+
return bindings if binding == expr
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def with bindings
|
109
|
+
bindings[self] || self
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class UnaryOpExpr
|
114
|
+
def match expr, bindings = {}
|
115
|
+
return nil unless expr.is_a?(UnaryOpExpr)
|
116
|
+
return nil unless self.op == expr.op
|
117
|
+
rhs.match(expr.rhs, bindings)
|
118
|
+
end
|
119
|
+
|
120
|
+
def value
|
121
|
+
v = rhs.value
|
122
|
+
eval "(#{v}).#{self.op}" if v
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class BinaryOpExpr
|
127
|
+
def match expr, bindings = {}
|
128
|
+
if expr.is_a?(BinaryOpExpr) && self.op == expr.op
|
129
|
+
lb = self.lhs.match(expr.lhs, bindings)
|
130
|
+
rb = self.rhs.match(expr.rhs, lb) if lb
|
131
|
+
return rb if rb
|
132
|
+
end
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def value
|
137
|
+
lv = lhs.value
|
138
|
+
rv = rhs.value
|
139
|
+
eval "(#{lv})#{self.op}(#{rv})" if lv && rv
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# module Expr
|
146
|
+
# def simplify bindings={}, rules=DEFAULT_SIMPLIFY_RULES
|
147
|
+
# for rule in rules
|
148
|
+
# new_bindings = self.match(rule.lhs, bindings)
|
149
|
+
# return rule.rhs.with(new_bindings).simplify(bindings,rules) if
|
150
|
+
# new_bindings
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
|
154
|
+
# rule: pair of expressions, which may refer to matchers
|
155
|
+
# matcher: name, predicate block
|
156
|
+
# bindings: map from matcher to expression
|
157
|
+
# the result of matching an expression with another expression should be
|
158
|
+
# false if the match fails, or it should return bindings for the matchers
|
159
|
+
# that make the match succeed.
|
160
|
+
# note: match is not commutative; it's probably easiest if we return
|
161
|
+
# bindings for the lhs (self), since most of the tricky stuff then goes
|
162
|
+
# into VarExpr. This isn't maximally rubyish: "foo" =~ /foo/ -- the
|
163
|
+
# pattern comes last -- but /foo/.match("foo") is also valid.
|
164
|
+
# note: a convention is also required for handling things like
|
165
|
+
# (x + y).match(a - b) -- this could succeed with x => a, y => -b, but it
|
166
|
+
# requires more checks -- can't just check op of root.
|
167
|
+
# def match expr
|
168
|
+
#
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# Simplification isn't really recursive (in the tree structure) -- we have
|
173
|
+
# to apply a list of rules, and we restart when the current rule matches
|
174
|
+
# any part of the expression tree.
|
175
|
+
# The simplification process for one rewrite rule could be recursive: do a
|
176
|
+
# post-order traversal, rewriting children; at the top level, we just
|
177
|
+
# compare the whole resulting expression with the original to see if
|
178
|
+
# anything's changed.
|
179
|
+
# The match is then non-recursive.
|
180
|
+
#
|
181
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rucas/rewrite'
|
2
|
+
|
3
|
+
module Rucas
|
4
|
+
#
|
5
|
+
# Simplification; this is still fairly primitive.
|
6
|
+
#
|
7
|
+
# things that don't simplify:
|
8
|
+
# Examples from PAIP 8.3: commutativity and associativity
|
9
|
+
# {3*(2*x)}
|
10
|
+
# {2 * x * x * 3}
|
11
|
+
# {2 * x * 3 * y * 4 * z * 5 * 6}
|
12
|
+
# {3 + x + 4 + x}
|
13
|
+
# {2 * x * 3 * x * 4 * (1 / x) * 5 * 6}
|
14
|
+
#
|
15
|
+
module Simplify
|
16
|
+
#
|
17
|
+
# Ordered hash of simplification rules.
|
18
|
+
#
|
19
|
+
# The order of the rules is very important for avoiding unsoundness due to
|
20
|
+
# division by zero, for example.
|
21
|
+
#
|
22
|
+
# Note that division by zero always results in a NaN. If it were possible to
|
23
|
+
# prove that x was strictly positive (negative), x / 0 should arguably
|
24
|
+
# evaluate to +Inf (-Inf), but this is substantially beyond what we can do
|
25
|
+
# at this point.
|
26
|
+
#
|
27
|
+
RULES = Rewrite.make_rules {
|
28
|
+
var :w
|
29
|
+
var :x
|
30
|
+
var :y
|
31
|
+
z = Symbolic::Expr.make(0)
|
32
|
+
nan = Symbolic::Expr.make(0.0/0.0)
|
33
|
+
|
34
|
+
# These rules are taken from Norvig's Paradigms of AI Programming
|
35
|
+
# (chapter 8). The source for that book is freely available.
|
36
|
+
rule( x + 0 => x )
|
37
|
+
rule( 0 + x => x )
|
38
|
+
rule( x + x => 2*x )
|
39
|
+
rule( x - 0 => x )
|
40
|
+
rule( 0 - x => -x )
|
41
|
+
rule( x - x => 0 )
|
42
|
+
rule( +x => x )
|
43
|
+
rule( --x => x )
|
44
|
+
rule( x * 1 => x )
|
45
|
+
rule( 1 * x => x )
|
46
|
+
rule( x * 0 => 0 )
|
47
|
+
rule( 0 * x => 0 )
|
48
|
+
rule( x * x => x**2)
|
49
|
+
rule( x / 0 => nan )
|
50
|
+
rule( 0 / x => 0 )
|
51
|
+
rule( x / 1 => x )
|
52
|
+
rule( x / x => 1 )
|
53
|
+
rule( z ** 0 => 1 ) # note: Ruby says 0 ** 0 is 1; be consistent
|
54
|
+
rule( x ** 0 => 1 )
|
55
|
+
rule( 0 ** x => 0 )
|
56
|
+
rule( 1 ** x => 1 )
|
57
|
+
rule( x ** 1 => x )
|
58
|
+
rule( x ** -1 => 1 / x)
|
59
|
+
rule( x * (y / x) => y )
|
60
|
+
rule( (y / x) * x => y )
|
61
|
+
rule( (y * x) / x => y )
|
62
|
+
rule( (x * y) / x => y )
|
63
|
+
rule( x + -x => 0 )
|
64
|
+
rule( -x + x => 0 )
|
65
|
+
rule( x + y - x => y )
|
66
|
+
rule( x - y - x => -y )
|
67
|
+
|
68
|
+
# These rules are helpful for "rebalancing" long trees, which can make
|
69
|
+
# some of the rules above effective. This is because we don't actually
|
70
|
+
# handle associativity properly. These rules do slow things down a bit,
|
71
|
+
# but hopefully it doesn't break anything too badly.
|
72
|
+
#
|
73
|
+
# Interestingly, Norvig's "infix->prefix" functions used right-
|
74
|
+
# associative grouping, which made things go more smoothly. For example,
|
75
|
+
# x^2 + x + x + 1 simplifies to x^2 + 2*x + 1 without the rebalancing
|
76
|
+
# rule, if you group from the right (but the reverse doesn't simplify).
|
77
|
+
rule( (w + x) + y => w + (x + y) )
|
78
|
+
rule( (w * x) * y => w * (x * y) )
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
module Symbolic
|
83
|
+
module Expr
|
84
|
+
#
|
85
|
+
# Return expression after algebraic simplification. Note that this isn't a
|
86
|
+
# very smart simplifier.
|
87
|
+
#
|
88
|
+
def simplify
|
89
|
+
new_self = self
|
90
|
+
changed = false
|
91
|
+
for pattern, output in Simplify::RULES
|
92
|
+
new_self = new_self.rewrite(pattern, output)
|
93
|
+
#puts "#{pattern}\t#{new_self.to_s_paren}"
|
94
|
+
changed = (new_self != self)
|
95
|
+
break if changed
|
96
|
+
end
|
97
|
+
if changed then new_self.simplify else self end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,238 @@
|
|
1
|
+
module Rucas
|
2
|
+
#
|
3
|
+
# Translate Ruby expressions into expression trees for symbolic manipulation.
|
4
|
+
#
|
5
|
+
module Symbolic
|
6
|
+
include Utility
|
7
|
+
|
8
|
+
#
|
9
|
+
# Define variable in this scope and return it.
|
10
|
+
#
|
11
|
+
def var name
|
12
|
+
var = VarExpr.new(name)
|
13
|
+
meta_def var.name do
|
14
|
+
var
|
15
|
+
end
|
16
|
+
var
|
17
|
+
end
|
18
|
+
|
19
|
+
CONST_CLASSES = [Fixnum, Float, Bignum]
|
20
|
+
|
21
|
+
#
|
22
|
+
# Symbolic expression; subclasses represent various kinds of expressions.
|
23
|
+
#
|
24
|
+
module Expr
|
25
|
+
# Children in the expression tree, if any. For example, an AddExpr returns
|
26
|
+
# its left and right operands (x and y in x + y).
|
27
|
+
def children; [] end
|
28
|
+
|
29
|
+
# Operator precedence; this reflects Ruby's built-in order-of-operations
|
30
|
+
# rules. You should not rely on the particular values; only the ordering
|
31
|
+
# they define is guaranteed.
|
32
|
+
def precedence; 0 end
|
33
|
+
|
34
|
+
# String representation including all parentheses; by default, +to_s+
|
35
|
+
# omits parentheses when operator precedence rules allow.
|
36
|
+
def to_s_paren; to_s end
|
37
|
+
|
38
|
+
def self.make e
|
39
|
+
return e if e.is_a?(Expr)
|
40
|
+
return ConstExpr.new(e) if CONST_CLASSES.any? {|t| e.is_a?(t)}
|
41
|
+
raise "#{e} is not a symbolic expression"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Numeric constant.
|
46
|
+
ConstExpr = Struct.new(:value)
|
47
|
+
class ConstExpr
|
48
|
+
include Expr
|
49
|
+
|
50
|
+
def to_s; value.to_s end
|
51
|
+
def constant?; true end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Variable (literal).
|
55
|
+
VarExpr = Struct.new(:name)
|
56
|
+
class VarExpr
|
57
|
+
include Expr
|
58
|
+
|
59
|
+
def to_s; name.to_s end
|
60
|
+
def constant?; false end
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Expression with an operator and operand(s).
|
65
|
+
#
|
66
|
+
module OpExpr
|
67
|
+
include Expr
|
68
|
+
|
69
|
+
#
|
70
|
+
# The operators on the same line have the same precedence; the list is
|
71
|
+
# from highest precedence to lowest precedence. This list is from the
|
72
|
+
# Pickaxe book ("The Ruby Language").
|
73
|
+
#
|
74
|
+
op_precedence_table =
|
75
|
+
[%w(**),
|
76
|
+
%w(~@ +@ -@),
|
77
|
+
%w(* / %),
|
78
|
+
%w(+ -)]
|
79
|
+
|
80
|
+
#
|
81
|
+
# Operator precedence; you shouldn't rely on the numbers, but (barring
|
82
|
+
# changes to Ruby), the order shouldn't change.
|
83
|
+
#
|
84
|
+
OP_PRECEDENCE = Hash[*op_precedence_table.
|
85
|
+
zip((1..op_precedence_table.size).to_a).
|
86
|
+
map{|ops,prec| ops.map{|op| [op.to_sym, -prec]}}.flatten]
|
87
|
+
|
88
|
+
#
|
89
|
+
# Precedence (in Ruby) of this operator; this affects how expressions are
|
90
|
+
# parenthesized.
|
91
|
+
#
|
92
|
+
def precedence
|
93
|
+
OP_PRECEDENCE[self.op]
|
94
|
+
end
|
95
|
+
|
96
|
+
def constant?; children.all {|c| c.constant?} end
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Unary operations.
|
101
|
+
#
|
102
|
+
UnaryOpExpr = Struct.new(:op, :rhs)
|
103
|
+
class UnaryOpExpr
|
104
|
+
include OpExpr
|
105
|
+
|
106
|
+
def children; [rhs] end
|
107
|
+
|
108
|
+
def to_s_paren
|
109
|
+
"#{self.op}(#{self.rhs.to_s_paren})"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class PosExpr < UnaryOpExpr
|
114
|
+
def to_s; rhs.to_s end
|
115
|
+
end
|
116
|
+
|
117
|
+
class NegExpr < UnaryOpExpr
|
118
|
+
def to_s; rhs.precedence < self.precedence ? "-(#{rhs})" : "-#{rhs}" end
|
119
|
+
end
|
120
|
+
|
121
|
+
UNARY_OPS = {
|
122
|
+
:+@ => :PosExpr,
|
123
|
+
:-@ => :NegExpr,
|
124
|
+
:~@ => :NotExpr,
|
125
|
+
}
|
126
|
+
|
127
|
+
for op, op_class in UNARY_OPS
|
128
|
+
class_eval %Q{
|
129
|
+
class #{op_class}
|
130
|
+
def initialize(rhs)
|
131
|
+
self.op, self.rhs = :#{op}, rhs
|
132
|
+
end
|
133
|
+
end
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
module Expr
|
138
|
+
for op, op_class in UNARY_OPS
|
139
|
+
class_eval %Q{
|
140
|
+
def #{op} ; #{op_class}.new(self) end
|
141
|
+
}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Binary operations.
|
147
|
+
#
|
148
|
+
BinaryOpExpr = Struct.new(:op, :lhs, :rhs)
|
149
|
+
class BinaryOpExpr
|
150
|
+
include OpExpr
|
151
|
+
|
152
|
+
def children; [lhs, rhs] end
|
153
|
+
|
154
|
+
def to_s_paren
|
155
|
+
op_string = " #{self.op} "
|
156
|
+
"(#{self.children.map{|c| c.to_s_paren}.join(op_string)})"
|
157
|
+
end
|
158
|
+
|
159
|
+
def to_s
|
160
|
+
inner = self.children.map{|c|
|
161
|
+
c.precedence < self.precedence ? "(#{c})" : c.to_s}
|
162
|
+
"#{inner.join(self.op_string)}"
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
# Allow for control of spacing between operands.
|
167
|
+
def op_string; " #{self.op} " end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Arithmetic
|
171
|
+
class ArithmeticOpExpr < BinaryOpExpr ; end
|
172
|
+
class AddExpr < ArithmeticOpExpr; end
|
173
|
+
class SubExpr < ArithmeticOpExpr; end
|
174
|
+
class MulExpr < ArithmeticOpExpr; end
|
175
|
+
class DivExpr < ArithmeticOpExpr; end
|
176
|
+
class PowExpr < ArithmeticOpExpr; end
|
177
|
+
|
178
|
+
# Comparison
|
179
|
+
class CompareOpExpr < BinaryOpExpr ; end
|
180
|
+
class EqExpr < CompareOpExpr ; end
|
181
|
+
class LTExpr < CompareOpExpr ; end
|
182
|
+
class LTEqExpr < CompareOpExpr ; end
|
183
|
+
class GTExpr < CompareOpExpr ; end
|
184
|
+
class GTEqExpr < CompareOpExpr ; end
|
185
|
+
|
186
|
+
# Logic -- see comments below
|
187
|
+
#class BooleanOpExpr < BinaryOpExpr ; end
|
188
|
+
#class AndExpr < BooleanOpExpr ; end
|
189
|
+
#class OrExpr < BooleanOpExpr ; end
|
190
|
+
|
191
|
+
BINARY_OPS = {
|
192
|
+
:+ => :AddExpr,
|
193
|
+
:- => :SubExpr,
|
194
|
+
:* => :MulExpr,
|
195
|
+
:/ => :DivExpr,
|
196
|
+
:** => :PowExpr,
|
197
|
+
:=~ => :EqExpr, # =, == and === are all taken!
|
198
|
+
:< => :LTExpr,
|
199
|
+
:<= => :LTEqExpr,
|
200
|
+
:> => :GTExpr,
|
201
|
+
:>= => :GTEqExpr}
|
202
|
+
|
203
|
+
# Note: can use & and | for logic, but there are problems with
|
204
|
+
# precedence: it binds before the (in)equality symbols. Also need to
|
205
|
+
# change the simplification code to treat them specially. This needs more
|
206
|
+
# thinking.
|
207
|
+
# :& => :AndExpr,
|
208
|
+
# :| => :OrExpr}
|
209
|
+
|
210
|
+
for op, op_class in BINARY_OPS
|
211
|
+
class_eval %Q{
|
212
|
+
class #{op_class}
|
213
|
+
def initialize(lhs, rhs)
|
214
|
+
self.lhs, self.op, self.rhs = lhs, :#{op}, rhs
|
215
|
+
end
|
216
|
+
end
|
217
|
+
}
|
218
|
+
end
|
219
|
+
|
220
|
+
module Expr
|
221
|
+
for op, op_class in BINARY_OPS
|
222
|
+
class_eval %Q{
|
223
|
+
def #{op} rhs ; #{op_class}.new(self, Expr.make(rhs)) end
|
224
|
+
}
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class MulExpr
|
229
|
+
# Reduce spacing between factors.
|
230
|
+
def op_string; self.op.to_s end
|
231
|
+
end
|
232
|
+
|
233
|
+
class PowExpr
|
234
|
+
# Reduce spacing between base and exponent.
|
235
|
+
def op_string; self.op.to_s end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|