#!/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 }
