rucas 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/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
|