Skip to content

Commit f0f1608

Browse files
committed
sync with O'Reilly Atlas
1 parent e986e3b commit f0f1608

File tree

26 files changed

+308
-1004
lines changed

26 files changed

+308
-1004
lines changed

02-array-seq/lispy/examples_test.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
Doctests for `parse`:
3+
4+
>>> from lis import parse
5+
6+
# tag::PARSE_DEMO[]
7+
>>> parse('1.5') # <1>
8+
1.5
9+
>>> parse('set!') # <2>
10+
'set!'
11+
>>> parse('(gcd 18 44)') # <3>
12+
['gcd', 18, 44]
13+
>>> parse('(- m (* n (// m n)))') # <4>
14+
['-', 'm', ['*', 'n', ['//', 'm', 'n']]]
15+
16+
# end::PARSE_DEMO[]
17+
18+
"""
19+
20+
import math
21+
22+
from lis import run
23+
24+
25+
fact_src = """
26+
(define (! n)
27+
(if (< n 2)
28+
1
29+
(* n (! (- n 1)))
30+
)
31+
)
32+
(! 42)
33+
"""
34+
def test_factorial():
35+
got = run(fact_src)
36+
assert got == 1405006117752879898543142606244511569936384000000000
37+
assert got == math.factorial(42)
38+
39+
40+
gcd_src = """
41+
(define (mod m n)
42+
(- m (* n (// m n))))
43+
(define (gcd m n)
44+
(if (= n 0)
45+
m
46+
(gcd n (mod m n))))
47+
(gcd 18 45)
48+
"""
49+
def test_gcd():
50+
got = run(gcd_src)
51+
assert got == 9
52+
53+
54+
quicksort_src = """
55+
(define (quicksort lst)
56+
(if (null? lst)
57+
lst
58+
(begin
59+
(define pivot (car lst))
60+
(define rest (cdr lst))
61+
(append
62+
(quicksort
63+
(filter (lambda (x) (< x pivot)) rest))
64+
(list pivot)
65+
(quicksort
66+
(filter (lambda (x) (>= x pivot)) rest)))
67+
)
68+
)
69+
)
70+
(quicksort (list 2 1 6 3 4 0 8 9 7 5))
71+
"""
72+
def test_quicksort():
73+
got = run(quicksort_src)
74+
assert got == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
75+
76+
77+
# Example from Structure and Interpretation of Computer Programs
78+
# https://mitpress.mit.edu/sites/default/files/sicp/full-text/sicp/book/node12.html
79+
80+
newton_src = """
81+
(define (sqrt x)
82+
(sqrt-iter 1.0 x))
83+
(define (sqrt-iter guess x)
84+
(if (good-enough? guess x)
85+
guess
86+
(sqrt-iter (improve guess x) x)))
87+
(define (good-enough? guess x)
88+
(< (abs (- (* guess guess) x)) 0.001))
89+
(define (improve guess x)
90+
(average guess (/ x guess)))
91+
(define (average x y)
92+
(/ (+ x y) 2))
93+
(sqrt 123454321)
94+
"""
95+
def test_newton():
96+
got = run(newton_src)
97+
assert math.isclose(got, 11111)
98+
99+
100+
closure_src = """
101+
(define (make-adder increment)
102+
(lambda (x) (+ increment x))
103+
)
104+
(define inc (make-adder 1))
105+
(inc 99)
106+
"""
107+
def test_newton():
108+
got = run(closure_src)
109+
assert got == 100

18-context-mngr/lispy/py3.10/lis.py renamed to 02-array-seq/lispy/lis.py

+58-34
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
################ Lispy: Scheme Interpreter in Python 3.9
1+
################ Lispy: Scheme Interpreter in Python 3.10
22

33
## (c) Peter Norvig, 2010-18; See http://norvig.com/lispy.html
44
## Minor edits for Fluent Python, Second Edition (O'Reilly, 2021)
55
## by Luciano Ramalho, adding type hints and pattern matching.
66

7-
################ Imports and Types
7+
################ imports and types
88

99
import math
1010
import operator as op
1111
from collections import ChainMap
12-
from collections.abc import MutableMapping
12+
from collections.abc import MutableMapping, Iterator
13+
from itertools import chain
1314
from typing import Any, TypeAlias
1415

1516
Symbol: TypeAlias = str
@@ -23,41 +24,44 @@ class Procedure:
2324
"A user-defined Scheme procedure."
2425

2526
def __init__(self, parms: list[Symbol], body: Expression, env: Environment):
26-
self.parms, self.body, self.env = parms, body, env
27+
self.parms = parms
28+
self.body = body
29+
self.env = env
2730

2831
def __call__(self, *args: Expression) -> Any:
2932
local_env = dict(zip(self.parms, args))
3033
env: Environment = ChainMap(local_env, self.env)
3134
return evaluate(self.body, env)
3235

3336

34-
################ Global Environment
37+
################ global environment
3538

3639

3740
def standard_env() -> Environment:
3841
"An environment with some Scheme standard procedures."
3942
env: Environment = {}
4043
env.update(vars(math)) # sin, cos, sqrt, pi, ...
41-
env.update(
42-
{
44+
env.update({
4345
'+': op.add,
4446
'-': op.sub,
4547
'*': op.mul,
4648
'/': op.truediv,
49+
'//': op.floordiv,
4750
'>': op.gt,
4851
'<': op.lt,
4952
'>=': op.ge,
5053
'<=': op.le,
5154
'=': op.eq,
5255
'abs': abs,
53-
'append': op.add,
56+
'append': lambda *args: list(chain(*args)),
5457
'apply': lambda proc, args: proc(*args),
5558
'begin': lambda *x: x[-1],
5659
'car': lambda x: x[0],
5760
'cdr': lambda x: x[1:],
5861
'cons': lambda x, y: [x] + y,
5962
'eq?': op.is_,
6063
'equal?': op.eq,
64+
'filter': lambda *args: list(filter(*args)),
6165
'length': len,
6266
'list': lambda *x: list(x),
6367
'list?': lambda x: isinstance(x, list),
@@ -70,12 +74,11 @@ def standard_env() -> Environment:
7074
'procedure?': callable,
7175
'round': round,
7276
'symbol?': lambda x: isinstance(x, Symbol),
73-
}
74-
)
77+
})
7578
return env
7679

7780

78-
################ Parsing: parse, tokenize, and read_from_tokens
81+
################ parse, tokenize, and read_from_tokens
7982

8083

8184
def parse(program: str) -> Expression:
@@ -94,11 +97,11 @@ def read_from_tokens(tokens: list[str]) -> Expression:
9497
raise SyntaxError('unexpected EOF while reading')
9598
token = tokens.pop(0)
9699
if '(' == token:
97-
L = []
100+
exp = []
98101
while tokens[0] != ')':
99-
L.append(read_from_tokens(tokens))
100-
tokens.pop(0) # pop off ')'
101-
return L
102+
exp.append(read_from_tokens(tokens))
103+
tokens.pop(0) # discard ')'
104+
return exp
102105
elif ')' == token:
103106
raise SyntaxError('unexpected )')
104107
else:
@@ -116,7 +119,7 @@ def parse_atom(token: str) -> Atom:
116119
return Symbol(token)
117120

118121

119-
################ Interaction: A REPL
122+
################ interaction: a REPL
120123

121124

122125
def repl(prompt: str = 'lis.py> ') -> None:
@@ -138,29 +141,50 @@ def lispstr(exp: object) -> str:
138141

139142
################ eval
140143

141-
142-
def evaluate(x: Expression, env: Environment) -> Any:
144+
# tag::EVALUATE[]
145+
def evaluate(exp: Expression, env: Environment) -> Any:
143146
"Evaluate an expression in an environment."
144-
match x:
145-
case Symbol(var): # variable reference
147+
match exp:
148+
case int(x) | float(x):
149+
return x
150+
case Symbol(var):
146151
return env[var]
147-
case literal if not isinstance(x, list): # constant literal
148-
return literal
149-
case ['quote', exp]: # (quote exp)
152+
case []:
153+
return []
154+
case ['quote', exp]:
150155
return exp
151-
case ['if', test, conseq, alt]: # (if test conseq alt)
156+
case ['if', test, consequence, alternative]:
152157
if evaluate(test, env):
153-
exp = conseq
158+
return evaluate(consequence, env)
154159
else:
155-
exp = alt
156-
return evaluate(exp, env)
157-
case ['lambda', parms, body]: # (lambda (parm...) body)
158-
return Procedure(parms, body, env)
159-
case ['define', Symbol(var), exp]: # (define var exp)
160-
env[var] = evaluate(exp, env)
161-
case ['define', [name, *parms], body]: # (define (fun parm...) body)
160+
return evaluate(alternative, env)
161+
case ['define', Symbol(var), value_exp]:
162+
env[var] = evaluate(value_exp, env)
163+
case ['define', [Symbol(name), *parms], body]:
162164
env[name] = Procedure(parms, body, env)
163-
case [op, *args]: # (proc arg...)
165+
case ['lambda', [*parms], body]:
166+
return Procedure(parms, body, env)
167+
case [op, *args]:
164168
proc = evaluate(op, env)
165-
values = (evaluate(arg, env) for arg in args)
169+
values = [evaluate(arg, env) for arg in args]
166170
return proc(*values)
171+
case _:
172+
raise SyntaxError(repr(exp))
173+
# end::EVALUATE[]
174+
175+
176+
################ non-interactive execution
177+
178+
179+
def run_lines(source: str) -> Iterator[Any]:
180+
global_env: Environment = standard_env()
181+
tokens = tokenize(source)
182+
while tokens:
183+
exp = read_from_tokens(tokens)
184+
yield evaluate(exp, global_env)
185+
186+
187+
def run(source: str) -> Any:
188+
for result in run_lines(source):
189+
pass
190+
return result

18-context-mngr/lispy/py3.10/lis_test.py renamed to 02-array-seq/lispy/lis_test.py

+17
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@
44

55
from lis import parse, evaluate, Expression, Environment, standard_env
66

7+
############################################################# tests for parse
8+
9+
@mark.parametrize( 'source, expected', [
10+
('7', 7),
11+
('x', 'x'),
12+
('(sum 1 2 3)', ['sum', 1, 2, 3]),
13+
('(+ (* 2 100) (* 1 10))', ['+', ['*', 2, 100], ['*', 1, 10]]),
14+
('99 100', 99), # parse stops at the first complete expression
15+
('(a)(b)', ['a']),
16+
])
17+
def test_parse(source: str, expected: Expression) -> None:
18+
got = parse(source)
19+
assert got == expected
20+
21+
22+
########################################################## tests for evaluate
23+
724
# Norvig's tests are not isolated: they assume the
825
# same environment from first to last test.
926
global_env_for_first_test = standard_env()

02-array-seq/lispy/meta_test.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import operator as op
2+
3+
from lis import run
4+
5+
env_scm = """
6+
(define standard-env (list
7+
(list (quote +) +)
8+
(list (quote -) -)
9+
))
10+
standard-env
11+
"""
12+
13+
def test_env_build():
14+
got = run(env_scm)
15+
assert got == [['+', op.add], ['-', op.sub]]
16+
17+
scan_scm = """
18+
(define l (quote (a b c)))
19+
(define (scan what where)
20+
(if (null? where)
21+
()
22+
(if (eq? what (car where))
23+
what
24+
(scan what (cdr where))))
25+
)
26+
"""
27+
28+
def test_scan():
29+
source = scan_scm + '(scan (quote a) l )'
30+
got = run(source)
31+
assert got == 'a'
32+
33+
34+
def test_scan_not_found():
35+
source = scan_scm + '(scan (quote z) l )'
36+
got = run(source)
37+
assert got == []
38+
39+
40+
lookup_scm = """
41+
(define env (list
42+
(list (quote +) +)
43+
(list (quote -) -)
44+
))
45+
(define (lookup what where)
46+
(if (null? where)
47+
()
48+
(if (eq? what (car (car where)))
49+
(car (cdr (car where)))
50+
(lookup what (cdr where))))
51+
)
52+
"""
53+
54+
def test_lookup():
55+
source = lookup_scm + '(lookup (quote +) env)'
56+
got = run(source)
57+
assert got == op.add
58+
59+
60+
def test_lookup_not_found():
61+
source = lookup_scm + '(lookup (quote z) env )'
62+
got = run(source)
63+
assert got == []
64+

0 commit comments

Comments
 (0)