sync from Atlas

This commit is contained in:
Luciano Ramalho
2021-09-20 10:37:26 -03:00
parent 6527037ae7
commit 2f2f87d4fb
16 changed files with 1730 additions and 115 deletions

View File

@@ -1,73 +1,24 @@
#!/usr/bin/env python
################ Lispy: Scheme Interpreter in Python 3.9
## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html
## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021)
## by Luciano Ramalho, adding type hints and pattern matching.
################ Imports and Types
import math
import operator as op
from collections import ChainMap
from collections.abc import MutableMapping, Iterator
from itertools import chain
from typing import Any, Union
from typing import Any, Union, NoReturn
Symbol = str
Atom = Union[float, int, Symbol]
Expression = Union[Atom, list]
Environment = MutableMapping[Symbol, object]
class Procedure:
"A user-defined Scheme procedure."
def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment):
self.parms = parms
self.body = body
self.env = env
def __call__(self, *args: Expression) -> Any:
local_env = dict(zip(self.parms, args))
env: Environment = ChainMap(local_env, self.env)
for exp in self.body:
result = evaluate(exp, env)
return result
################ Global Environment
def standard_env() -> Environment:
"An environment with some Scheme standard procedures."
env: Environment = {}
env.update(vars(math)) # sin, cos, sqrt, pi, ...
env.update({
'+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv,
'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
'abs': abs,
'append': op.add,
'apply': lambda proc, args: proc(*args),
'begin': lambda *x: x[-1],
'car': lambda x: x[0],
'cdr': lambda x: x[1:],
'cons': lambda x,y: [x] + y,
'eq?': op.is_,
'equal?': op.eq,
'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),
})
return env
################ Parsing: parse, tokenize, and read_from_tokens
@@ -75,12 +26,10 @@ 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 s.replace('(', ' ( ').replace(')', ' ) ').split()
def read_from_tokens(tokens: list[str]) -> Expression:
"Read an expression from a sequence of tokens."
if len(tokens) == 0:
@@ -97,7 +46,6 @@ def read_from_tokens(tokens: list[str]) -> Expression:
else:
return parse_atom(token)
def parse_atom(token: str) -> Atom:
"Numbers become numbers; every other token is a symbol."
try:
@@ -109,17 +57,73 @@ def parse_atom(token: str) -> Atom:
return Symbol(token)
################ Global Environment
class Environment(ChainMap[Symbol, Any]):
"A ChainMap that allows changing an item in-place."
def change(self, key: Symbol, value: object) -> None:
"Find where key is defined and change the value there."
for map in self.maps:
if key in map:
map[key] = value # type: ignore[index]
return
raise KeyError(key)
def standard_env() -> Environment:
"An environment with some Scheme standard procedures."
env = Environment()
env.update(vars(math)) # sin, cos, sqrt, pi, ...
env.update({
'+': op.add,
'-': op.sub,
'*': op.mul,
'/': op.truediv,
'//': op.floordiv,
'>': op.gt,
'<': op.lt,
'>=': op.ge,
'<=': op.le,
'=': op.eq,
'abs': abs,
'append': lambda *args: list(chain(*args)),
'apply': lambda proc, args: proc(*args),
'begin': lambda *x: x[-1],
'car': lambda x: x[0],
'cdr': lambda x: x[1:],
'cons': lambda x, y: [x] + y,
'display': lambda x: print(lispstr(x)),
'eq?': op.is_,
'equal?': op.eq,
'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),
})
return env
################ Interaction: A REPL
def repl(prompt: str = 'lis.py> ') -> None:
def repl(prompt: str = 'lis.py> ') -> NoReturn:
"A prompt-read-eval-print loop."
global_env = standard_env()
while True:
val = evaluate(parse(input(prompt)), global_env)
ast = parse(input(prompt))
val = evaluate(ast, global_env)
if val is not None:
print(lispstr(val))
def lispstr(exp: object) -> str:
"Convert a Python object back into a Lisp-readable string."
if isinstance(exp, list):
@@ -128,30 +132,81 @@ def lispstr(exp: object) -> str:
return str(exp)
################ eval
################ Evaluator
def evaluate(x: Expression, env: Environment) -> Any:
def evaluate(exp: Expression, env: Environment) -> Any:
"Evaluate an expression in an environment."
if isinstance(x, Symbol): # variable reference
return env[x]
elif not isinstance(x, list): # constant literal
return x
elif x[0] == 'quote': # (quote exp)
(_, exp) = x
if isinstance(exp, Symbol): # variable reference
return env[exp]
elif not isinstance(exp, list): # constant literal
return exp
elif x[0] == 'if': # (if test conseq alt)
(_, test, consequence, alternative) = x
elif exp[0] == 'quote': # (quote exp)
(_, x) = exp
return x
elif exp[0] == 'if': # (if test conseq alt)
(_, test, consequence, alternative) = exp
if evaluate(test, env):
return evaluate(consequence, env)
else:
return evaluate(alternative, env)
elif x[0] == 'define': # (define name exp)
(_, name, exp) = x
env[name] = evaluate(exp, env)
elif x[0] == 'lambda': # (lambda (parm…) body)
(_, parms, *body) = x
elif exp[0] == 'lambda': # (lambda (parm…) body…)
(_, parms, *body) = exp
if not isinstance(parms, list):
raise SyntaxError(lispstr(exp))
return Procedure(parms, body, env)
elif exp[0] == 'define':
(_, name_exp, *rest) = exp
if isinstance(name_exp, Symbol): # (define name exp)
value_exp = rest[0]
env[name_exp] = evaluate(value_exp, env)
else: # (define (name parm…) body…)
name, *parms = name_exp
env[name] = Procedure(parms, rest, env)
elif exp[0] == 'set!':
(_, var, value_exp) = exp
env.change(var, evaluate(value_exp, env))
else: # (proc arg…)
proc = evaluate(x[0], env)
args = [evaluate(exp, env) for exp in x[1:]]
(func_exp, *args) = exp
proc = evaluate(func_exp, env)
args = [evaluate(arg, env) for arg in args]
return proc(*args)
class Procedure:
"A user-defined Scheme procedure."
def __init__(
self, parms: list[Symbol], body: list[Expression], env: Environment
):
self.parms = parms
self.body = body
self.env = env
def __call__(self, *args: Expression) -> Any:
local_env = dict(zip(self.parms, args))
env = Environment(local_env, self.env)
for exp in self.body:
result = evaluate(exp, env)
return result
################ command-line interface
def run(source: str) -> Any:
global_env = standard_env()
tokens = tokenize(source)
while tokens:
exp = read_from_tokens(tokens)
result = evaluate(exp, global_env)
return result
def main(args: list[str]) -> None:
if len(args) == 1:
with open(args[0]) as fp:
run(fp.read())
else:
repl()
if __name__ == '__main__':
import sys
main(sys.argv[1:])