updated lis.py examples
This commit is contained in:
21
02-array-seq/lispy/LICENSE
Normal file
21
02-array-seq/lispy/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2017 Peter Norvig
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
36
02-array-seq/lispy/README.md
Normal file
36
02-array-seq/lispy/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Norvig's originals and updates
|
||||||
|
|
||||||
|
This directory contains:
|
||||||
|
|
||||||
|
* `original/`:
|
||||||
|
Norvig's [`lis.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lis.py),
|
||||||
|
[`lispy.py`](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lispy.py), and the `lispytest.py` custom test script for testing both;
|
||||||
|
* `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10.
|
||||||
|
|
||||||
|
The `py3.10/` directory also has `lis_test.py` to run with
|
||||||
|
[pytest](https://docs.pytest.org), including all the
|
||||||
|
[`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5)
|
||||||
|
from `original/lispytest.py`,
|
||||||
|
and additional separate tests for each expression and special form handled by `evaluate`.
|
||||||
|
|
||||||
|
|
||||||
|
## Provenance, Copyright and License
|
||||||
|
|
||||||
|
`lis.py` is
|
||||||
|
[published](https://github.com/norvig/pytudes/blob/c33cd6835a506a57d9fe73e3a8317d49babb13e8/py/lis.py)
|
||||||
|
in the [norvig/pytudes](https://github.com/norvig/pytudes) repository on Github.
|
||||||
|
The copyright holder is Peter Norvig and the code is licensed under the
|
||||||
|
[MIT license](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/LICENSE).
|
||||||
|
|
||||||
|
|
||||||
|
## Changes to Norvig's code
|
||||||
|
|
||||||
|
I made small changes to the programs in `original/`:
|
||||||
|
|
||||||
|
* In `lis.py`:
|
||||||
|
* The `Procedure` class accepts a list of expressions as the `body`, and `__call__` evaluates all those expressions in order, returning the value of the last. This is consistent with Scheme's `lambda` syntax and provided a useful example for pattern matching.
|
||||||
|
* In the `elif` block for `'lambda'`, I added the `*` in front of the `*body` variable in the tuple unpacking to capture the expressions as a list, before calling the `Procedure` constructor.
|
||||||
|
|
||||||
|
* In `lispy.py` I made [changes and a pull request](https://github.com/norvig/pytudes/pull/106) to make it run on Python 3.
|
||||||
|
|
||||||
|
_Luciano Ramalho<br/>June 29, 2021_
|
||||||
135
02-array-seq/lispy/original/lis.py
Normal file
135
02-array-seq/lispy/original/lis.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
################ Lispy: Scheme Interpreter in Python 3.3+
|
||||||
|
|
||||||
|
## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html
|
||||||
|
|
||||||
|
################ Imports and Types
|
||||||
|
|
||||||
|
import math
|
||||||
|
import operator as op
|
||||||
|
from collections import ChainMap as Environment
|
||||||
|
|
||||||
|
Symbol = str # A Lisp Symbol is implemented as a Python str
|
||||||
|
List = list # A Lisp List is implemented as a Python list
|
||||||
|
Number = (int, float) # A Lisp Number is implemented as a Python int or float
|
||||||
|
|
||||||
|
class Procedure(object):
|
||||||
|
"A user-defined Scheme procedure."
|
||||||
|
def __init__(self, parms, body, env):
|
||||||
|
self.parms, self.body, self.env = parms, body, env
|
||||||
|
def __call__(self, *args):
|
||||||
|
env = Environment(dict(zip(self.parms, args)), self.env)
|
||||||
|
for exp in self.body:
|
||||||
|
result = eval(exp, env)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
################ Global Environment
|
||||||
|
|
||||||
|
def standard_env():
|
||||||
|
"An environment with some Scheme standard procedures."
|
||||||
|
env = {}
|
||||||
|
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, Number),
|
||||||
|
'procedure?': callable,
|
||||||
|
'round': round,
|
||||||
|
'symbol?': lambda x: isinstance(x, Symbol),
|
||||||
|
})
|
||||||
|
return env
|
||||||
|
|
||||||
|
global_env = standard_env()
|
||||||
|
|
||||||
|
################ Parsing: parse, tokenize, and read_from_tokens
|
||||||
|
|
||||||
|
def parse(program):
|
||||||
|
"Read a Scheme expression from a string."
|
||||||
|
return read_from_tokens(tokenize(program))
|
||||||
|
|
||||||
|
def tokenize(s):
|
||||||
|
"Convert a string into a list of tokens."
|
||||||
|
return s.replace('(',' ( ').replace(')',' ) ').split()
|
||||||
|
|
||||||
|
def read_from_tokens(tokens):
|
||||||
|
"Read an expression from a sequence of tokens."
|
||||||
|
if len(tokens) == 0:
|
||||||
|
raise SyntaxError('unexpected EOF while reading')
|
||||||
|
token = tokens.pop(0)
|
||||||
|
if '(' == token:
|
||||||
|
L = []
|
||||||
|
while tokens[0] != ')':
|
||||||
|
L.append(read_from_tokens(tokens))
|
||||||
|
tokens.pop(0) # pop off ')'
|
||||||
|
return L
|
||||||
|
elif ')' == token:
|
||||||
|
raise SyntaxError('unexpected )')
|
||||||
|
else:
|
||||||
|
return atom(token)
|
||||||
|
|
||||||
|
def atom(token):
|
||||||
|
"Numbers become numbers; every other token is a symbol."
|
||||||
|
try: return int(token)
|
||||||
|
except ValueError:
|
||||||
|
try: return float(token)
|
||||||
|
except ValueError:
|
||||||
|
return Symbol(token)
|
||||||
|
|
||||||
|
################ Interaction: A REPL
|
||||||
|
|
||||||
|
def repl(prompt='lis.py> '):
|
||||||
|
"A prompt-read-eval-print loop."
|
||||||
|
while True:
|
||||||
|
val = eval(parse(input(prompt)))
|
||||||
|
if val is not None:
|
||||||
|
print(lispstr(val))
|
||||||
|
|
||||||
|
def lispstr(exp):
|
||||||
|
"Convert a Python object back into a Lisp-readable string."
|
||||||
|
if isinstance(exp, List):
|
||||||
|
return '(' + ' '.join(map(lispstr, exp)) + ')'
|
||||||
|
else:
|
||||||
|
return str(exp)
|
||||||
|
|
||||||
|
################ eval
|
||||||
|
|
||||||
|
def eval(x, env=global_env):
|
||||||
|
"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
|
||||||
|
return exp
|
||||||
|
elif x[0] == 'if': # (if test conseq alt)
|
||||||
|
(_, test, conseq, alt) = x
|
||||||
|
exp = (conseq if eval(test, env) else alt)
|
||||||
|
return eval(exp, env)
|
||||||
|
elif x[0] == 'define': # (define var exp)
|
||||||
|
(_, var, exp) = x
|
||||||
|
env[var] = eval(exp, env)
|
||||||
|
elif x[0] == 'lambda': # (lambda (var...) body)
|
||||||
|
(_, parms, *body) = x
|
||||||
|
return Procedure(parms, body, env)
|
||||||
|
else: # (proc arg...)
|
||||||
|
proc = eval(x[0], env)
|
||||||
|
args = [eval(exp, env) for exp in x[1:]]
|
||||||
|
return proc(*args)
|
||||||
316
02-array-seq/lispy/original/lispy.py
Normal file
316
02-array-seq/lispy/original/lispy.py
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
################ Scheme Interpreter in Python
|
||||||
|
|
||||||
|
## (c) Peter Norvig, 2010; See http://norvig.com/lispy2.html
|
||||||
|
|
||||||
|
################ Symbol, Procedure, classes
|
||||||
|
|
||||||
|
import re, sys, io
|
||||||
|
|
||||||
|
class Symbol(str): pass
|
||||||
|
|
||||||
|
def Sym(s, symbol_table={}):
|
||||||
|
"Find or create unique Symbol entry for str s in symbol table."
|
||||||
|
if s not in symbol_table: symbol_table[s] = Symbol(s)
|
||||||
|
return symbol_table[s]
|
||||||
|
|
||||||
|
_quote, _if, _set, _define, _lambda, _begin, _definemacro, = map(Sym,
|
||||||
|
"quote if set! define lambda begin define-macro".split())
|
||||||
|
|
||||||
|
_quasiquote, _unquote, _unquotesplicing = map(Sym,
|
||||||
|
"quasiquote unquote unquote-splicing".split())
|
||||||
|
|
||||||
|
class Procedure:
|
||||||
|
"A user-defined Scheme procedure."
|
||||||
|
def __init__(self, parms, exp, env):
|
||||||
|
self.parms, self.exp, self.env = parms, exp, env
|
||||||
|
def __call__(self, *args):
|
||||||
|
return eval(self.exp, Env(self.parms, args, self.env))
|
||||||
|
|
||||||
|
################ parse, read, and user interaction
|
||||||
|
|
||||||
|
def parse(inport):
|
||||||
|
"Parse a program: read and expand/error-check it."
|
||||||
|
# Backwards compatibility: given a str, convert it to an InPort
|
||||||
|
if isinstance(inport, str): inport = InPort(io.StringIO(inport))
|
||||||
|
return expand(read(inport), toplevel=True)
|
||||||
|
|
||||||
|
eof_object = Symbol('#<eof-object>') # Note: uninterned; can't be read
|
||||||
|
|
||||||
|
class InPort:
|
||||||
|
"An input port. Retains a line of chars."
|
||||||
|
tokenizer = r"""\s*(,@|[('`,)]|"(?:[\\].|[^\\"])*"|;.*|[^\s('"`,;)]*)(.*)"""
|
||||||
|
def __init__(self, file):
|
||||||
|
self.file = file; self.line = ''
|
||||||
|
def next_token(self):
|
||||||
|
"Return the next token, reading new text into line buffer if needed."
|
||||||
|
while True:
|
||||||
|
if self.line == '': self.line = self.file.readline()
|
||||||
|
if self.line == '': return eof_object
|
||||||
|
token, self.line = re.match(InPort.tokenizer, self.line).groups()
|
||||||
|
if token != '' and not token.startswith(';'):
|
||||||
|
return token
|
||||||
|
|
||||||
|
def readchar(inport):
|
||||||
|
"Read the next character from an input port."
|
||||||
|
if inport.line != '':
|
||||||
|
ch, inport.line = inport.line[0], inport.line[1:]
|
||||||
|
return ch
|
||||||
|
else:
|
||||||
|
return inport.file.read(1) or eof_object
|
||||||
|
|
||||||
|
def read(inport):
|
||||||
|
"Read a Scheme expression from an input port."
|
||||||
|
def read_ahead(token):
|
||||||
|
if '(' == token:
|
||||||
|
L = []
|
||||||
|
while True:
|
||||||
|
token = inport.next_token()
|
||||||
|
if token == ')': return L
|
||||||
|
else: L.append(read_ahead(token))
|
||||||
|
elif ')' == token: raise SyntaxError('unexpected )')
|
||||||
|
elif token in quotes: return [quotes[token], read(inport)]
|
||||||
|
elif token is eof_object: raise SyntaxError('unexpected EOF in list')
|
||||||
|
else: return atom(token)
|
||||||
|
# body of read:
|
||||||
|
token1 = inport.next_token()
|
||||||
|
return eof_object if token1 is eof_object else read_ahead(token1)
|
||||||
|
|
||||||
|
quotes = {"'":_quote, "`":_quasiquote, ",":_unquote, ",@":_unquotesplicing}
|
||||||
|
|
||||||
|
def atom(token):
|
||||||
|
'Numbers become numbers; #t and #f are booleans; "..." string; otherwise Symbol.'
|
||||||
|
if token == '#t': return True
|
||||||
|
elif token == '#f': return False
|
||||||
|
elif token[0] == '"': return token[1:-1]
|
||||||
|
try: return int(token)
|
||||||
|
except ValueError:
|
||||||
|
try: return float(token)
|
||||||
|
except ValueError:
|
||||||
|
try: return complex(token.replace('i', 'j', 1))
|
||||||
|
except ValueError:
|
||||||
|
return Sym(token)
|
||||||
|
|
||||||
|
def to_string(x):
|
||||||
|
"Convert a Python object back into a Lisp-readable string."
|
||||||
|
if x is True: return "#t"
|
||||||
|
elif x is False: return "#f"
|
||||||
|
elif isa(x, Symbol): return x
|
||||||
|
elif isa(x, str): return repr(x)
|
||||||
|
elif isa(x, list): return '('+' '.join(map(to_string, x))+')'
|
||||||
|
elif isa(x, complex): return str(x).replace('j', 'i')
|
||||||
|
else: return str(x)
|
||||||
|
|
||||||
|
def load(filename):
|
||||||
|
"Eval every expression from a file."
|
||||||
|
repl(None, InPort(open(filename)), None)
|
||||||
|
|
||||||
|
def repl(prompt='lispy> ', inport=InPort(sys.stdin), out=sys.stdout):
|
||||||
|
"A prompt-read-eval-print loop."
|
||||||
|
sys.stderr.write("Lispy version 2.0\n")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if prompt: sys.stderr.write(prompt)
|
||||||
|
x = parse(inport)
|
||||||
|
if x is eof_object: return
|
||||||
|
val = eval(x)
|
||||||
|
if val is not None and out: print(to_string(val), file=out)
|
||||||
|
except Exception as e:
|
||||||
|
print('%s: %s' % (type(e).__name__, e))
|
||||||
|
|
||||||
|
################ Environment class
|
||||||
|
|
||||||
|
class Env(dict):
|
||||||
|
"An environment: a dict of {'var':val} pairs, with an outer Env."
|
||||||
|
def __init__(self, parms=(), args=(), outer=None):
|
||||||
|
# Bind parm list to corresponding args, or single parm to list of args
|
||||||
|
self.outer = outer
|
||||||
|
if isa(parms, Symbol):
|
||||||
|
self.update({parms:list(args)})
|
||||||
|
else:
|
||||||
|
if len(args) != len(parms):
|
||||||
|
raise TypeError('expected %s, given %s, '
|
||||||
|
% (to_string(parms), to_string(args)))
|
||||||
|
self.update(zip(parms,args))
|
||||||
|
def find(self, var):
|
||||||
|
"Find the innermost Env where var appears."
|
||||||
|
if var in self: return self
|
||||||
|
elif self.outer is None: raise LookupError(var)
|
||||||
|
else: return self.outer.find(var)
|
||||||
|
|
||||||
|
def is_pair(x): return x != [] and isa(x, list)
|
||||||
|
def cons(x, y): return [x]+y
|
||||||
|
|
||||||
|
def callcc(proc):
|
||||||
|
"Call proc with current continuation; escape only"
|
||||||
|
ball = RuntimeWarning("Sorry, can't continue this continuation any longer.")
|
||||||
|
def throw(retval): ball.retval = retval; raise ball
|
||||||
|
try:
|
||||||
|
return proc(throw)
|
||||||
|
except RuntimeWarning as w:
|
||||||
|
if w is ball: return ball.retval
|
||||||
|
else: raise w
|
||||||
|
|
||||||
|
def add_globals(self):
|
||||||
|
"Add some Scheme standard procedures."
|
||||||
|
import math, cmath, operator as op
|
||||||
|
self.update(vars(math))
|
||||||
|
self.update(vars(cmath))
|
||||||
|
self.update({
|
||||||
|
'+':op.add, '-':op.sub, '*':op.mul, '/':op.truediv, 'not':op.not_,
|
||||||
|
'>':op.gt, '<':op.lt, '>=':op.ge, '<=':op.le, '=':op.eq,
|
||||||
|
'equal?':op.eq, 'eq?':op.is_, 'length':len, 'cons':cons,
|
||||||
|
'car':lambda x:x[0], 'cdr':lambda x:x[1:], 'append':op.add,
|
||||||
|
'list':lambda *x:list(x), 'list?': lambda x:isa(x,list),
|
||||||
|
'null?':lambda x:x==[], 'symbol?':lambda x: isa(x, Symbol),
|
||||||
|
'boolean?':lambda x: isa(x, bool), 'pair?':is_pair,
|
||||||
|
'port?': lambda x:isa(x,file), 'apply':lambda proc,l: proc(*l),
|
||||||
|
'eval':lambda x: eval(expand(x)), 'load':lambda fn: load(fn), 'call/cc':callcc,
|
||||||
|
'open-input-file':open,'close-input-port':lambda p: p.file.close(),
|
||||||
|
'open-output-file':lambda f:open(f,'w'), 'close-output-port':lambda p: p.close(),
|
||||||
|
'eof-object?':lambda x:x is eof_object, 'read-char':readchar,
|
||||||
|
'read':read, 'write':lambda x,port=sys.stdout:port.write(to_string(x)),
|
||||||
|
'display':lambda x,port=sys.stdout:port.write(x if isa(x,str) else to_string(x))})
|
||||||
|
return self
|
||||||
|
|
||||||
|
isa = isinstance
|
||||||
|
|
||||||
|
global_env = add_globals(Env())
|
||||||
|
|
||||||
|
################ eval (tail recursive)
|
||||||
|
|
||||||
|
def eval(x, env=global_env):
|
||||||
|
"Evaluate an expression in an environment."
|
||||||
|
while True:
|
||||||
|
if isa(x, Symbol): # variable reference
|
||||||
|
return env.find(x)[x]
|
||||||
|
elif not isa(x, list): # constant literal
|
||||||
|
return x
|
||||||
|
elif x[0] is _quote: # (quote exp)
|
||||||
|
(_, exp) = x
|
||||||
|
return exp
|
||||||
|
elif x[0] is _if: # (if test conseq alt)
|
||||||
|
(_, test, conseq, alt) = x
|
||||||
|
x = (conseq if eval(test, env) else alt)
|
||||||
|
elif x[0] is _set: # (set! var exp)
|
||||||
|
(_, var, exp) = x
|
||||||
|
env.find(var)[var] = eval(exp, env)
|
||||||
|
return None
|
||||||
|
elif x[0] is _define: # (define var exp)
|
||||||
|
(_, var, exp) = x
|
||||||
|
env[var] = eval(exp, env)
|
||||||
|
return None
|
||||||
|
elif x[0] is _lambda: # (lambda (var*) exp)
|
||||||
|
(_, vars, exp) = x
|
||||||
|
return Procedure(vars, exp, env)
|
||||||
|
elif x[0] is _begin: # (begin exp+)
|
||||||
|
for exp in x[1:-1]:
|
||||||
|
eval(exp, env)
|
||||||
|
x = x[-1]
|
||||||
|
else: # (proc exp*)
|
||||||
|
exps = [eval(exp, env) for exp in x]
|
||||||
|
proc = exps.pop(0)
|
||||||
|
if isa(proc, Procedure):
|
||||||
|
x = proc.exp
|
||||||
|
env = Env(proc.parms, exps, proc.env)
|
||||||
|
else:
|
||||||
|
return proc(*exps)
|
||||||
|
|
||||||
|
################ expand
|
||||||
|
|
||||||
|
def expand(x, toplevel=False):
|
||||||
|
"Walk tree of x, making optimizations/fixes, and signaling SyntaxError."
|
||||||
|
require(x, x!=[]) # () => Error
|
||||||
|
if not isa(x, list): # constant => unchanged
|
||||||
|
return x
|
||||||
|
elif x[0] is _quote: # (quote exp)
|
||||||
|
require(x, len(x)==2)
|
||||||
|
return x
|
||||||
|
elif x[0] is _if:
|
||||||
|
if len(x)==3: x = x + [None] # (if t c) => (if t c None)
|
||||||
|
require(x, len(x)==4)
|
||||||
|
return list(map(expand, x))
|
||||||
|
elif x[0] is _set:
|
||||||
|
require(x, len(x)==3);
|
||||||
|
var = x[1] # (set! non-var exp) => Error
|
||||||
|
require(x, isa(var, Symbol), "can set! only a symbol")
|
||||||
|
return [_set, var, expand(x[2])]
|
||||||
|
elif x[0] is _define or x[0] is _definemacro:
|
||||||
|
require(x, len(x)>=3)
|
||||||
|
_def, v, body = x[0], x[1], x[2:]
|
||||||
|
if isa(v, list) and v: # (define (f args) body)
|
||||||
|
f, args = v[0], v[1:] # => (define f (lambda (args) body))
|
||||||
|
return expand([_def, f, [_lambda, args]+body])
|
||||||
|
else:
|
||||||
|
require(x, len(x)==3) # (define non-var/list exp) => Error
|
||||||
|
require(x, isa(v, Symbol), "can define only a symbol")
|
||||||
|
exp = expand(x[2])
|
||||||
|
if _def is _definemacro:
|
||||||
|
require(x, toplevel, "define-macro only allowed at top level")
|
||||||
|
proc = eval(exp)
|
||||||
|
require(x, callable(proc), "macro must be a procedure")
|
||||||
|
macro_table[v] = proc # (define-macro v proc)
|
||||||
|
return None # => None; add v:proc to macro_table
|
||||||
|
return [_define, v, exp]
|
||||||
|
elif x[0] is _begin:
|
||||||
|
if len(x)==1: return None # (begin) => None
|
||||||
|
else: return [expand(xi, toplevel) for xi in x]
|
||||||
|
elif x[0] is _lambda: # (lambda (x) e1 e2)
|
||||||
|
require(x, len(x)>=3) # => (lambda (x) (begin e1 e2))
|
||||||
|
vars, body = x[1], x[2:]
|
||||||
|
require(x, (isa(vars, list) and all(isa(v, Symbol) for v in vars))
|
||||||
|
or isa(vars, Symbol), "illegal lambda argument list")
|
||||||
|
exp = body[0] if len(body) == 1 else [_begin] + body
|
||||||
|
return [_lambda, vars, expand(exp)]
|
||||||
|
elif x[0] is _quasiquote: # `x => expand_quasiquote(x)
|
||||||
|
require(x, len(x)==2)
|
||||||
|
return expand_quasiquote(x[1])
|
||||||
|
elif isa(x[0], Symbol) and x[0] in macro_table:
|
||||||
|
return expand(macro_table[x[0]](*x[1:]), toplevel) # (m arg...)
|
||||||
|
else: # => macroexpand if m isa macro
|
||||||
|
return list(map(expand, x)) # (f arg...) => expand each
|
||||||
|
|
||||||
|
def require(x, predicate, msg="wrong length"):
|
||||||
|
"Signal a syntax error if predicate is false."
|
||||||
|
if not predicate: raise SyntaxError(to_string(x)+': '+msg)
|
||||||
|
|
||||||
|
_append, _cons, _let = map(Sym, "append cons let".split())
|
||||||
|
|
||||||
|
def expand_quasiquote(x):
|
||||||
|
"""Expand `x => 'x; `,x => x; `(,@x y) => (append x y) """
|
||||||
|
if not is_pair(x):
|
||||||
|
return [_quote, x]
|
||||||
|
require(x, x[0] is not _unquotesplicing, "can't splice here")
|
||||||
|
if x[0] is _unquote:
|
||||||
|
require(x, len(x)==2)
|
||||||
|
return x[1]
|
||||||
|
elif is_pair(x[0]) and x[0][0] is _unquotesplicing:
|
||||||
|
require(x[0], len(x[0])==2)
|
||||||
|
return [_append, x[0][1], expand_quasiquote(x[1:])]
|
||||||
|
else:
|
||||||
|
return [_cons, expand_quasiquote(x[0]), expand_quasiquote(x[1:])]
|
||||||
|
|
||||||
|
def let(*args):
|
||||||
|
args = list(args)
|
||||||
|
x = cons(_let, args)
|
||||||
|
require(x, len(args)>1)
|
||||||
|
bindings, body = args[0], args[1:]
|
||||||
|
require(x, all(isa(b, list) and len(b)==2 and isa(b[0], Symbol)
|
||||||
|
for b in bindings), "illegal binding list")
|
||||||
|
vars, vals = zip(*bindings)
|
||||||
|
return [[_lambda, list(vars)]+list(map(expand, body))] + list(map(expand, vals))
|
||||||
|
|
||||||
|
macro_table = {_let:let} ## More macros can go here
|
||||||
|
|
||||||
|
eval(parse("""(begin
|
||||||
|
|
||||||
|
(define-macro and (lambda args
|
||||||
|
(if (null? args) #t
|
||||||
|
(if (= (length args) 1) (car args)
|
||||||
|
`(if ,(car args) (and ,@(cdr args)) #f)))))
|
||||||
|
|
||||||
|
;; More macros can also go here
|
||||||
|
|
||||||
|
)"""))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
repl()
|
||||||
122
02-array-seq/lispy/original/lispytest.py
Normal file
122
02-array-seq/lispy/original/lispytest.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
################ Tests for lis.py and lispy.py
|
||||||
|
|
||||||
|
lis_tests = [
|
||||||
|
("(quote (testing 1 (2.0) -3.14e159))", ['testing', 1, [2.0], -3.14e159]),
|
||||||
|
("(+ 2 2)", 4),
|
||||||
|
("(+ (* 2 100) (* 1 10))", 210),
|
||||||
|
("(if (> 6 5) (+ 1 1) (+ 2 2))", 2),
|
||||||
|
("(if (< 6 5) (+ 1 1) (+ 2 2))", 4),
|
||||||
|
("(define x 3)", None), ("x", 3), ("(+ x x)", 6),
|
||||||
|
("((lambda (x) (+ x x)) 5)", 10),
|
||||||
|
("(define twice (lambda (x) (* 2 x)))", None), ("(twice 5)", 10),
|
||||||
|
("(define compose (lambda (f g) (lambda (x) (f (g x)))))", None),
|
||||||
|
("((compose list twice) 5)", [10]),
|
||||||
|
("(define repeat (lambda (f) (compose f f)))", None),
|
||||||
|
("((repeat twice) 5)", 20), ("((repeat (repeat twice)) 5)", 80),
|
||||||
|
("(define fact (lambda (n) (if (<= n 1) 1 (* n (fact (- n 1))))))", None),
|
||||||
|
("(fact 3)", 6),
|
||||||
|
("(fact 50)", 30414093201713378043612608166064768844377641568960512000000000000),
|
||||||
|
("(define abs (lambda (n) ((if (> n 0) + -) 0 n)))", None),
|
||||||
|
("(list (abs -3) (abs 0) (abs 3))", [3, 0, 3]),
|
||||||
|
("""(define combine (lambda (f)
|
||||||
|
(lambda (x y)
|
||||||
|
(if (null? x) (quote ())
|
||||||
|
(f (list (car x) (car y))
|
||||||
|
((combine f) (cdr x) (cdr y)))))))""", None),
|
||||||
|
("(define zip (combine cons))", None),
|
||||||
|
("(zip (list 1 2 3 4) (list 5 6 7 8))", [[1, 5], [2, 6], [3, 7], [4, 8]]),
|
||||||
|
("""(define riff-shuffle (lambda (deck) (begin
|
||||||
|
(define take (lambda (n seq) (if (<= n 0) (quote ()) (cons (car seq) (take (- n 1) (cdr seq))))))
|
||||||
|
(define drop (lambda (n seq) (if (<= n 0) seq (drop (- n 1) (cdr seq)))))
|
||||||
|
(define mid (lambda (seq) (/ (length seq) 2)))
|
||||||
|
((combine append) (take (mid deck) deck) (drop (mid deck) deck)))))""", None),
|
||||||
|
("(riff-shuffle (list 1 2 3 4 5 6 7 8))", [1, 5, 2, 6, 3, 7, 4, 8]),
|
||||||
|
("((repeat riff-shuffle) (list 1 2 3 4 5 6 7 8))", [1, 3, 5, 7, 2, 4, 6, 8]),
|
||||||
|
("(riff-shuffle (riff-shuffle (riff-shuffle (list 1 2 3 4 5 6 7 8))))", [1,2,3,4,5,6,7,8]),
|
||||||
|
]
|
||||||
|
|
||||||
|
lispy_tests = [
|
||||||
|
("()", SyntaxError), ("(set! x)", SyntaxError),
|
||||||
|
("(define 3 4)", SyntaxError),
|
||||||
|
("(quote 1 2)", SyntaxError), ("(if 1 2 3 4)", SyntaxError),
|
||||||
|
("(lambda 3 3)", SyntaxError), ("(lambda (x))", SyntaxError),
|
||||||
|
("""(if (= 1 2) (define-macro a 'a)
|
||||||
|
(define-macro a 'b))""", SyntaxError),
|
||||||
|
("(define (twice x) (* 2 x))", None), ("(twice 2)", 4),
|
||||||
|
("(twice 2 2)", TypeError),
|
||||||
|
("(define lyst (lambda items items))", None),
|
||||||
|
("(lyst 1 2 3 (+ 2 2))", [1,2,3,4]),
|
||||||
|
("(if 1 2)", 2),
|
||||||
|
("(if (= 3 4) 2)", None),
|
||||||
|
("(begin (define x 1) (set! x (+ x 1)) (+ x 1))", 3),
|
||||||
|
("(define ((account bal) amt) (set! bal (+ bal amt)) bal)", None),
|
||||||
|
("(define a1 (account 100))", None),
|
||||||
|
("(a1 0)", 100), ("(a1 10)", 110), ("(a1 10)", 120),
|
||||||
|
("""(define (newton guess function derivative epsilon)
|
||||||
|
(define guess2 (- guess (/ (function guess) (derivative guess))))
|
||||||
|
(if (< (abs (- guess guess2)) epsilon) guess2
|
||||||
|
(newton guess2 function derivative epsilon)))""", None),
|
||||||
|
("""(define (square-root a)
|
||||||
|
(newton 1 (lambda (x) (- (* x x) a)) (lambda (x) (* 2 x)) 1e-8))""", None),
|
||||||
|
("(> (square-root 200.) 14.14213)", True),
|
||||||
|
("(< (square-root 200.) 14.14215)", True),
|
||||||
|
("(= (square-root 200.) (sqrt 200.))", True),
|
||||||
|
("""(define (sum-squares-range start end)
|
||||||
|
(define (sumsq-acc start end acc)
|
||||||
|
(if (> start end) acc (sumsq-acc (+ start 1) end (+ (* start start) acc))))
|
||||||
|
(sumsq-acc start end 0))""", None),
|
||||||
|
("(sum-squares-range 1 3000)", 9004500500), ## Tests tail recursion
|
||||||
|
("(call/cc (lambda (throw) (+ 5 (* 10 (throw 1))))) ;; throw", 1),
|
||||||
|
("(call/cc (lambda (throw) (+ 5 (* 10 1)))) ;; do not throw", 15),
|
||||||
|
("""(call/cc (lambda (throw)
|
||||||
|
(+ 5 (* 10 (call/cc (lambda (escape) (* 100 (escape 3)))))))) ; 1 level""", 35),
|
||||||
|
("""(call/cc (lambda (throw)
|
||||||
|
(+ 5 (* 10 (call/cc (lambda (escape) (* 100 (throw 3)))))))) ; 2 levels""", 3),
|
||||||
|
("""(call/cc (lambda (throw)
|
||||||
|
(+ 5 (* 10 (call/cc (lambda (escape) (* 100 1))))))) ; 0 levels""", 1005),
|
||||||
|
("(* 1i 1i)", -1), ("(sqrt -1)", 1j),
|
||||||
|
("(let ((a 1) (b 2)) (+ a b))", 3),
|
||||||
|
("(let ((a 1) (b 2 3)) (+ a b))", SyntaxError),
|
||||||
|
("(and 1 2 3)", 3), ("(and (> 2 1) 2 3)", 3), ("(and)", True),
|
||||||
|
("(and (> 2 1) (> 2 3))", False),
|
||||||
|
("(define-macro unless (lambda args `(if (not ,(car args)) (begin ,@(cdr args))))) ; test `", None),
|
||||||
|
("(unless (= 2 (+ 1 1)) (display 2) 3 4)", None),
|
||||||
|
(r'(unless (= 4 (+ 1 1)) (display 2) (display "\n") 3 4)', 4),
|
||||||
|
("(quote x)", 'x'),
|
||||||
|
("(quote (1 2 three))", [1, 2, 'three']),
|
||||||
|
("'x", 'x'),
|
||||||
|
("'(one 2 3)", ['one', 2, 3]),
|
||||||
|
("(define L (list 1 2 3))", None),
|
||||||
|
("`(testing ,@L testing)", ['testing',1,2,3,'testing']),
|
||||||
|
("`(testing ,L testing)", ['testing',[1,2,3],'testing']),
|
||||||
|
("`,@L", SyntaxError),
|
||||||
|
("""'(1 ;test comments '
|
||||||
|
;skip this line
|
||||||
|
2 ; more ; comments ; ) )
|
||||||
|
3) ; final comment""", [1,2,3]),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test(tests, name=''):
|
||||||
|
"For each (exp, expected) test case, see if eval(parse(exp)) == expected."
|
||||||
|
fails = 0
|
||||||
|
for (x, expected) in tests:
|
||||||
|
try:
|
||||||
|
result = eval(parse(x))
|
||||||
|
print(x, '=>', lispstr(result))
|
||||||
|
ok = (result == expected)
|
||||||
|
except Exception as e:
|
||||||
|
print(x, '=raises=>', type(e).__name__, e)
|
||||||
|
ok = isinstance(expected, type) and issubclass(expected, Exception) and isinstance(e, expected)
|
||||||
|
if not ok:
|
||||||
|
fails += 1
|
||||||
|
print('FAIL!!! Expected', expected)
|
||||||
|
print('%s %s: %d out of %d tests fail.' % ('*'*45, name, fails, len(tests)))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from lis import *
|
||||||
|
test(lis_tests, 'lis.py')
|
||||||
|
from lispy import *
|
||||||
|
test(lis_tests+lispy_tests, 'lispy.py')
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Environment: TypeAlias = MutableMapping[Symbol, object]
|
|||||||
class Procedure:
|
class Procedure:
|
||||||
"A user-defined Scheme procedure."
|
"A user-defined Scheme procedure."
|
||||||
|
|
||||||
def __init__(self, parms: list[Symbol], body: Expression, env: Environment):
|
def __init__(self, parms: list[Symbol], body: list[Expression], env: Environment):
|
||||||
self.parms = parms
|
self.parms = parms
|
||||||
self.body = body
|
self.body = body
|
||||||
self.env = env
|
self.env = env
|
||||||
@@ -31,7 +31,9 @@ class Procedure:
|
|||||||
def __call__(self, *args: Expression) -> Any:
|
def __call__(self, *args: Expression) -> Any:
|
||||||
local_env = dict(zip(self.parms, args))
|
local_env = dict(zip(self.parms, args))
|
||||||
env: Environment = ChainMap(local_env, self.env)
|
env: Environment = ChainMap(local_env, self.env)
|
||||||
return evaluate(self.body, env)
|
for exp in self.body:
|
||||||
|
result = evaluate(exp, env)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
################ global environment
|
################ global environment
|
||||||
@@ -160,9 +162,9 @@ def evaluate(exp: Expression, env: Environment) -> Any:
|
|||||||
return evaluate(alternative, env)
|
return evaluate(alternative, env)
|
||||||
case ['define', Symbol(var), value_exp]:
|
case ['define', Symbol(var), value_exp]:
|
||||||
env[var] = evaluate(value_exp, env)
|
env[var] = evaluate(value_exp, env)
|
||||||
case ['define', [Symbol(name), *parms], body]:
|
case ['define', [Symbol(name), *parms], *body]:
|
||||||
env[name] = Procedure(parms, body, env)
|
env[name] = Procedure(parms, body, env)
|
||||||
case ['lambda', [*parms], body]:
|
case ['lambda', [*parms], *body]:
|
||||||
return Procedure(parms, body, env)
|
return Procedure(parms, body, env)
|
||||||
case [op, *args]:
|
case [op, *args]:
|
||||||
proc = evaluate(op, env)
|
proc = evaluate(op, env)
|
||||||
@@ -122,7 +122,7 @@ def test_lambda(std_env: Environment) -> None:
|
|||||||
source = '(lambda (a b) (if (>= a b) a b))'
|
source = '(lambda (a b) (if (>= a b) a b))'
|
||||||
func = evaluate(parse(source), std_env)
|
func = evaluate(parse(source), std_env)
|
||||||
assert func.parms == ['a', 'b']
|
assert func.parms == ['a', 'b']
|
||||||
assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b']
|
assert func.body == [['if', ['>=', 'a', 'b'], 'a', 'b']]
|
||||||
assert func.env is std_env
|
assert func.env is std_env
|
||||||
assert func(1, 2) == 2
|
assert func(1, 2) == 2
|
||||||
assert func(3, 2) == 3
|
assert func(3, 2) == 3
|
||||||
@@ -176,7 +176,7 @@ def test_define_function(std_env: Environment) -> None:
|
|||||||
assert got is None
|
assert got is None
|
||||||
max_fn = std_env['max']
|
max_fn = std_env['max']
|
||||||
assert max_fn.parms == ['a', 'b']
|
assert max_fn.parms == ['a', 'b']
|
||||||
assert max_fn.body == ['if', ['>=', 'a', 'b'], 'a', 'b']
|
assert max_fn.body == [['if', ['>=', 'a', 'b'], 'a', 'b']]
|
||||||
assert max_fn.env is std_env
|
assert max_fn.env is std_env
|
||||||
assert max_fn(1, 2) == 2
|
assert max_fn(1, 2) == 2
|
||||||
assert max_fn(3, 2) == 3
|
assert max_fn(3, 2) == 3
|
||||||
Reference in New Issue
Block a user