complete draft: update from Atlas
This commit is contained in:
parent
08a4001b43
commit
135eca25d9
@ -5,7 +5,7 @@ import random
|
||||
from tombola import Tombola
|
||||
|
||||
|
||||
class LotteryBlower(Tombola):
|
||||
class LottoBlower(Tombola):
|
||||
|
||||
def __init__(self, iterable):
|
||||
self._balls = list(iterable) # <1>
|
||||
@ -17,14 +17,14 @@ class LotteryBlower(Tombola):
|
||||
try:
|
||||
position = random.randrange(len(self._balls)) # <2>
|
||||
except ValueError:
|
||||
raise LookupError('pick from empty BingoCage')
|
||||
raise LookupError('pick from empty LottoBlower')
|
||||
return self._balls.pop(position) # <3>
|
||||
|
||||
def loaded(self): # <4>
|
||||
return bool(self._balls)
|
||||
|
||||
def inspect(self): # <5>
|
||||
return tuple(sorted(self._balls))
|
||||
return tuple(self._balls)
|
||||
|
||||
|
||||
# end::LOTTERY_BLOWER[]
|
||||
|
@ -28,7 +28,7 @@ class Tombola(abc.ABC): # <1>
|
||||
except LookupError:
|
||||
break
|
||||
self.load(items) # <7>
|
||||
return tuple(sorted(items))
|
||||
return tuple(items)
|
||||
|
||||
|
||||
# end::TOMBOLA_ABC[]
|
||||
|
@ -1,4 +1,5 @@
|
||||
# tag::TOMBOLA_RUNNER[]
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import doctest
|
||||
|
||||
from tombola import Tombola
|
||||
@ -13,8 +14,7 @@ TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
|
||||
def main(argv):
|
||||
verbose = '-v' in argv
|
||||
real_subclasses = Tombola.__subclasses__() # <2>
|
||||
virtual_subclasses = list(Tombola._abc_registry) # <3>
|
||||
|
||||
virtual_subclasses = [tombolist.TomboList] # <3>
|
||||
for cls in real_subclasses + virtual_subclasses: # <4>
|
||||
test(cls, verbose)
|
||||
|
||||
@ -33,4 +33,3 @@ def test(cls, verbose=False):
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
main(sys.argv)
|
||||
# end::TOMBOLA_RUNNER[]
|
||||
|
@ -11,8 +11,8 @@ Create and load instance from iterable::
|
||||
>>> globe = ConcreteTombola(balls)
|
||||
>>> globe.loaded()
|
||||
True
|
||||
>>> globe.inspect()
|
||||
(0, 1, 2)
|
||||
>>> sorted(globe.inspect())
|
||||
[0, 1, 2]
|
||||
|
||||
|
||||
Pick and collect balls::
|
||||
|
@ -18,6 +18,6 @@ class TomboList(list): # <2>
|
||||
return bool(self) # <6>
|
||||
|
||||
def inspect(self):
|
||||
return tuple(sorted(self))
|
||||
return tuple(self)
|
||||
|
||||
# Tombola.register(TomboList) # <7>
|
||||
|
@ -38,8 +38,7 @@ deploy(trash_can)
|
||||
# tag::DEPLOY_NOT_VALID[]
|
||||
compost_can: TrashCan[Compostable] = TrashCan()
|
||||
deploy(compost_can)
|
||||
# end::DEPLOY_NOT_VALID[]
|
||||
|
||||
## Argument 1 to "deploy" has
|
||||
## mypy: Argument 1 to "deploy" has
|
||||
## incompatible type "TrashCan[Compostable]"
|
||||
## expected "TrashCan[Biodegradable]"
|
||||
# end::DEPLOY_NOT_VALID[]
|
||||
|
@ -38,11 +38,12 @@ orange_juice_dispenser = BeverageDispenser(OrangeJuice())
|
||||
install(orange_juice_dispenser)
|
||||
# end::INSTALL_JUICE_DISPENSERS[]
|
||||
|
||||
################################################ not a juice dispenser
|
||||
################################################ more general dispenser
|
||||
|
||||
# tag::INSTALL_BEVERAGE_DISPENSER[]
|
||||
beverage_dispenser = BeverageDispenser(Beverage())
|
||||
|
||||
## Argument 1 to "install" has
|
||||
install(beverage_dispenser)
|
||||
## mypy: Argument 1 to "install" has
|
||||
## incompatible type "BeverageDispenser[Beverage]"
|
||||
## expected "BeverageDispenser[Juice]"
|
||||
install(beverage_dispenser)
|
||||
# end::INSTALL_BEVERAGE_DISPENSER[]
|
||||
|
@ -37,7 +37,7 @@ install(juice_dispenser)
|
||||
# tag::INSTALL_BEVERAGE_DISPENSER[]
|
||||
beverage_dispenser = BeverageDispenser(Beverage())
|
||||
install(beverage_dispenser)
|
||||
## Argument 1 to "install" has
|
||||
## mypy: Argument 1 to "install" has
|
||||
## incompatible type "BeverageDispenser[Beverage]"
|
||||
## expected "BeverageDispenser[Juice]"
|
||||
# end::INSTALL_BEVERAGE_DISPENSER[]
|
||||
@ -48,7 +48,7 @@ install(beverage_dispenser)
|
||||
# tag::INSTALL_ORANGE_JUICE_DISPENSER[]
|
||||
orange_juice_dispenser = BeverageDispenser(OrangeJuice())
|
||||
install(orange_juice_dispenser)
|
||||
# end::INSTALL_ORANGE_JUICE_DISPENSER[]
|
||||
## Argument 1 to "install" has
|
||||
## mypy: Argument 1 to "install" has
|
||||
## incompatible type "BeverageDispenser[OrangeJuice]"
|
||||
## expected "BeverageDispenser[Juice]"
|
||||
# end::INSTALL_ORANGE_JUICE_DISPENSER[]
|
||||
|
2
15-more-types/cast/empty.py
Normal file
2
15-more-types/cast/empty.py
Normal file
@ -0,0 +1,2 @@
|
||||
# Mypy 0.812 can't spot this inevitable runtime IndexError
|
||||
print([][0])
|
23
15-more-types/cast/find.py
Normal file
23
15-more-types/cast/find.py
Normal file
@ -0,0 +1,23 @@
|
||||
# tag::CAST[]
|
||||
from typing import cast
|
||||
|
||||
def find_first_str(a: list[object]) -> str:
|
||||
index = next(i for i, x in enumerate(a) if isinstance(x, str))
|
||||
# We only get here if there's at least one string in a
|
||||
return cast(str, a[index])
|
||||
# end::CAST[]
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
l1 = [10, 20, 'thirty', 40]
|
||||
if TYPE_CHECKING:
|
||||
reveal_type(l1)
|
||||
|
||||
print(find_first_str(l1))
|
||||
|
||||
l2 = [0, ()]
|
||||
try:
|
||||
find_first_str(l2)
|
||||
except StopIteration as e:
|
||||
print(repr(e))
|
37
15-more-types/cast/tcp_echo.py
Normal file
37
15-more-types/cast/tcp_echo.py
Normal file
@ -0,0 +1,37 @@
|
||||
import asyncio
|
||||
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
|
||||
# tag::CAST_IMPORTS[]
|
||||
from asyncio.trsock import TransportSocket
|
||||
from typing import cast
|
||||
# end::CAST_IMPORTS[]
|
||||
|
||||
async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None:
|
||||
data = await reader.read(100)
|
||||
message = data.decode()
|
||||
addr = writer.get_extra_info('peername')
|
||||
|
||||
print(f"Received {message!r} from {addr!r}")
|
||||
|
||||
print(f"Send: {message!r}")
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
|
||||
print("Close the connection")
|
||||
writer.close()
|
||||
|
||||
async def main() -> None:
|
||||
server = await asyncio.start_server(
|
||||
handle_echo, '127.0.0.1', 8888)
|
||||
|
||||
# tag::CAST_USE[]
|
||||
socket_list = cast(tuple[TransportSocket, ...], server.sockets)
|
||||
addr = socket_list[0].getsockname()
|
||||
# end::CAST_USE[]
|
||||
print(f'Serving on {addr}')
|
||||
|
||||
async with server:
|
||||
await server.serve_forever()
|
||||
|
||||
asyncio.run(main())
|
29
15-more-types/cast/tcp_echo_no_cast.py
Normal file
29
15-more-types/cast/tcp_echo_no_cast.py
Normal file
@ -0,0 +1,29 @@
|
||||
import asyncio
|
||||
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
|
||||
async def handle_echo(reader: StreamReader, writer: StreamWriter) -> None:
|
||||
data = await reader.read(100)
|
||||
message = data.decode()
|
||||
addr = writer.get_extra_info('peername')
|
||||
|
||||
print(f"Received {message!r} from {addr!r}")
|
||||
|
||||
print(f"Send: {message!r}")
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
|
||||
print("Close the connection")
|
||||
writer.close()
|
||||
|
||||
async def main() -> None:
|
||||
server = await asyncio.start_server(
|
||||
handle_echo, '127.0.0.1', 8888)
|
||||
|
||||
addr = server.sockets[0].getsockname()
|
||||
print(f'Serving on {addr}')
|
||||
|
||||
async with server:
|
||||
await server.serve_forever()
|
||||
|
||||
asyncio.run(main())
|
3
15-more-types/clip_annot_demo.py
Normal file
3
15-more-types/clip_annot_demo.py
Normal file
@ -0,0 +1,3 @@
|
||||
from clip_annot_post import clip
|
||||
|
||||
print(clip.__annotations__)
|
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
>>> clip('banana ', 6)
|
||||
'banana'
|
||||
@ -18,9 +20,9 @@
|
||||
"""
|
||||
|
||||
# tag::CLIP_ANNOT[]
|
||||
|
||||
def clip(text: str, max_len: 'int > 0' = 80) -> str: # <1>
|
||||
"""Return text clipped at the last space before or after max_len
|
||||
def clip(text: str, max_len: int = 80) -> str:
|
||||
"""Return new ``str`` clipped at last space before or after ``max_len``.
|
||||
Return full ``text`` if no space found.
|
||||
"""
|
||||
end = None
|
||||
if len(text) > max_len:
|
||||
@ -31,7 +33,7 @@ def clip(text: str, max_len: 'int > 0' = 80) -> str: # <1>
|
||||
space_after = text.rfind(' ', max_len)
|
||||
if space_after >= 0:
|
||||
end = space_after
|
||||
if end is None: # no spaces were found
|
||||
if end is None:
|
||||
end = len(text)
|
||||
return text[:end].rstrip()
|
||||
|
@ -1,10 +0,0 @@
|
||||
>>> from clip_annot import clip
|
||||
>>> from inspect import signature
|
||||
>>> sig = signature(clip)
|
||||
>>> sig.return_annotation
|
||||
<class 'str'>
|
||||
>>> for param in sig.parameters.values():
|
||||
... note = repr(param.annotation).ljust(13)
|
||||
... print(note, ':', param.name, '=', param.default)
|
||||
<class 'str'> : text = <class 'inspect._empty'>
|
||||
'int > 0' : max_len = 80
|
29
15-more-types/lotto/generic_lotto.py
Normal file
29
15-more-types/lotto/generic_lotto.py
Normal file
@ -0,0 +1,29 @@
|
||||
import random
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
from tombola import Tombola
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class LottoBlower(Tombola, Generic[T]): # <1>
|
||||
|
||||
def __init__(self, items: Iterable[T]) -> None: # <2>
|
||||
self._balls = list[T](items)
|
||||
|
||||
def load(self, items: Iterable[T]) -> None: # <3>
|
||||
self._balls.extend(items)
|
||||
|
||||
def pick(self) -> T: # <4>
|
||||
try:
|
||||
position = random.randrange(len(self._balls))
|
||||
except ValueError:
|
||||
raise LookupError('pick from empty LottoBlower')
|
||||
return self._balls.pop(position)
|
||||
|
||||
def loaded(self) -> bool: # <5>
|
||||
return bool(self._balls)
|
||||
|
||||
def inspect(self) -> tuple[T, ...]: # <6>
|
||||
return tuple(self._balls)
|
28
15-more-types/lotto/generic_lotto_demo.py
Executable file
28
15-more-types/lotto/generic_lotto_demo.py
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# tag::LOTTO_USE[]
|
||||
from generic_lotto import LottoBlower
|
||||
|
||||
machine = LottoBlower[int](range(1, 11)) # <1>
|
||||
|
||||
first = machine.pick() # <2>
|
||||
remain = machine.inspect() # <3>
|
||||
# end::LOTTO_USE[]
|
||||
|
||||
expected = set(i for i in range(1, 11) if i != first)
|
||||
|
||||
assert set(remain) == expected
|
||||
|
||||
print('picked:', first)
|
||||
print('remain:', remain)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
reveal_type(first)
|
||||
# Revealed type is 'builtins.int*'
|
||||
if TYPE_CHECKING:
|
||||
reveal_type(remain)
|
||||
# Revealed type is 'builtins.tuple[builtins.int*]'
|
||||
|
||||
|
18
15-more-types/lotto/generic_lotto_errors.py
Executable file
18
15-more-types/lotto/generic_lotto_errors.py
Executable file
@ -0,0 +1,18 @@
|
||||
from generic_lotto import LottoBlower
|
||||
|
||||
machine = LottoBlower[int]([1, .2])
|
||||
## error: List item 1 has incompatible type "float"; # <1>
|
||||
## expected "int"
|
||||
|
||||
machine = LottoBlower[int](range(1, 11))
|
||||
|
||||
machine.load('ABC')
|
||||
## error: Argument 1 to "load" of "LottoBlower" # <2>
|
||||
## has incompatible type "str";
|
||||
## expected "Iterable[int]"
|
||||
## note: Following member(s) of "str" have conflicts:
|
||||
## note: Expected:
|
||||
## note: def __iter__(self) -> Iterator[int]
|
||||
## note: Got:
|
||||
## note: def __iter__(self) -> Iterator[str]
|
||||
|
34
15-more-types/lotto/tombola.py
Normal file
34
15-more-types/lotto/tombola.py
Normal file
@ -0,0 +1,34 @@
|
||||
# tag::TOMBOLA_ABC[]
|
||||
|
||||
import abc
|
||||
|
||||
class Tombola(abc.ABC): # <1>
|
||||
|
||||
@abc.abstractmethod
|
||||
def load(self, iterable): # <2>
|
||||
"""Add items from an iterable."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def pick(self): # <3>
|
||||
"""Remove item at random, returning it.
|
||||
|
||||
This method should raise `LookupError` when the instance is empty.
|
||||
"""
|
||||
|
||||
def loaded(self): # <4>
|
||||
"""Return `True` if there's at least 1 item, `False` otherwise."""
|
||||
return bool(self.inspect()) # <5>
|
||||
|
||||
def inspect(self):
|
||||
"""Return a sorted tuple with the items currently inside."""
|
||||
items = []
|
||||
while True: # <6>
|
||||
try:
|
||||
items.append(self.pick())
|
||||
except LookupError:
|
||||
break
|
||||
self.load(items) # <7>
|
||||
return tuple(items)
|
||||
|
||||
|
||||
# end::TOMBOLA_ABC[]
|
@ -1,13 +1,14 @@
|
||||
from functools import reduce # <1>
|
||||
from operator import add
|
||||
from typing import overload, Iterable, Union, TypeVar
|
||||
import functools
|
||||
import operator
|
||||
from collections.abc import Iterable
|
||||
from typing import overload, Union, TypeVar
|
||||
|
||||
T = TypeVar('T')
|
||||
S = TypeVar('S') # <2>
|
||||
S = TypeVar('S') # <1>
|
||||
|
||||
@overload
|
||||
def sum(it: Iterable[T]) -> Union[T, int]: ... # <3>
|
||||
def sum(it: Iterable[T]) -> Union[T, int]: ... # <2>
|
||||
@overload
|
||||
def sum(it: Iterable[T], /, start: S) -> Union[T, S]: ... # <4>
|
||||
def sum(it, /, start=0): # <5>
|
||||
return reduce(add, it, start)
|
||||
def sum(it: Iterable[T], /, start: S) -> Union[T, S]: ... # <3>
|
||||
def sum(it, /, start=0): # <4>
|
||||
return functools.reduce(operator.add, it, start)
|
||||
|
32
15-more-types/protocol/abs_demo.py
Executable file
32
15-more-types/protocol/abs_demo.py
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import math
|
||||
from typing import NamedTuple, SupportsAbs
|
||||
|
||||
class Vector2d(NamedTuple):
|
||||
x: float
|
||||
y: float
|
||||
|
||||
def __abs__(self) -> float: # <1>
|
||||
return math.hypot(self.x, self.y)
|
||||
|
||||
def is_unit(v: SupportsAbs[float]) -> bool: # <2>
|
||||
"""'True' if the magnitude of 'v' is close to 1."""
|
||||
return math.isclose(abs(v), 1.0) # <3>
|
||||
|
||||
assert issubclass(Vector2d, SupportsAbs) # <4>
|
||||
|
||||
v0 = Vector2d(0, 1) # <5>
|
||||
sqrt2 = math.sqrt(2)
|
||||
v1 = Vector2d(sqrt2 / 2, sqrt2 / 2)
|
||||
v2 = Vector2d(1, 1)
|
||||
v3 = complex(.5, math.sqrt(3) / 2)
|
||||
v4 = 1 # <6>
|
||||
|
||||
assert is_unit(v0)
|
||||
assert is_unit(v1)
|
||||
assert not is_unit(v2)
|
||||
assert is_unit(v3)
|
||||
assert is_unit(v4)
|
||||
|
||||
print('OK')
|
@ -12,10 +12,10 @@ MISSING = object()
|
||||
EMPTY_MSG = 'max() arg is an empty sequence'
|
||||
|
||||
@overload
|
||||
def max(__arg1: LT, __arg2: LT, *_args: LT, key: None = ...) -> LT:
|
||||
def max(__arg1: LT, __arg2: LT, *args: LT, key: None = ...) -> LT:
|
||||
...
|
||||
@overload
|
||||
def max(__arg1: T, __arg2: T, *_args: T, key: Callable[[T], LT]) -> T:
|
||||
def max(__arg1: T, __arg2: T, *args: T, key: Callable[[T], LT]) -> T:
|
||||
...
|
||||
@overload
|
||||
def max(__iterable: Iterable[LT], *, key: None = ...) -> LT:
|
7
15-more-types/protocol/random/generic_randompick.py
Normal file
7
15-more-types/protocol/random/generic_randompick.py
Normal file
@ -0,0 +1,7 @@
|
||||
from typing import Protocol, runtime_checkable, TypeVar
|
||||
|
||||
T_co = TypeVar('T_co', covariant=True) # <1>
|
||||
|
||||
@runtime_checkable
|
||||
class RandomPicker(Protocol[T_co]): # <2>
|
||||
def pick(self) -> T_co: ... # <3>
|
@ -1,24 +1,26 @@
|
||||
import random
|
||||
from typing import Iterable, TYPE_CHECKING
|
||||
from typing import Iterable, Generic, TypeVar, TYPE_CHECKING
|
||||
|
||||
from randompick_generic import GenericRandomPicker
|
||||
T_co = TypeVar('T_co', covariant=True)
|
||||
|
||||
from generic_randompick import RandomPicker
|
||||
|
||||
|
||||
class LottoPicker:
|
||||
def __init__(self, items: Iterable[int]) -> None:
|
||||
class LottoPicker(Generic[T_co]):
|
||||
def __init__(self, items: Iterable[T_co]) -> None:
|
||||
self._items = list(items)
|
||||
random.shuffle(self._items)
|
||||
|
||||
def pick(self) -> int:
|
||||
def pick(self) -> T_co:
|
||||
return self._items.pop()
|
||||
|
||||
|
||||
def test_issubclass() -> None:
|
||||
assert issubclass(LottoPicker, GenericRandomPicker)
|
||||
assert issubclass(LottoPicker, RandomPicker)
|
||||
|
||||
|
||||
def test_isinstance() -> None:
|
||||
popper: GenericRandomPicker = LottoPicker([1])
|
||||
popper: RandomPicker = LottoPicker[int]([1])
|
||||
if TYPE_CHECKING:
|
||||
reveal_type(popper)
|
||||
# Revealed type is '???'
|
@ -1,7 +0,0 @@
|
||||
from typing import Protocol, runtime_checkable, TypeVar
|
||||
|
||||
T_co = TypeVar('T_co', covariant=True)
|
||||
|
||||
@runtime_checkable
|
||||
class GenericRandomPicker(Protocol[T_co]):
|
||||
def pick(self) -> T_co: ...
|
21
18-context-mngr/lispy/LICENSE
Normal file
21
18-context-mngr/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.
|
24
18-context-mngr/lispy/README.md
Normal file
24
18-context-mngr/lispy/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# lis.py
|
||||
|
||||
This directory contains 3 versions of
|
||||
[Peter Norvig's `lis.py` interpreter](https://norvig.com/lispy.html)
|
||||
for Scheme.
|
||||
|
||||
* `original/`: Norvig's `lis.py` unchanged, `lispy.py` with
|
||||
[minor changes](https://github.com/norvig/pytudes/pull/106) to run on Python 3,
|
||||
and the `lispytest.py` custom test suite;
|
||||
* `py3.9/`: `lis.py` with type hints and a few minor edits—requires Python 3.9;
|
||||
* `py3.10/`: `lis.py` with type hints, pattern matching, and minor edits—requires Python 3.10.
|
||||
|
||||
The `py3.9/` and `py3.10/` directories also have identical `lis_test.py` to run with
|
||||
[pytest](https://docs.pytest.org).
|
||||
These files include all the
|
||||
[`lis_tests` suite](https://github.com/norvig/pytudes/blob/60168bce8cdfacf57c92a5b2979f0b2e95367753/py/lispytest.py#L5)
|
||||
from `original/lispytest.py`,
|
||||
and individual tests for each expression and special form handled by `evaluate`.
|
||||
|
||||
## Provenance, Copyright and License
|
||||
|
||||
`lis.py` is published 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).
|
132
18-context-mngr/lispy/original/lis.py
Normal file
132
18-context-mngr/lispy/original/lis.py
Normal file
@ -0,0 +1,132 @@
|
||||
################ 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)
|
||||
return eval(self.body, env)
|
||||
|
||||
################ 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
18-context-mngr/lispy/original/lispy.py
Normal file
316
18-context-mngr/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
18-context-mngr/lispy/original/lispytest.py
Normal file
122
18-context-mngr/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')
|
||||
|
160
18-context-mngr/lispy/py3.10/lis.py
Normal file
160
18-context-mngr/lispy/py3.10/lis.py
Normal file
@ -0,0 +1,160 @@
|
||||
################ 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
|
||||
from typing import Any, TypeAlias
|
||||
|
||||
Atom: TypeAlias = float | int | str
|
||||
Expression: TypeAlias = Atom | list
|
||||
|
||||
Environment: TypeAlias = MutableMapping[str, object]
|
||||
|
||||
|
||||
class Procedure:
|
||||
"A user-defined Scheme procedure."
|
||||
|
||||
def __init__(self, parms: list[str], body: Expression, env: Environment):
|
||||
self.parms, self.body, self.env = parms, body, env
|
||||
|
||||
def __call__(self, *args: Expression) -> Any:
|
||||
env: Environment = ChainMap(dict(zip(self.parms, args)), self.env)
|
||||
return evaluate(self.body, env)
|
||||
|
||||
|
||||
################ 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, str),
|
||||
}
|
||||
)
|
||||
return env
|
||||
|
||||
|
||||
global_env: Environment = standard_env()
|
||||
|
||||
################ Parsing: parse, tokenize, and read_from_tokens
|
||||
|
||||
|
||||
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:
|
||||
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 parse_atom(token)
|
||||
|
||||
|
||||
def parse_atom(token: str) -> Atom:
|
||||
"Numbers become numbers; every other token is a symbol."
|
||||
try:
|
||||
return int(token)
|
||||
except ValueError:
|
||||
try:
|
||||
return float(token)
|
||||
except ValueError:
|
||||
return str(token)
|
||||
|
||||
|
||||
################ Interaction: A REPL
|
||||
|
||||
|
||||
def repl(prompt: str = 'lis.py> ') -> None:
|
||||
"A prompt-read-evaluate-print loop."
|
||||
while True:
|
||||
val = evaluate(parse(input(prompt)))
|
||||
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):
|
||||
return '(' + ' '.join(map(lispstr, exp)) + ')'
|
||||
else:
|
||||
return str(exp)
|
||||
|
||||
|
||||
################ eval
|
||||
|
||||
|
||||
def evaluate(x: Expression, env: Environment = global_env) -> Any:
|
||||
"Evaluate an expression in an environment."
|
||||
match x:
|
||||
case str(): # variable reference
|
||||
return env[x]
|
||||
case literal if not isinstance(x, list): # constant literal
|
||||
return literal
|
||||
case ['quote', exp]: # (quote exp)
|
||||
return exp
|
||||
case ['if', test, conseq, alt]: # (if test conseq alt)
|
||||
exp = conseq if evaluate(test, env) else alt
|
||||
return evaluate(exp, env)
|
||||
case ['define', var, exp]: # (define var exp)
|
||||
env[var] = evaluate(exp, env)
|
||||
case ['lambda', parms, body]: # (lambda (var...) body)
|
||||
return Procedure(parms, body, env)
|
||||
case [op, *args]: # (proc arg...)
|
||||
proc = evaluate(op, env)
|
||||
values = (evaluate(arg, env) for arg in args)
|
||||
return proc(*values)
|
147
18-context-mngr/lispy/py3.10/lis_test.py
Executable file
147
18-context-mngr/lispy/py3.10/lis_test.py
Executable file
@ -0,0 +1,147 @@
|
||||
from typing import Optional
|
||||
|
||||
from pytest import mark
|
||||
|
||||
from lis import parse, evaluate, Expression, Environment, standard_env
|
||||
|
||||
|
||||
@mark.parametrize( 'source, expected', [
|
||||
("(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]),
|
||||
])
|
||||
@mark.skip
|
||||
def test_evaluate(source: str, expected: Optional[Expression]) -> None:
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
# tests for each of the cases in evaluate
|
||||
|
||||
def test_evaluate_variable() -> None:
|
||||
env: Environment = dict(x=10)
|
||||
source = 'x'
|
||||
expected = 10
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_literal() -> None:
|
||||
source = '3.3'
|
||||
expected = 3.3
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_quote() -> None:
|
||||
source = '(quote (1.1 is not 1))'
|
||||
expected = [1.1, 'is', 'not', 1]
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_if_true() -> None:
|
||||
source = '(if 1 10 no-such-thing)'
|
||||
expected = 10
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_if_false() -> None:
|
||||
source = '(if 0 no-such-thing 20)'
|
||||
expected = 20
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_define() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(define answer (* 6 7))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got is None
|
||||
assert env['answer'] == 42
|
||||
|
||||
|
||||
def test_lambda() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(lambda (a b) (if (>= a b) a b))'
|
||||
func = evaluate(parse(source), env)
|
||||
assert func.parms == ['a', 'b']
|
||||
assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b']
|
||||
assert func.env is env
|
||||
assert func(1, 2) == 2
|
||||
assert func(3, 2) == 3
|
||||
|
||||
|
||||
def test_begin() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = """
|
||||
(begin
|
||||
(define x (* 2 3))
|
||||
(* x 7)
|
||||
)
|
||||
"""
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == 42
|
||||
|
||||
|
||||
def test_invocation_builtin_car() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(car (quote (11 22 33)))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == 11
|
||||
|
||||
|
||||
def test_invocation_builtin_append() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(append (quote (a b)) (quote (c d)))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == ['a', 'b', 'c', 'd']
|
||||
|
||||
|
||||
def test_invocation_builtin_map() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == [2, 4, 6]
|
||||
|
||||
|
||||
def test_invocation_user_procedure() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = """
|
||||
(begin
|
||||
(define max (lambda (a b) (if (>= a b) a b)))
|
||||
(max 22 11)
|
||||
)
|
||||
"""
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == 22
|
163
18-context-mngr/lispy/py3.9/lis.py
Normal file
163
18-context-mngr/lispy/py3.9/lis.py
Normal file
@ -0,0 +1,163 @@
|
||||
################ 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, mostly adding type hints.
|
||||
|
||||
################ Imports and Types
|
||||
|
||||
import math
|
||||
import operator as op
|
||||
from collections import ChainMap
|
||||
from collections.abc import MutableMapping
|
||||
from typing import Union, Any
|
||||
|
||||
Atom = Union[float, int, str]
|
||||
Expression = Union[Atom, list]
|
||||
|
||||
Environment = MutableMapping[str, object]
|
||||
|
||||
|
||||
class Procedure:
|
||||
"A user-defined Scheme procedure."
|
||||
|
||||
def __init__(self, parms: list[str], body: Expression, env: Environment):
|
||||
self.parms, self.body, self.env = parms, body, env
|
||||
|
||||
def __call__(self, *args: Expression) -> Any:
|
||||
env: Environment = ChainMap(dict(zip(self.parms, args)), self.env)
|
||||
return evaluate(self.body, env)
|
||||
|
||||
|
||||
################ 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, str),
|
||||
}
|
||||
)
|
||||
return env
|
||||
|
||||
|
||||
global_env: Environment = standard_env()
|
||||
|
||||
################ Parsing: parse, tokenize, and read_from_tokens
|
||||
|
||||
|
||||
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:
|
||||
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 parse_atom(token)
|
||||
|
||||
|
||||
def parse_atom(token: str) -> Atom:
|
||||
"Numbers become numbers; every other token is a symbol."
|
||||
try:
|
||||
return int(token)
|
||||
except ValueError:
|
||||
try:
|
||||
return float(token)
|
||||
except ValueError:
|
||||
return str(token)
|
||||
|
||||
|
||||
################ Interaction: A REPL
|
||||
|
||||
|
||||
def repl(prompt: str = 'lis.py> ') -> None:
|
||||
"A prompt-read-evaluate-print loop."
|
||||
while True:
|
||||
val = evaluate(parse(input(prompt)))
|
||||
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):
|
||||
return '(' + ' '.join(map(lispstr, exp)) + ')'
|
||||
else:
|
||||
return str(exp)
|
||||
|
||||
|
||||
################ eval
|
||||
|
||||
|
||||
def evaluate(x: Expression, env: Environment = global_env) -> Any:
|
||||
"Evaluate an expression in an environment."
|
||||
if isinstance(x, str): # 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 evaluate(test, env) else alt
|
||||
return evaluate(exp, env)
|
||||
elif x[0] == 'define': # (define var exp)
|
||||
(_, var, exp) = x
|
||||
env[var] = evaluate(exp, env)
|
||||
elif x[0] == 'lambda': # (lambda (var...) body)
|
||||
(_, parms, body) = x
|
||||
return Procedure(parms, body, env)
|
||||
else: # (proc arg...)
|
||||
proc = evaluate(x[0], env)
|
||||
args = [evaluate(exp, env) for exp in x[1:]]
|
||||
return proc(*args)
|
147
18-context-mngr/lispy/py3.9/lis_test.py
Executable file
147
18-context-mngr/lispy/py3.9/lis_test.py
Executable file
@ -0,0 +1,147 @@
|
||||
from typing import Optional
|
||||
|
||||
from pytest import mark
|
||||
|
||||
from lis import parse, evaluate, Expression, Environment, standard_env
|
||||
|
||||
|
||||
@mark.parametrize( 'source, expected', [
|
||||
("(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]),
|
||||
])
|
||||
@mark.skip
|
||||
def test_evaluate(source: str, expected: Optional[Expression]) -> None:
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
# tests for each of the cases in evaluate
|
||||
|
||||
def test_evaluate_variable() -> None:
|
||||
env: Environment = dict(x=10)
|
||||
source = 'x'
|
||||
expected = 10
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_literal() -> None:
|
||||
source = '3.3'
|
||||
expected = 3.3
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_quote() -> None:
|
||||
source = '(quote (1.1 is not 1))'
|
||||
expected = [1.1, 'is', 'not', 1]
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_if_true() -> None:
|
||||
source = '(if 1 10 no-such-thing)'
|
||||
expected = 10
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_evaluate_if_false() -> None:
|
||||
source = '(if 0 no-such-thing 20)'
|
||||
expected = 20
|
||||
got = evaluate(parse(source))
|
||||
assert got == expected
|
||||
|
||||
|
||||
def test_define() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(define answer (* 6 7))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got is None
|
||||
assert env['answer'] == 42
|
||||
|
||||
|
||||
def test_lambda() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(lambda (a b) (if (>= a b) a b))'
|
||||
func = evaluate(parse(source), env)
|
||||
assert func.parms == ['a', 'b']
|
||||
assert func.body == ['if', ['>=', 'a', 'b'], 'a', 'b']
|
||||
assert func.env is env
|
||||
assert func(1, 2) == 2
|
||||
assert func(3, 2) == 3
|
||||
|
||||
|
||||
def test_begin() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = """
|
||||
(begin
|
||||
(define x (* 2 3))
|
||||
(* x 7)
|
||||
)
|
||||
"""
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == 42
|
||||
|
||||
|
||||
def test_invocation_builtin_car() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(car (quote (11 22 33)))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == 11
|
||||
|
||||
|
||||
def test_invocation_builtin_append() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(append (quote (a b)) (quote (c d)))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == ['a', 'b', 'c', 'd']
|
||||
|
||||
|
||||
def test_invocation_builtin_map() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = '(map (lambda (x) (* x 2)) (quote (1 2 3))))'
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == [2, 4, 6]
|
||||
|
||||
|
||||
def test_invocation_user_procedure() -> None:
|
||||
env: Environment = standard_env()
|
||||
source = """
|
||||
(begin
|
||||
(define max (lambda (a b) (if (>= a b) a b)))
|
||||
(max 22 11)
|
||||
)
|
||||
"""
|
||||
got = evaluate(parse(source), env)
|
||||
assert got == 22
|
@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# tag::TCP_MOJIFINDER_TOP[]
|
||||
import sys
|
||||
import asyncio
|
||||
import functools
|
||||
import sys
|
||||
from asyncio.trsock import TransportSocket
|
||||
from typing import cast
|
||||
|
||||
from charindex import InvertedIndex, format_results # <1>
|
||||
|
||||
@ -54,17 +56,19 @@ async def supervisor(index: InvertedIndex, host: str, port: int):
|
||||
server = await asyncio.start_server( # <1>
|
||||
functools.partial(finder, index), # <2>
|
||||
host, port) # <3>
|
||||
addr = server.sockets[0].getsockname() # type: ignore # <4>
|
||||
print(f'Serving on {addr}. Hit CTRL-C to stop.')
|
||||
await server.serve_forever() # <5>
|
||||
|
||||
socket_list = cast(tuple[TransportSocket, ...], server.sockets) # <4>
|
||||
addr = socket_list[0].getsockname()
|
||||
print(f'Serving on {addr}. Hit CTRL-C to stop.') # <5>
|
||||
await server.serve_forever() # <6>
|
||||
|
||||
def main(host: str = '127.0.0.1', port_arg: str = '2323'):
|
||||
port = int(port_arg)
|
||||
print('Building index.')
|
||||
index = InvertedIndex() # <6>
|
||||
index = InvertedIndex() # <7>
|
||||
try:
|
||||
asyncio.run(supervisor(index, host, port)) # <7>
|
||||
except KeyboardInterrupt: # <8>
|
||||
asyncio.run(supervisor(index, host, port)) # <8>
|
||||
except KeyboardInterrupt: # <9>
|
||||
print('\nServer shut down.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -96,7 +96,7 @@ def checked(cls: type) -> type: # <1>
|
||||
for name, constructor in _fields(cls).items(): # <2>
|
||||
setattr(cls, name, Field(name, constructor)) # <3>
|
||||
|
||||
cls._fields = classmethod(_fields) #type: ignore # <4>
|
||||
cls._fields = classmethod(_fields) # type: ignore # <4>
|
||||
|
||||
instance_methods = ( # <5>
|
||||
__init__,
|
||||
|
Loading…
Reference in New Issue
Block a user