diff --git a/02-array-seq/lispy/LICENSE b/02-array-seq/lispy/LICENSE
new file mode 100644
index 0000000..ca550a2
--- /dev/null
+++ b/02-array-seq/lispy/LICENSE
@@ -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.
\ No newline at end of file
diff --git a/02-array-seq/lispy/README.md b/02-array-seq/lispy/README.md
new file mode 100644
index 0000000..0c11fd8
--- /dev/null
+++ b/02-array-seq/lispy/README.md
@@ -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
June 29, 2021_
\ No newline at end of file
diff --git a/02-array-seq/lispy/original/lis.py b/02-array-seq/lispy/original/lis.py
new file mode 100644
index 0000000..6e43fa2
--- /dev/null
+++ b/02-array-seq/lispy/original/lis.py
@@ -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)
diff --git a/02-array-seq/lispy/original/lispy.py b/02-array-seq/lispy/original/lispy.py
new file mode 100644
index 0000000..b17341c
--- /dev/null
+++ b/02-array-seq/lispy/original/lispy.py
@@ -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('#') # 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()
diff --git a/02-array-seq/lispy/original/lispytest.py b/02-array-seq/lispy/original/lispytest.py
new file mode 100644
index 0000000..b324ff3
--- /dev/null
+++ b/02-array-seq/lispy/original/lispytest.py
@@ -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')
+
diff --git a/02-array-seq/lispy/examples_test.py b/02-array-seq/lispy/py3.10/examples_test.py
similarity index 100%
rename from 02-array-seq/lispy/examples_test.py
rename to 02-array-seq/lispy/py3.10/examples_test.py
diff --git a/02-array-seq/lispy/lis.py b/02-array-seq/lispy/py3.10/lis.py
similarity index 95%
rename from 02-array-seq/lispy/lis.py
rename to 02-array-seq/lispy/py3.10/lis.py
index e37ba23..7ae0a9d 100644
--- a/02-array-seq/lispy/lis.py
+++ b/02-array-seq/lispy/py3.10/lis.py
@@ -23,7 +23,7 @@ Environment: TypeAlias = MutableMapping[Symbol, object]
class 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.body = body
self.env = env
@@ -31,7 +31,9 @@ class Procedure:
def __call__(self, *args: Expression) -> Any:
local_env = dict(zip(self.parms, args))
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
@@ -160,9 +162,9 @@ def evaluate(exp: Expression, env: Environment) -> Any:
return evaluate(alternative, env)
case ['define', Symbol(var), value_exp]:
env[var] = evaluate(value_exp, env)
- case ['define', [Symbol(name), *parms], body]:
+ case ['define', [Symbol(name), *parms], *body]:
env[name] = Procedure(parms, body, env)
- case ['lambda', [*parms], body]:
+ case ['lambda', [*parms], *body]:
return Procedure(parms, body, env)
case [op, *args]:
proc = evaluate(op, env)
diff --git a/02-array-seq/lispy/lis_test.py b/02-array-seq/lispy/py3.10/lis_test.py
similarity index 97%
rename from 02-array-seq/lispy/lis_test.py
rename to 02-array-seq/lispy/py3.10/lis_test.py
index e220e74..3688888 100644
--- a/02-array-seq/lispy/lis_test.py
+++ b/02-array-seq/lispy/py3.10/lis_test.py
@@ -12,7 +12,7 @@ from lis import parse, evaluate, Expression, Environment, standard_env
('(sum 1 2 3)', ['sum', 1, 2, 3]),
('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]),
('99 100', 99), # parse stops at the first complete expression
- ('(a)(b)', ['a']),
+ ('(a)(b)', ['a']),
])
def test_parse(source: str, expected: Expression) -> None:
got = parse(source)
@@ -122,7 +122,7 @@ def test_lambda(std_env: Environment) -> None:
source = '(lambda (a b) (if (>= a b) a b))'
func = evaluate(parse(source), std_env)
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(1, 2) == 2
assert func(3, 2) == 3
@@ -176,7 +176,7 @@ def test_define_function(std_env: Environment) -> None:
assert got is None
max_fn = std_env['max']
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(1, 2) == 2
assert max_fn(3, 2) == 3
diff --git a/02-array-seq/lispy/meta_test.py b/02-array-seq/lispy/py3.10/meta_test.py
similarity index 100%
rename from 02-array-seq/lispy/meta_test.py
rename to 02-array-seq/lispy/py3.10/meta_test.py