The mal Environment

project:

mal-py

filename:

env.py

As with the reader, this is held separate.

mal-py env.py
from reader import S

class Env:
    def __init__(
        self, outer: Env | None = None, binds: list[str] = None, exprs: list[Any] = None,
    ):
        self.data = {}
        self.outer = outer

        binds = binds or []
        exprs = exprs or []
        for b, e in zip(binds, exprs):
            self.set(b.name, e)

    def set(self, key: str, value):
        self.data[key] = value

    def get(self, key: str):
        if (value := self.data.get(key)) is None:
            if self.outer is not None:
                return self.outer.get(key)
            raise KeyError(key)
        return value

The core

filename:

core.py

From step 4, mal calls for a core.ns object that defines all the built-in functions for the language.

This defines a defn decorator that makes adding functions to the namespace nice.

mal-py core.py
import pathlib
from reader import A, read_str

ns = {}

def defn(name):
    def wrap(fn):
        ns[name] = fn
        return fn
    return wrap

Atoms

mal-py core.py
@defn('atom')
def atom(v):
    return A(v)

@defn('atom?')
def isatom(a):
    return isinstance(a, A)

@defn('deref')
def deref(a):
    return a.v

@defn('reset!')
def reset(a, v):
    a.v = v
    return v

@defn('swap!')
def swap(a, f, *args):
    v = f(a.v, *args)
    a.v = v
    return v

Comparisons

mal-py core.py
@defn('=')
def eql(x, y):
    return x == y

@defn('<')
def lt(x, y):
    return x < y

@defn('>')
def gt(x, y):
    return x > y

@defn('<=')
def lte(x, y):
    return x <= y

@defn('>=')
def gte(x, y):
    return x >= y

Eval

mal-py core.py
@defn('read-string')
def read(s):
    return read_str(s)

@defn('slurp')
def slurp(s):
    return pathlib.Path(s).read_text()

Lists

It wouldn’t be a Lisp without some list operators.

mal-py core.py
@defn('cons')
def cons(v, lst):
    return [v, *lst]

@defn('concat')
def concat(*lsts):
    l = []
    for lst in lsts:
        l.extend(lst)
    return l

@defn('list')
def make_list(*args):
    return list(args)

@defn('list?')
def is_list(x):
    return isinstance(x, list)

@defn('empty?')
def is_empty(x):
    return len(x) == 0

@defn('count')
def count(x):
    if not x:
        return 0
    return len(x)

Math functions

Plus, times, divide etc.

mal-py core.py
@defn('+')
def plus(a, b):
    return a + b

@defn('-')
def minus(a, b):
    return a - b

@defn('*')
def multiply(a, b):
    return a * b

@defn('/')
def divide(a, b):
    return int(a/b)

Printing

We want to be able to print from within mal

mal-py core.py
from printer import print_form

@defn('prn')
def printit(f):
    print(print_form(f))

Strings

mal-py core.py
@defn('str')
def tostr(*args):
    return "".join(args)