#!/usr/bin/ruby -w # rev 0.1 # see sicp 2.3.2 # TODO readline input, rewrite infix expression into []-form and derive # output: rewrite []-form into infix expression module Symbolic def derive exp, var if number?(exp) 0 elsif variable?(exp) same_variable?(exp, var) ? 1 : 0 elsif sum?(exp) make_sum(derive(addend(exp), var), derive(augend(exp), var)) elsif product?(exp) make_sum(make_product(multiplier(exp), derive(multiplicand(exp), var)), make_product(derive(multiplier(exp), var), multiplicand(exp))) else raise("error: unknown expression type #{exp}") end end def to_list exp if exp.is_a?(Array) "(" + exp.collect { |e| to_list(e) }.join(" ") + ")" else exp.to_s end end def number? x x.is_a?(Numeric) end def pair? exp exp.is_a?(Array) and exp.size == 2 end def variable? x x.is_a?(Symbol) end def same_variable? v1, v2 variable?(v1) and variable?(v2) and v1.eql?(v2) end def sum? x #pair?(x) and x[0].eql?(:+) x[0].eql?(:+) end def addend s s[1] end def augend s s[2] end def product? x #pair?(x) and x[0].eql?(:*) x[0].eql?(:*) end def multiplier p p[1] end def multiplicand p p[2] end def make_sum_unsimplified a1, a2 [:+, a1, a2] end def make_sum a1, a2 return a2 if isnumber?(a1, 0) return a1 if isnumber?(a2, 0) if number?(a1) and number?(a2) return a1 + a2 else return [:+, a1, a2] end end def make_product_unsimplified m1, m2 [:*, m1, m2] end def make_product m1, m2 return 0 if isnumber?(m1, 0) or isnumber?(m2, 0) return m2 if isnumber?(m1, 1) return m1 if isnumber?(m2, 1) if number?(m1) and number?(m2) return m1 * m2 else return [:*, m1, m2] end end def isnumber? exp, num number?(exp) and exp.eql?(num) end # def parse txt # if txt[0].chr == "(" # [parse(txt[1..-1])] # [txt.scan(/\w+|\(|\)/).collect # else # i = txt.index(" ") # txt[0..(i-1)] # end # end end include Symbolic [ [[:+, 1, :x], :x], [[:+, :x, :x], :x], [[:*, :x, :x], :x], [[:*, :x, :y], :x], [[:*, [:*, :x, :y], [:+, :x, 3]], :x] ].each { |exp| puts to_list(derive(*exp)) } # a = parse("(* x x)") # p a exit ########################################################################### # unit tests module UnitTests require 'Lapidary/TestCase' require 'Lapidary/UI/Console/TestRunner' class Test < Lapidary::TestCase def setup end def tearDown end def test_pair assert(!pair?([])) assert(!pair?([:x])) assert(pair?([:x, :y])) assert(!pair?([:x, :y, :z])) end def test_variable assert(!variable?(1)) assert(!variable?("urgh")) assert(variable?(:x)) end def test_same_variable assert(!same_variable?(:x, :y)) assert(same_variable?(:x, :x)) end def test_sum assert(!sum?([:x, :y])) assert(sum?([:+, [:x, :y]])) end def test_addend_and_augend assertEqual(:x, addend([:+, :x, :y])) assertEqual(:y, augend([:+, :x, :y])) end def test_product assert(!product?([:x, :y])) assert(product?([:*, [:x, :y]])) end def test_multiplier_and_multiplicand assertEqual(:x, multiplier([:+, :x, :y])) assertEqual(:y, multiplicand([:+, :x, :y])) end end class All def All.suite suite = Lapidary::TestSuite.new suite.add(Test.suite) suite end end if __FILE__ == $0 Lapidary::UI::Console::TestRunner.run(All) end end # assert(true) # assertEqual(1, 1) # assertInstanceOf(String, "foo") # assertKindOf(All, Module) # assertMatch("abc", /b/) # assertNil(nil) # assertRaises(Exception) { raise Exception.new }