# chapter=4 
def is_variable(x): return is_string(x)
def is_same_variable(v1, v2):
    return is_variable(v1) and is_variable(v2) and v1 == v2
def deriv(exp, variable):
    return (0
            if is_number(exp)
            else (1 if is_same_variable(exp, variable) else 0)
            if is_variable(exp)
            else get("deriv", operator(exp))(operands(exp), variable))
def operator(exp): return head(exp)

def operands(exp): return tail(exp)
def make_sum(a1, a2):
    return llist("+", a1, a2)
            
def make_product(m1, m2):
    return llist("*", m1, m2)
            
def addend(operands):
    return head(operands)
            
def augend(operands):
    return head(tail(operands))
            
def multiplier(operands):
    return head(operands)
            
def multiplicand(operands):
    return head(tail(operands))
# operation_table, put and get
# from chapter 3 (section 3.3.3)
def assoc(key, records):
    return (None
            if is_none(records)
            else head(records)
            if key == head(head(records))
            else assoc(key, tail(records)))
def make_table():
    local_table = llist("*table*")
    def lookup(key_1, key_2):
        subtable = assoc(key_1, tail(local_table))
        if is_none(subtable):
            return None
        else:
            record = assoc(key_2, tail(subtable))
            return (None
                    if is_none(record)
                    else tail(record))
    def insert(key_1, key_2, value):
        subtable = assoc(key_1, tail(local_table))
        if is_none(subtable):
            set_tail(local_table,
                     pair(llist(key_1, pair(key_2, value)),
                          tail(local_table)))
        else:
            record = assoc(key_2, tail(subtable))
            if is_none(record):
                set_tail(subtable,
                         pair(pair(key_2, value), tail(subtable)))
            else:
                set_tail(record, value)
    def dispatch(m):
        return (lookup if m == "lookup"
                else insert if m == "insert"
                else error("unknown operation -- table", m))
    return dispatch
operation_table = make_table()
get = operation_table("lookup")
put = operation_table("insert")
def deriv_sum(operands, variable):
    return make_sum(deriv(addend(operands), variable),
                    deriv(augend(operands), variable))
def deriv_product(operands, variable):
    return make_sum(make_product(multiplier(operands),
                        deriv(multiplicand(operands),
                              variable)),
                    make_product(deriv(multiplier(
                                             operands),
                                       variable),
                        multiplicand(operands)))
def install_deriv():
    put("deriv", "+", deriv_sum)
    put("deriv", "*", deriv_product)
    return "done"
print(install_deriv())

print(head(tail(head(tail(deriv(llist("*", llist("*", "x", "y"), llist("+", "x", 3)), "x"))))))

# expected: [ '*', [ 'x', [ 'y', None ] ] ]
