I'm building a very simple Lisp interpreter. Similar to the the one from here. Here is what I have so far for the parsing part, that is, everything from "text is passed to the the program" to "ast is built". How does the below look? What can be improved? The hardest part for me was the recursive read_from_tokens
function.
import re
Symbol = str
def pops(L, d=None):
"Pop from the left of the list, or return the default."
return L.pop(0) if L else d
def parse(program):
"Read a Scheme expression from a string and return tokens in AST."
program = preprocess(program)
assert program.count(')') == program.count('('), "Mismatched parens"
return read_from_tokens(tokenize(program))
def preprocess(s):
"Replace comments with a single space."
return re.sub(r';.+', ' ', s)
def tokenize(s):
"Convert a string into a list of tokens."
return s.replace('(', ' ( ').replace(')', ' ) ').split()
def atom(token):
"Return a number (only accepted literal) or a symbol."
try:
return int(token) if token.isdigit() else float(token)
except ValueError:
return Symbol(token)
def read_from_tokens(tokens):
"Read one or more expression from a sequence of tokens. Returns a list of lists."
L = []
while (token := pops(tokens, ')')) != ')':
L.append(atom(token) if token!='(' else read_from_tokens(tokens))
return L
if __name__ == '__main__':
print (parse("(define (+ 2 2) 10) (* pi (* r r))"))
print (parse("(define r 10)"))
print (parse("(* pi (* (+ 1 1) r))"))