SoFunction
Updated on 2024-11-18

Writing an interpreter for the Lisp language using Python

General source code program through the compiler to generate a parse tree, Lisp's peculiarities is that you can completely uninstall the program, control the parse tree, arbitrary access operations, that is, you can use the program to generate the program.

Python claims to be the closest language to Lisp, but it's ultimately not. But since almost all languages are Turing-complete, even if Python can't do something in Lisp, you can do that thing by writing a Lisp interpreter in Python. It's marvelous isn't it?

Let's write a simple Lisp parser based on Scheme syntax:

pilot entry

################ : Scheme Interpreter in Python 3.10
## (c) Peter Norvig, 2010-18; See /
## Type hints and minor additions by Luciano Ramalho
 
import math
import operator as op
from collections import ChainMap
from itertools import chain
from typing import Any, NoReturn
from typing import Union, List, MutableMapping, Optional, Iterator
 
Symbol = str
Atom = Union[float, int, Symbol]
Expression = Union[Atom, List]
 
Environment = MutableMapping[Symbol, object]
 
print(Atom, Expression)
print(Environment)

Creating a Parse Parse

def parse(program: str) -> Expression:
    "Read a Scheme expression from a string."
    return read_from_tokens(tokenize(program))
 
def tokenize(s: str) -> List[str]:
    "Convert a string into a list of tokens."
    return ('(', ' ( ').replace(')', ' ) ').split()
 
def read_from_tokens(tokens: List[str]) -> Expression:
    "Read an expression from a sequence of tokens."
    if len(tokens) == 0:
        raise SyntaxError('unexpected EOF while reading')
    token = (0)
    if '(' == token:
        exp = []
        while tokens[0] != ')':
            (read_from_tokens(tokens))
        (0)  # discard ')'
        return exp
    elif ')' == token:
        raise SyntaxError('unexpected )')
    else:
        return parse_atom(token)
 
def parse_atom(token: str) -> Atom:
    "Numbers become numbers; every other token is a symbol."
    try:
        return int(token)
    except ValueError:
        try:
            return float(token)
        except ValueError:
            return Symbol(token)

Creating the Environment

def standard_env() -> Environment:
    "An environment with some Scheme standard procedures."
    env: Environment = {}
    (vars(math))   # sin, cos, sqrt, pi, ...
    (
        {
            '+': ,
            '-': ,
            '*': ,
            '/': , # Fractional division
            'quotient': , # Quotient Floor division Integer division
            '>': ,
            '<': ,
            '>=': ,
            '<=': ,
            '=': ,
            'abs': abs,
            'append': lambda *args: list(chain(*args)),          
            'apply': lambda proc, args: proc(*args),
            'begin': lambda *x: x[-1],
            'Up': lambda *x: x[-1],
            'car': lambda x: x[0],
            'cdr': lambda x: x[1:],
            'cons': lambda x, y: [x] + y,
            'eq?': op.is_,
            'equal?': ,
            'filter': lambda *args: list(filter(*args)),
            'length': len,
            'list': lambda *x: list(x),
            'list?': lambda x: isinstance(x, list),
            'map': lambda *args: list(map(*args)),
            'max': max,
            'min': min,
            'not': op.not_,
            'null?': lambda x: x == [],
            'number?': lambda x: isinstance(x, (int, float)),
            'procedure?': callable,
            'round': round,
            'symbol?': lambda x: isinstance(x, Symbol),
            'display': lambda x: print(lispstr(x), end=''),
            'conspicuous': lambda x: print(lispstr(x), end=''),
            'newline': lambda: print(),
        }
    )
    return env

executable function

def evaluate(x: Expression, env: Environment) -> Any:
    "Evaluate an expression in an environment."
    if isinstance(x, str):                       # variable reference
        return env[x]
    elif not isinstance(x, list):                # constant literal
        return x
    elif x[0] == 'define':                       # (define var exp)
        _, var, exp = x
        env[var] = evaluate(exp, env)
    elif x[0] == 'lambda':                       # (lambda (var...) body)
        _, parms, body = x
        return Procedure(parms, body, env)
    elif x[0] == 'quote':                        # (quote exp)
        _, exp = x
        return exp
    elif x[0] == 'if':                           # (if test consequence alternative)
        _, test, consequence, alternative = x
        if evaluate(test, env):
            return evaluate(consequence, env)
        else:
            return evaluate(alternative, env)
    elif x[0] == 'Set':                       # (define var exp)
        _, var, exp = x
        env[var] = evaluate(exp, env)
    elif x[0] == 'Letter':                       # (lambda (var...) body)
        _, parms, body = x
        return Procedure(parms, body, env)
    elif x[0] == 'Lead':                        # (quote exp)
        _, exp = x
        return exp
    elif x[0] == 'If':                           # (if test consequence alternative)
        _, test, consequence, alternative = x
        if evaluate(test, env):
            return evaluate(consequence, env)
        else:
            return evaluate(alternative, env)
    else:                                        # (proc arg...)
        proc_exp, *args = x
        proc = evaluate(proc_exp, env)
        arg_values = [evaluate(exp, env) for exp in args]
        return proc(*arg_values)

executable function (math.)

def run_lines(source: str, env: Optional[Environment] = None) -> Iterator[Any]:
    global_env: Environment = ChainMap({}, standard_env())
    if env is not None:
        global_env.update(env)
    tokens = tokenize(source)
    while tokens:
        exp = read_from_tokens(tokens)
        yield evaluate(exp, global_env)
 
 
def run(source: str, env: Optional[Environment] = None) -> Any:
    # In fact, this function simply iterates over all the results of run_lines and does nothing with them.
    # Finally, return the last result of run_lines.
    for result in run_lines(source, env):
        pass
    return result

operational test

percent = """
(define a 126)
(define b (* 6 50))
(* (/ a b) 100)
"""
run(percent)

Output: 42

Of course we can also use Chinese keywords:

percent = """
(found a 126)
(found b (* 6 50))
(* (/ a b) 100)
"""
run(percent)

Doesn't this look a little more intimate?

The above code is excerpted from:/fluentpython/lispy

Attachment:

scheme learning materials:The Scheme Programming Language, 4th Edition

to this article on the use of Python to write a Lisp language interpreter is introduced to this article, more related to Python Lisp language interpreter content, please search for my previous articles or continue to browse the following related articles I hope you will support me in the future!