Why don't people like lisp?

Andrew Dalke adalke at mindspring.com
Wed Oct 22 05:07:25 EDT 2003


Me:
> I then chose
> a problem which could be done that way and implemented it
> in the canonical tokenizer/parser/translater/compiler framework
> Kaz Kylheku (to whom I was responding) wanted, when it's
> inappropriate for  this case, hence complicating my original code.

Here's what the code would look like without that strict
partitioning.  It can be made a bit smaller (I don't really need the
'Stack' class; it just helps simplify dealing with line numbers,
and the is_float make the if/elif/else structure nice, compared
to using a try/except.)

The point though is that Python allows the same sorts of
 domain language -> AST -> "native" AST -> native code
steps that Kaz Kylheku wanted.

                    Andrew
                    dalke at dalkescientific.com

import re, sys
from compiler import ast, misc, pycodegen

class RPNError(Exception):
    pass

_symbol_re = re.compile(r"\S+")
#_variable_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$")
# Let's take anything!
_variable_re = re.compile(r".*$")

class Stack:
    def __init__(self):
        self.stack = []

    def add_term(self, term, lineno):
        term.lineno = lineno
        self.stack.append(term)

    def add_oper(self, klass, lineno, charpos):
        if len(self.stack) < 2:
            raise RPNError(
    "Binary operator at line %d char %d missing terms" %
    (lineno, charpos))
        term = klass(self.stack[-2:])
        term.lineno = lineno
        self.stack[-2:] = [term]

def is_float(s):
    try:
        float(s)
        return 1
    except ValueError:
        return 0

_id_gen = iter(xrange(sys.maxint))
_oper_table = {
    "+": ast.Add,
    "-": ast.Sub,
    "*": ast.Mul,
    "/": ast.Div,
    "**": ast.Power,
    }

def compile(s):
    param_names = []
    stack = Stack()

    for lineno, line in enumerate(s.split("\n")):
        for match in _symbol_re.finditer(line):
            word = match.group(0)
            charpos = match.start(0) + 1

            if is_float(word):
                stack.add_term(ast.Const(float(word)), lineno)

            elif word in _oper_table:
                stack.add_oper(_oper_table[word],
                                lineno, charpos)

            elif _variable_re.match(word):
                stack.add_term(ast.Name(word), lineno)
                if word not in param_names:
                    param_names.append(word)

            else:
                # Hmm, wonder what it is.
                raise RPNError(
                    "Unknown token %r at line %d, character %d" %
                    (word, lineno, charpos))

    stack = stack.stack
    if len(stack) != 1:
        raise RPNError("stack ends with size %d" %
                       len(stack))

    # go through an ugly bit of shenanigans
    # (I don't like the compiler API ):
    fctn_name = 'RPN' + str(_id_gen.next())
    fctn = ast.Function(fctn_name, param_names, [],
                        0, None,
                        ast.Stmt([ast.Return(stack[0])]))
    mod = ast.Module(None, ast.Stmt([fctn]))
    misc.set_filename("<RPN string " + fctn_name + ">", mod)
    code = pycodegen.ModuleCodeGenerator(mod).getCode()
    d = {"__builtins__": {}}
    exec code in d
    return d[fctn_name]

def main():
    assert compile("2 3 **")() == 8
    assert compile("a 2 3 + -")(a=6) == 1
    assert compile("a 2 3 + -")(7) == 2
    assert compile("a b *")(2, 3) == 6
    assert compile("a b -")(b=2, a=3) == 1
    assert compile("1 2 3 4 + + +")() == 10
    f = compile("(#) ') try + -")
    print f(**{"(#)": 5, "')": 7, "try": 3})
    print "All tests passed"


if __name__ == "__main__":
    main()






More information about the Python-list mailing list