lambda-repl 1.1.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lambda_repl-1.2.1/.flake8 +3 -0
- lambda_repl-1.2.1/.gitignore +164 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1}/PKG-INFO +13 -6
- {lambda_repl-1.1.0 → lambda_repl-1.2.1}/README.md +8 -3
- lambda_repl-1.2.1/codecov +0 -0
- lambda_repl-1.2.1/codecov.SHA256SUM +1 -0
- lambda_repl-1.2.1/codecov.SHA256SUM.sig +16 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1}/pyproject.toml +38 -9
- {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/__init__.py +26 -1
- lambda_repl-1.2.1/src/lambda_repl/grammar.lark +13 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/parsing.py +12 -7
- lambda_repl-1.2.1/tests/__init__.py +0 -0
- lambda_repl-1.2.1/tests/test_aliases.py +68 -0
- lambda_repl-1.2.1/tests/test_parsing.py +242 -0
- lambda_repl-1.2.1/tests/test_repl.py +176 -0
- lambda_repl-1.1.0/lambda_repl/grammar.lark +0 -11
- lambda_repl-1.1.0/lambda_repl.egg-info/PKG-INFO +0 -53
- lambda_repl-1.1.0/lambda_repl.egg-info/SOURCES.txt +0 -16
- lambda_repl-1.1.0/lambda_repl.egg-info/dependency_links.txt +0 -1
- lambda_repl-1.1.0/lambda_repl.egg-info/entry_points.txt +0 -2
- lambda_repl-1.1.0/lambda_repl.egg-info/requires.txt +0 -2
- lambda_repl-1.1.0/lambda_repl.egg-info/top_level.txt +0 -1
- lambda_repl-1.1.0/setup.cfg +0 -4
- {lambda_repl-1.1.0 → lambda_repl-1.2.1}/LICENSE +0 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/__main__.py +0 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/aliases.py +0 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/main.py +0 -0
- {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/py.typed +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
.direnv
|
|
126
|
+
env/
|
|
127
|
+
venv/
|
|
128
|
+
ENV/
|
|
129
|
+
env.bak/
|
|
130
|
+
venv.bak/
|
|
131
|
+
|
|
132
|
+
# Spyder project settings
|
|
133
|
+
.spyderproject
|
|
134
|
+
.spyproject
|
|
135
|
+
|
|
136
|
+
# Rope project settings
|
|
137
|
+
.ropeproject
|
|
138
|
+
|
|
139
|
+
# mkdocs documentation
|
|
140
|
+
/site
|
|
141
|
+
|
|
142
|
+
# mypy
|
|
143
|
+
.mypy_cache/
|
|
144
|
+
.dmypy.json
|
|
145
|
+
dmypy.json
|
|
146
|
+
|
|
147
|
+
# Pyre type checker
|
|
148
|
+
.pyre/
|
|
149
|
+
|
|
150
|
+
# pytype static type analyzer
|
|
151
|
+
.pytype/
|
|
152
|
+
|
|
153
|
+
# Cython debug symbols
|
|
154
|
+
cython_debug/
|
|
155
|
+
|
|
156
|
+
# PyCharm
|
|
157
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
158
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
159
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
160
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
161
|
+
#.idea/
|
|
162
|
+
|
|
163
|
+
# VSCode
|
|
164
|
+
.vscode/
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: lambda_repl
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: REPL for the lambda calculus
|
|
5
|
-
Author-email: Eric Niklas Wolf <eric_niklas.wolf@mailbox.tu-dresden.de>
|
|
6
5
|
Project-URL: Repository, https://github.com/Deric-W/lambda_repl
|
|
7
6
|
Project-URL: Bugtracker, https://github.com/Deric-W/lambda_repl/issues
|
|
7
|
+
Author-email: Eric Niklas Wolf <eric_niklas.wolf@mailbox.tu-dresden.de>
|
|
8
|
+
License-File: LICENSE
|
|
8
9
|
Classifier: Intended Audience :: Education
|
|
9
10
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
10
11
|
Classifier: Operating System :: OS Independent
|
|
@@ -13,21 +14,25 @@ Classifier: Topic :: Education
|
|
|
13
14
|
Classifier: Topic :: Utilities
|
|
14
15
|
Classifier: Typing :: Typed
|
|
15
16
|
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: lambda-calculus~=3.0
|
|
18
|
+
Requires-Dist: lark~=1.0
|
|
16
19
|
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
20
|
|
|
19
21
|
# lambda_repl
|
|
20
22
|
|
|
23
|
+
[](https://github.com/pypa/hatch)
|
|
21
24
|

|
|
22
25
|
[](https://codecov.io/gh/Deric-W/lambda_repl)
|
|
23
26
|
|
|
24
|
-
The `lambda_repl` package contains a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
|
|
27
|
+
The `lambda_repl` package contains a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
|
|
28
|
+
for the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus).
|
|
25
29
|
|
|
26
30
|
To use it, execute `lambda-repl` or `python3 -m lambda_repl` and enter commands.
|
|
27
31
|
|
|
28
32
|
## Requirements
|
|
29
33
|
|
|
30
|
-
Python >= 3.10 and the `lambda_calculus`
|
|
34
|
+
Python >= 3.10 and the packages [`lambda_calculus`](https://github.com/Deric-W/lambda_calculus)
|
|
35
|
+
and [`lark`](https://github.com/lark-parser/lark) are required to use this package.
|
|
31
36
|
|
|
32
37
|
## Installation
|
|
33
38
|
|
|
@@ -42,9 +47,11 @@ python3 -m lambda_repl
|
|
|
42
47
|
Welcome to the the Lambda REPL, type 'help' for help
|
|
43
48
|
λ alias I = \x.x
|
|
44
49
|
λ alias K = λx.λy.x
|
|
50
|
+
λ import SUCC = lambda_calculus.terms.arithmetic.SUCCESSOR
|
|
45
51
|
λ aliases
|
|
46
52
|
I = (λx.x)
|
|
47
53
|
K = (λx.(λy.x))
|
|
54
|
+
SUCC = (λn.(λf.(λx.(f ((n f) x)))))
|
|
48
55
|
λ trace K a b
|
|
49
56
|
β ((λy.a) b)
|
|
50
57
|
β a
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# lambda_repl
|
|
2
2
|
|
|
3
|
+
[](https://github.com/pypa/hatch)
|
|
3
4
|

|
|
4
5
|
[](https://codecov.io/gh/Deric-W/lambda_repl)
|
|
5
6
|
|
|
6
|
-
The `lambda_repl` package contains a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
|
|
7
|
+
The `lambda_repl` package contains a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
|
|
8
|
+
for the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus).
|
|
7
9
|
|
|
8
10
|
To use it, execute `lambda-repl` or `python3 -m lambda_repl` and enter commands.
|
|
9
11
|
|
|
10
12
|
## Requirements
|
|
11
13
|
|
|
12
|
-
Python >= 3.10 and the `lambda_calculus`
|
|
14
|
+
Python >= 3.10 and the packages [`lambda_calculus`](https://github.com/Deric-W/lambda_calculus)
|
|
15
|
+
and [`lark`](https://github.com/lark-parser/lark) are required to use this package.
|
|
13
16
|
|
|
14
17
|
## Installation
|
|
15
18
|
|
|
@@ -24,12 +27,14 @@ python3 -m lambda_repl
|
|
|
24
27
|
Welcome to the the Lambda REPL, type 'help' for help
|
|
25
28
|
λ alias I = \x.x
|
|
26
29
|
λ alias K = λx.λy.x
|
|
30
|
+
λ import SUCC = lambda_calculus.terms.arithmetic.SUCCESSOR
|
|
27
31
|
λ aliases
|
|
28
32
|
I = (λx.x)
|
|
29
33
|
K = (λx.(λy.x))
|
|
34
|
+
SUCC = (λn.(λf.(λx.(f ((n f) x)))))
|
|
30
35
|
λ trace K a b
|
|
31
36
|
β ((λy.a) b)
|
|
32
37
|
β a
|
|
33
38
|
λ exit
|
|
34
39
|
Exiting REPL...
|
|
35
|
-
```
|
|
40
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
8930c4bb30254a42f3d8c340706b1be340885e20c0df5160a24efa2e030e662b codecov
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-----BEGIN PGP SIGNATURE-----
|
|
2
|
+
|
|
3
|
+
iQIzBAABCgAdFiEEJwNOf9uFDgu8LGL/gGuyiu13mGkFAmnnz8MACgkQgGuyiu13
|
|
4
|
+
mGnysA/7Byxt7YmTKIwMRW8+VOoUCp0mKosnlZMvDyc/lVS1K2xpMxXOl/mIMzPD
|
|
5
|
+
OZT7ApPZ70A2hDrsz631yMMxM9/xDAFPEmWfM3aepoQNsrZjyW8Qh9SSQQPvu+sh
|
|
6
|
+
v6cnZhLBwWty1fvdZTS9RWWBaqheCYSXmrLocLXgrh4TzlHeXxA2dAjNKWXZb5H6
|
|
7
|
+
M6FXnSTuQ5+kckijzzuaZJ7sA9IWQI4CtVXc+n850YPAOpg35xc7aR3vLOL5dlmW
|
|
8
|
+
jICLk0T2DX0PDE7Nw9mQhnRXZsGHifQUPrNsDu2CqRmHMaiP/9dL2LcD8A4bIR46
|
|
9
|
+
R48LTqDu75BueDIGerQya5SH38JZk3E2YAQb7tZMdviDp0yOGeYhNMfxUd8mxs8Q
|
|
10
|
+
oxLhZW73CFOjapQnRxyYaGi27f9ldCQ4w5KrxwM9YaFBr335cFYG183jDDFUYjr2
|
|
11
|
+
gzRi+jwKowr6h0Edx+teoB5B7FO0Gvx5gWhyI8xuoeklIu3iLaDYAyHxmUjHRfMx
|
|
12
|
+
D3ioTVwm+vkq+eIXJfDe0sAlJjTHMo/udsc4aKYmxtgj7Um0Xe8IUmPkopn09F+5
|
|
13
|
+
E5X0yncFG5CPLFHY5xZklEW2pZI2WdkXp/fXwfKUxgME4ntr1C1/y9x2j71bbRtu
|
|
14
|
+
neus07LhjRdYfDb3/9opafzxSJ8sTvkGnMaSfDshYo15dfTU+1g=
|
|
15
|
+
=SKRh
|
|
16
|
+
-----END PGP SIGNATURE-----
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lambda_repl"
|
|
3
|
-
version = "1.1
|
|
3
|
+
version = "1.2.1"
|
|
4
4
|
description = "REPL for the lambda calculus"
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
keywords = []
|
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Typing :: Typed"
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
|
-
"lambda-calculus ~=
|
|
17
|
+
"lambda-calculus ~= 3.0",
|
|
18
18
|
"lark ~= 1.0"
|
|
19
19
|
]
|
|
20
20
|
|
|
@@ -34,15 +34,36 @@ Bugtracker = "https://github.com/Deric-W/lambda_repl/issues"
|
|
|
34
34
|
lambda-repl = "lambda_repl.main:main_cli"
|
|
35
35
|
|
|
36
36
|
[build-system]
|
|
37
|
-
requires = ["
|
|
38
|
-
build-backend = "
|
|
37
|
+
requires = ["hatchling"]
|
|
38
|
+
build-backend = "hatchling.build"
|
|
39
39
|
|
|
40
|
-
[tool.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
[tool.hatch.envs.hatch-test]
|
|
41
|
+
installer = "pip"
|
|
42
|
+
|
|
43
|
+
[[tool.hatch.envs.hatch-test.matrix]]
|
|
44
|
+
python = ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.envs.lint]
|
|
47
|
+
dependencies = [
|
|
48
|
+
"mypy >= 1.0.0",
|
|
49
|
+
"pylint >= 2.12.2",
|
|
50
|
+
"flake8 >= 5.0.0",
|
|
51
|
+
"isort >= 5.10.1"
|
|
44
52
|
]
|
|
45
53
|
|
|
54
|
+
[tool.hatch.envs.lint.scripts]
|
|
55
|
+
lint = [
|
|
56
|
+
"- flake8 src/lambda_repl",
|
|
57
|
+
"- pylint src/lambda_repl"
|
|
58
|
+
]
|
|
59
|
+
typecheck = "mypy -p lambda_repl"
|
|
60
|
+
release = [
|
|
61
|
+
"typecheck"
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.targets.sdist]
|
|
65
|
+
exclude = ["/.github"]
|
|
66
|
+
|
|
46
67
|
[tool.mypy]
|
|
47
68
|
disallow_any_unimported = true
|
|
48
69
|
disallow_any_generics = true
|
|
@@ -52,4 +73,12 @@ strict_optional = true
|
|
|
52
73
|
warn_redundant_casts = true
|
|
53
74
|
warn_unused_ignores = true
|
|
54
75
|
warn_return_any = true
|
|
55
|
-
warn_unreachable = true
|
|
76
|
+
warn_unreachable = true
|
|
77
|
+
|
|
78
|
+
[tool.pylint]
|
|
79
|
+
max-line-length = 100
|
|
80
|
+
|
|
81
|
+
[tool.coverage.run]
|
|
82
|
+
source_pkgs = ["lambda_repl"]
|
|
83
|
+
branch = true
|
|
84
|
+
parallel = true
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from cmd import Cmd
|
|
7
|
+
from importlib import import_module
|
|
7
8
|
from typing import Any
|
|
8
9
|
from lambda_calculus.terms import Term
|
|
9
10
|
from lambda_calculus.visitors.normalisation import (
|
|
@@ -14,7 +15,7 @@ from lark.exceptions import UnexpectedInput
|
|
|
14
15
|
from .parsing import LambdaTransformer
|
|
15
16
|
from .aliases import Aliases
|
|
16
17
|
|
|
17
|
-
__version__ = "1.1
|
|
18
|
+
__version__ = "1.2.1"
|
|
18
19
|
__author__ = "Eric Niklas Wolf"
|
|
19
20
|
__email__ = "eric_niklas.wolf@mailbox.tu-dresden.de"
|
|
20
21
|
__all__ = (
|
|
@@ -51,6 +52,19 @@ class LambdaREPL(Cmd):
|
|
|
51
52
|
self.stdout.write(error.get_context(term))
|
|
52
53
|
return None
|
|
53
54
|
|
|
55
|
+
def import_term(self, location: str) -> Term[str] | None:
|
|
56
|
+
"""import a term and handle error display"""
|
|
57
|
+
module, _, name = location.strip().rpartition(".")
|
|
58
|
+
try:
|
|
59
|
+
term = getattr(import_module(module), name)
|
|
60
|
+
except Exception as error: # pylint: disable=W0718
|
|
61
|
+
self.stdout.write(f"Error while importing: {error}\n")
|
|
62
|
+
return None
|
|
63
|
+
if not isinstance(term, Term):
|
|
64
|
+
self.stdout.write(f"Error: object {term} is not a lambda term\n")
|
|
65
|
+
return None
|
|
66
|
+
return term
|
|
67
|
+
|
|
54
68
|
def emptyline(self) -> bool:
|
|
55
69
|
"""ignore empty lines"""
|
|
56
70
|
return False
|
|
@@ -95,6 +109,17 @@ class LambdaREPL(Cmd):
|
|
|
95
109
|
self.stdout.write("invalid Command: missing alias value\n")
|
|
96
110
|
return False
|
|
97
111
|
|
|
112
|
+
def do_import(self, arg: str) -> bool:
|
|
113
|
+
"""import an alias from a module with name = module.name"""
|
|
114
|
+
match arg.partition("="):
|
|
115
|
+
case (alias, "=", location):
|
|
116
|
+
term = self.import_term(location)
|
|
117
|
+
if term is not None:
|
|
118
|
+
self.aliases[alias.strip()] = term
|
|
119
|
+
case _:
|
|
120
|
+
self.stdout.write("invalid Command: missing import location\n")
|
|
121
|
+
return False
|
|
122
|
+
|
|
98
123
|
def do_aliases(self, _: object) -> bool:
|
|
99
124
|
"""list defined aliases"""
|
|
100
125
|
for alias, term in self.aliases.items():
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
_WHITESPACE: /\s+/
|
|
2
|
+
|
|
3
|
+
VARIABLE: /[^\s().λ\\]+/
|
|
4
|
+
|
|
5
|
+
abstraction: ("\\" | "λ") VARIABLE "." term
|
|
6
|
+
|
|
7
|
+
application: _application_term _WHITESPACE (abstraction | _simple_term)
|
|
8
|
+
|
|
9
|
+
?term: abstraction | _application_term
|
|
10
|
+
|
|
11
|
+
_application_term: application | _simple_term
|
|
12
|
+
|
|
13
|
+
_simple_term: VARIABLE | "(" term ")"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from collections import deque
|
|
7
|
-
from collections.abc import Iterator
|
|
7
|
+
from collections.abc import Iterator
|
|
8
8
|
from itertools import chain
|
|
9
9
|
from lambda_calculus.terms import Abstraction, Application, Term, Variable
|
|
10
10
|
from lark import Lark, Token
|
|
@@ -52,10 +52,10 @@ class LambdaTransformer(Transformer[Token, Term[str]]):
|
|
|
52
52
|
"""parse a string and return the transformed lambda term"""
|
|
53
53
|
# the parser sometimes return tokens directly instead of a tree
|
|
54
54
|
match PARSER.parse(string):
|
|
55
|
-
case Token(type="VARIABLE") as name:
|
|
56
|
-
return self.VARIABLE(name)
|
|
57
|
-
case Token() as token:
|
|
58
|
-
raise UnexpectedToken(token, {"VARIABLE",})
|
|
55
|
+
case Token(type="VARIABLE") as name:
|
|
56
|
+
return self.VARIABLE(name)
|
|
57
|
+
case Token() as token:
|
|
58
|
+
raise UnexpectedToken(token, {"VARIABLE", })
|
|
59
59
|
case tree:
|
|
60
60
|
return self.transform(tree)
|
|
61
61
|
|
|
@@ -63,6 +63,10 @@ class LambdaTransformer(Transformer[Token, Term[str]]):
|
|
|
63
63
|
"""handle unknown nodes"""
|
|
64
64
|
raise UnexpectedInput(f"unknown node: {data}")
|
|
65
65
|
|
|
66
|
+
def __default_token__(self, token: Token) -> Token:
|
|
67
|
+
"""handle unknown tokens"""
|
|
68
|
+
raise UnexpectedInput(f"unknown token: {token}")
|
|
69
|
+
|
|
66
70
|
def VARIABLE(self, name: Token) -> Variable[str]:
|
|
67
71
|
"""transform a variable node"""
|
|
68
72
|
return Variable(name.value)
|
|
@@ -72,9 +76,10 @@ class LambdaTransformer(Transformer[Token, Term[str]]):
|
|
|
72
76
|
"""transform an abstraction"""
|
|
73
77
|
return Abstraction(variable.name, body)
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
@v_args(inline=True)
|
|
80
|
+
def application(self, abstraction: Term[str], argument: Term[str]) -> Application[str]:
|
|
76
81
|
"""transform an application"""
|
|
77
|
-
return Application
|
|
82
|
+
return Application(abstraction, argument)
|
|
78
83
|
|
|
79
84
|
|
|
80
85
|
PARSER = Lark.open_from_package(
|
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
"""Tests for alias implementations"""
|
|
4
|
+
|
|
5
|
+
from unittest import TestCase
|
|
6
|
+
from lambda_calculus.terms import Variable
|
|
7
|
+
from lambda_calculus.visitors.substitution.renaming import CountingSubstitution
|
|
8
|
+
from lambda_repl import aliases
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LetAliasesTest(TestCase):
|
|
12
|
+
"""Test for alias implementation without self reference"""
|
|
13
|
+
|
|
14
|
+
aliases: aliases.LetAliases[str]
|
|
15
|
+
|
|
16
|
+
def setUp(self) -> None:
|
|
17
|
+
"""create empty aliases"""
|
|
18
|
+
self.aliases = aliases.LetAliases(CountingSubstitution)
|
|
19
|
+
|
|
20
|
+
def test_set(self) -> None:
|
|
21
|
+
"""test setting new aliases"""
|
|
22
|
+
self.aliases["a"] = Variable("1")
|
|
23
|
+
self.aliases["b"] = Variable("a").apply_to(Variable("c"))
|
|
24
|
+
self.aliases["c"] = Variable("2")
|
|
25
|
+
self.assertEqual(
|
|
26
|
+
list(self.aliases.items()),
|
|
27
|
+
[
|
|
28
|
+
("a", Variable("1")),
|
|
29
|
+
("b", Variable("1").apply_to(Variable("c"))),
|
|
30
|
+
("c", Variable("2"))
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def test_override(self) -> None:
|
|
35
|
+
"""test overriding aliases"""
|
|
36
|
+
self.aliases["a"] = Variable("1")
|
|
37
|
+
self.aliases["b"] = Variable("a").apply_to(Variable("c"))
|
|
38
|
+
self.aliases["a"] = Variable("2")
|
|
39
|
+
self.assertEqual(
|
|
40
|
+
list(self.aliases.items()),
|
|
41
|
+
[
|
|
42
|
+
("b", Variable("1").apply_to(Variable("c"))),
|
|
43
|
+
("a", Variable("2"))
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def test_apply(self) -> None:
|
|
48
|
+
"""test applying aliases"""
|
|
49
|
+
self.aliases["a"] = Variable("1")
|
|
50
|
+
self.aliases["b"] = Variable("a").apply_to(Variable("c"))
|
|
51
|
+
self.aliases["a"] = Variable("2")
|
|
52
|
+
self.aliases["c"] = Variable("3")
|
|
53
|
+
self.assertEqual(
|
|
54
|
+
self.aliases.apply(
|
|
55
|
+
Variable("a").apply_to(Variable("b"), Variable("c"))
|
|
56
|
+
),
|
|
57
|
+
Variable("2").apply_to(
|
|
58
|
+
Variable("1").apply_to(Variable("c")), Variable("3")
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def test_self_reference(self) -> None:
|
|
63
|
+
"""test handling of self references"""
|
|
64
|
+
self.aliases["a"] = Variable("a").apply_to(Variable("b"))
|
|
65
|
+
self.assertEqual(
|
|
66
|
+
self.aliases["a"],
|
|
67
|
+
Variable("a").apply_to(Variable("b"))
|
|
68
|
+
)
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
"""Tests for lambda term parsing"""
|
|
4
|
+
|
|
5
|
+
from unittest import TestCase
|
|
6
|
+
from lambda_calculus.terms import Variable, Abstraction, Application
|
|
7
|
+
from lark import Token
|
|
8
|
+
from lambda_repl import parsing
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ParsingTest(TestCase):
|
|
12
|
+
"""Test for the term parser and transformer"""
|
|
13
|
+
|
|
14
|
+
transformer: parsing.LambdaTransformer
|
|
15
|
+
|
|
16
|
+
def setUp(self) -> None:
|
|
17
|
+
"""create a transformer"""
|
|
18
|
+
self.transformer = parsing.LambdaTransformer()
|
|
19
|
+
|
|
20
|
+
def test_example(self) -> None:
|
|
21
|
+
"""test lambda_calculus example"""
|
|
22
|
+
x = Variable("x")
|
|
23
|
+
y = Variable("y")
|
|
24
|
+
term = Variable("+") \
|
|
25
|
+
.apply_to(x, y) \
|
|
26
|
+
.abstract("x", "y") \
|
|
27
|
+
.apply_to(y, Variable("3")) \
|
|
28
|
+
.abstract("y") \
|
|
29
|
+
.apply_to(Variable("4"))
|
|
30
|
+
self.assertEqual(
|
|
31
|
+
self.transformer.transform_string("(λy.(λx.(λy. + x y)) y 3) 4"),
|
|
32
|
+
term
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def test_whitespace(self) -> None:
|
|
36
|
+
"""test allowed whitespace"""
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
self.transformer.transform_string("a \t b"),
|
|
39
|
+
Application(Variable("a"), Variable("b"))
|
|
40
|
+
)
|
|
41
|
+
self.assertEqual(
|
|
42
|
+
self.transformer.transform_string("λ a \t. \t b"),
|
|
43
|
+
Abstraction("a", Variable("b"))
|
|
44
|
+
)
|
|
45
|
+
self.assertEqual(
|
|
46
|
+
self.transformer.transform_string("a ( λ a . b ) c"),
|
|
47
|
+
Application.with_arguments(
|
|
48
|
+
Variable("a"),
|
|
49
|
+
(Abstraction("a", Variable("b")), Variable("c"))
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def test_variables(self) -> None:
|
|
54
|
+
"""test valid variable names"""
|
|
55
|
+
for name in ("x", "1", "1x", "hi!", "ähm-hi?"):
|
|
56
|
+
self.assertEqual(
|
|
57
|
+
self.transformer.transform_string(f"λ{name}.{name}"),
|
|
58
|
+
Abstraction(name, Variable(name))
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def test_application(self) -> None:
|
|
62
|
+
"""test application order"""
|
|
63
|
+
self.assertEqual(
|
|
64
|
+
self.transformer.transform_string("a b c"),
|
|
65
|
+
Variable("a").apply_to(Variable("b"), Variable("c"))
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def test_abstraction(self) -> None:
|
|
69
|
+
"""test abstraction parsing"""
|
|
70
|
+
self.assertEqual(
|
|
71
|
+
self.transformer.transform_string("λa.λb.a b c"),
|
|
72
|
+
Variable("a")
|
|
73
|
+
.apply_to(Variable("b"), Variable("c"))
|
|
74
|
+
.abstract("a", "b")
|
|
75
|
+
)
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
self.transformer.transform_string("a λa.a b c"),
|
|
78
|
+
Variable("a").apply_to(Variable("a").apply_to(Variable("b"), Variable("c")).abstract("a"))
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def test_brackets(self) -> None:
|
|
82
|
+
"""test bracket parsing"""
|
|
83
|
+
self.assertEqual(
|
|
84
|
+
self.transformer.transform_string("(a)"),
|
|
85
|
+
Variable("a")
|
|
86
|
+
)
|
|
87
|
+
self.assertEqual(
|
|
88
|
+
self.transformer.transform_string("(a b) (c d)"),
|
|
89
|
+
Application(
|
|
90
|
+
Application(
|
|
91
|
+
Variable("a"),
|
|
92
|
+
Variable("b")
|
|
93
|
+
),
|
|
94
|
+
Application(
|
|
95
|
+
Variable("c"),
|
|
96
|
+
Variable("d")
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
self.assertEqual(
|
|
101
|
+
self.transformer.transform_string("λa.(λb.a) b c"),
|
|
102
|
+
Abstraction("b", Variable("a"))
|
|
103
|
+
.apply_to(Variable("b"), Variable("c"))
|
|
104
|
+
.abstract("a")
|
|
105
|
+
)
|
|
106
|
+
self.assertEqual(
|
|
107
|
+
self.transformer.transform_string("(λa.a) λb.b"),
|
|
108
|
+
Application(
|
|
109
|
+
Abstraction("a", Variable("a")),
|
|
110
|
+
Abstraction("b", Variable("b"))
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def test_backslash(self) -> None:
|
|
115
|
+
"""test lambda alternative"""
|
|
116
|
+
self.assertEqual(
|
|
117
|
+
self.transformer.transform_string(r"\a.a"),
|
|
118
|
+
Abstraction("a", Variable("a"))
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class PostLexTest(TestCase):
|
|
123
|
+
"""Tests for WhitespacePostLex"""
|
|
124
|
+
|
|
125
|
+
postlex: parsing.WhitespacePostLex
|
|
126
|
+
|
|
127
|
+
def setUp(self) -> None:
|
|
128
|
+
"""create a PostLex"""
|
|
129
|
+
self.postlex = parsing.WhitespacePostLex()
|
|
130
|
+
|
|
131
|
+
def test_no_whitespace(self) -> None:
|
|
132
|
+
"""test handling of tokens other than whitespace"""
|
|
133
|
+
tokens = (
|
|
134
|
+
Token("BACKLASH", "\\"),
|
|
135
|
+
Token("VARIABLE", "a"),
|
|
136
|
+
Token("DOT", "."),
|
|
137
|
+
Token("LPAR", "("),
|
|
138
|
+
Token("VARIABLE", "a"),
|
|
139
|
+
Token("RPAR", ")")
|
|
140
|
+
)
|
|
141
|
+
self.assertEqual(
|
|
142
|
+
tuple(self.postlex.process(iter(tokens))),
|
|
143
|
+
tokens
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def test_abstraction_unnecessary(self) -> None:
|
|
147
|
+
"""test handling of unnecessary whitespace in abstractions"""
|
|
148
|
+
tokens = (
|
|
149
|
+
Token("BACKLASH", "λ"),
|
|
150
|
+
Token("_WHITESPACE", " "),
|
|
151
|
+
Token("VARIABLE", "a"),
|
|
152
|
+
Token("_WHITESPACE", "\t"),
|
|
153
|
+
Token("DOT", "."),
|
|
154
|
+
Token("_WHITESPACE", " "),
|
|
155
|
+
Token("LPAR", "("),
|
|
156
|
+
Token("VARIABLE", "a"),
|
|
157
|
+
Token("RPAR", ")")
|
|
158
|
+
)
|
|
159
|
+
self.assertEqual(
|
|
160
|
+
tuple(self.postlex.process(iter(tokens))),
|
|
161
|
+
(
|
|
162
|
+
Token("BACKLASH", "λ"),
|
|
163
|
+
Token("VARIABLE", "a"),
|
|
164
|
+
Token("DOT", "."),
|
|
165
|
+
Token("LPAR", "("),
|
|
166
|
+
Token("VARIABLE", "a"),
|
|
167
|
+
Token("RPAR", ")")
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_backslash_unnecessary(self) -> None:
|
|
172
|
+
"""test handling of unnecessary whitespace in abstractions with a backslash"""
|
|
173
|
+
tokens = (
|
|
174
|
+
Token("BACKLASH", "\\"),
|
|
175
|
+
Token("_WHITESPACE", " "),
|
|
176
|
+
Token("VARIABLE", "a"),
|
|
177
|
+
Token("_WHITESPACE", "\t"),
|
|
178
|
+
Token("DOT", "."),
|
|
179
|
+
Token("_WHITESPACE", " "),
|
|
180
|
+
Token("LPAR", "("),
|
|
181
|
+
Token("VARIABLE", "a"),
|
|
182
|
+
Token("RPAR", ")")
|
|
183
|
+
)
|
|
184
|
+
self.assertEqual(
|
|
185
|
+
tuple(self.postlex.process(iter(tokens))),
|
|
186
|
+
(
|
|
187
|
+
Token("BACKLASH", "\\"),
|
|
188
|
+
Token("VARIABLE", "a"),
|
|
189
|
+
Token("DOT", "."),
|
|
190
|
+
Token("LPAR", "("),
|
|
191
|
+
Token("VARIABLE", "a"),
|
|
192
|
+
Token("RPAR", ")")
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def test_brackets_unnecessary(self) -> None:
|
|
197
|
+
"""test handling of unnecessary whitespace in brackets"""
|
|
198
|
+
tokens = (
|
|
199
|
+
Token("BACKLASH", "\\"),
|
|
200
|
+
Token("VARIABLE", "a"),
|
|
201
|
+
Token("DOT", "."),
|
|
202
|
+
Token("LPAR", "("),
|
|
203
|
+
Token("_WHITESPACE", "\t"),
|
|
204
|
+
Token("VARIABLE", "a"),
|
|
205
|
+
Token("_WHITESPACE", " "),
|
|
206
|
+
Token("RPAR", ")")
|
|
207
|
+
)
|
|
208
|
+
self.assertEqual(
|
|
209
|
+
tuple(self.postlex.process(iter(tokens))),
|
|
210
|
+
(
|
|
211
|
+
Token("BACKLASH", "\\"),
|
|
212
|
+
Token("VARIABLE", "a"),
|
|
213
|
+
Token("DOT", "."),
|
|
214
|
+
Token("LPAR", "("),
|
|
215
|
+
Token("VARIABLE", "a"),
|
|
216
|
+
Token("RPAR", ")")
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def test_ends_unnecessary(self) -> None:
|
|
221
|
+
"""test handling of unnecessary whitespace at the start and end"""
|
|
222
|
+
tokens = (
|
|
223
|
+
Token("_WHITESPACE", "\t"),
|
|
224
|
+
Token("VARIABLE", "a"),
|
|
225
|
+
Token("_WHITESPACE", " ")
|
|
226
|
+
)
|
|
227
|
+
self.assertEqual(
|
|
228
|
+
tuple(self.postlex.process(iter(tokens))),
|
|
229
|
+
(tokens[1],)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def test_necessary(self) -> None:
|
|
233
|
+
"""test handling of necessary whitespace"""
|
|
234
|
+
tokens = (
|
|
235
|
+
Token("VARIABLE", "a"),
|
|
236
|
+
Token("_WHITESPACE", "\t"),
|
|
237
|
+
Token("VARIABLE", "a")
|
|
238
|
+
)
|
|
239
|
+
self.assertEqual(
|
|
240
|
+
tuple(self.postlex.process(iter(tokens))),
|
|
241
|
+
tokens
|
|
242
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
"""Tests for the REPL"""
|
|
4
|
+
|
|
5
|
+
from io import StringIO
|
|
6
|
+
from unittest import TestCase
|
|
7
|
+
from lambda_calculus.terms import Variable
|
|
8
|
+
from lambda_calculus.terms.arithmetic import SUCCESSOR
|
|
9
|
+
from lambda_calculus.visitors.substitution.renaming import CountingSubstitution
|
|
10
|
+
from lambda_calculus.visitors.normalisation import BetaNormalisingVisitor
|
|
11
|
+
from lambda_repl import LambdaREPL
|
|
12
|
+
from lambda_repl.aliases import LetAliases
|
|
13
|
+
from lambda_repl.parsing import LambdaTransformer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class REPLTest(TestCase):
|
|
17
|
+
"""Test for the REPL"""
|
|
18
|
+
|
|
19
|
+
repl: LambdaREPL
|
|
20
|
+
|
|
21
|
+
stdin: StringIO
|
|
22
|
+
|
|
23
|
+
stdout: StringIO
|
|
24
|
+
|
|
25
|
+
def setUp(self) -> None:
|
|
26
|
+
"""create a REPL"""
|
|
27
|
+
self.stdin = StringIO()
|
|
28
|
+
self.stdout = StringIO()
|
|
29
|
+
self.repl = LambdaREPL(
|
|
30
|
+
LetAliases(CountingSubstitution),
|
|
31
|
+
LambdaTransformer(),
|
|
32
|
+
BetaNormalisingVisitor(),
|
|
33
|
+
stdin=self.stdin,
|
|
34
|
+
stdout=self.stdout
|
|
35
|
+
)
|
|
36
|
+
self.repl.use_rawinput = False
|
|
37
|
+
|
|
38
|
+
def test_empty(self) -> None:
|
|
39
|
+
"""test handling of empty lines"""
|
|
40
|
+
self.assertFalse(self.repl.onecmd(""))
|
|
41
|
+
self.assertEqual(self.stdout.getvalue(), "")
|
|
42
|
+
|
|
43
|
+
def test_evaluate(self) -> None:
|
|
44
|
+
"""test evaluating terms"""
|
|
45
|
+
self.assertFalse(self.repl.onecmd(r"evaluate (\x.\y.x) a b"))
|
|
46
|
+
self.assertEqual(
|
|
47
|
+
self.stdout.getvalue(),
|
|
48
|
+
"a\n"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def test_eval(self) -> None:
|
|
52
|
+
"""test eval alias"""
|
|
53
|
+
self.assertFalse(self.repl.onecmd(r"eval (\x.\y.x) a b"))
|
|
54
|
+
self.assertEqual(
|
|
55
|
+
self.stdout.getvalue(),
|
|
56
|
+
"a\n"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def test_trace(self) -> None:
|
|
60
|
+
"""test tracing term evaluation"""
|
|
61
|
+
self.assertFalse(self.repl.onecmd(r"trace (\x.\y.x) a b"))
|
|
62
|
+
self.assertEqual(
|
|
63
|
+
self.stdout.getvalue(),
|
|
64
|
+
"β ((λy.a) b)\nβ a\n"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def test_syntax_error(self) -> None:
|
|
68
|
+
"""test handling of syntax errors while parsing"""
|
|
69
|
+
self.assertFalse(self.repl.onecmd(r"eval (\x.\y.x) a b."))
|
|
70
|
+
self.assertTrue(self.stdout.getvalue().startswith("Error while parsing: "))
|
|
71
|
+
self.assertTrue(self.stdout.getvalue().endswith("\n"))
|
|
72
|
+
|
|
73
|
+
def test_alias(self) -> None:
|
|
74
|
+
"""test setting aliases"""
|
|
75
|
+
self.assertFalse(self.repl.onecmd("alias a = b c"))
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
self.repl.aliases,
|
|
78
|
+
{
|
|
79
|
+
"a": Variable("b").apply_to(Variable("c"))
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def test_invalid_alias(self) -> None:
|
|
84
|
+
"""test handling invalid aliases"""
|
|
85
|
+
self.assertFalse(self.repl.onecmd("alias a = b c."))
|
|
86
|
+
self.assertEqual(self.repl.aliases, {})
|
|
87
|
+
self.assertTrue(self.stdout.getvalue().startswith("Error while parsing: "))
|
|
88
|
+
self.assertTrue(self.stdout.getvalue().endswith("\n"))
|
|
89
|
+
|
|
90
|
+
def test_no_alias_value(self) -> None:
|
|
91
|
+
"""test handling missing alias values"""
|
|
92
|
+
self.assertFalse(self.repl.onecmd("alias a"))
|
|
93
|
+
self.assertEqual(self.repl.aliases, {})
|
|
94
|
+
self.assertTrue(self.stdout.getvalue().startswith("invalid Command: "))
|
|
95
|
+
self.assertTrue(self.stdout.getvalue().endswith("\n"))
|
|
96
|
+
|
|
97
|
+
def test_import(self) -> None:
|
|
98
|
+
"""test importing aliases"""
|
|
99
|
+
self.assertFalse(self.repl.onecmd(
|
|
100
|
+
"import SUCC = lambda_calculus.terms.arithmetic.SUCCESSOR"
|
|
101
|
+
))
|
|
102
|
+
self.assertEqual(
|
|
103
|
+
self.repl.aliases,
|
|
104
|
+
{
|
|
105
|
+
"SUCC": SUCCESSOR
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def test_invalid_import(self) -> None:
|
|
110
|
+
"""test handling of invalid imports"""
|
|
111
|
+
for location in (
|
|
112
|
+
"lambda_calculus.terms.arithmetic.SUCCESSORX",
|
|
113
|
+
"lambda_calculus.terms.arithmeticX.SUCCESSOR"
|
|
114
|
+
):
|
|
115
|
+
self.assertFalse(self.repl.onecmd(f"import SUCC = {location}"))
|
|
116
|
+
self.assertEqual(self.repl.aliases, {})
|
|
117
|
+
self.assertTrue(self.stdout.getvalue().startswith("Error while importing: "))
|
|
118
|
+
self.assertTrue(self.stdout.getvalue().endswith("\n"))
|
|
119
|
+
self.stdout.seek(0)
|
|
120
|
+
self.stdout.truncate(0)
|
|
121
|
+
self.assertFalse(self.repl.onecmd("import SUCC = lambda_calculus.terms.arithmetic.number"))
|
|
122
|
+
self.assertEqual(self.repl.aliases, {})
|
|
123
|
+
self.assertTrue(self.stdout.getvalue().startswith("Error"))
|
|
124
|
+
self.assertTrue(self.stdout.getvalue().endswith("\n"))
|
|
125
|
+
|
|
126
|
+
def test_no_import_value(self) -> None:
|
|
127
|
+
"""test handling missing import values"""
|
|
128
|
+
self.assertFalse(self.repl.onecmd("import a"))
|
|
129
|
+
self.assertEqual(self.repl.aliases, {})
|
|
130
|
+
self.assertTrue(self.stdout.getvalue().startswith("invalid Command: "))
|
|
131
|
+
self.assertTrue(self.stdout.getvalue().endswith("\n"))
|
|
132
|
+
|
|
133
|
+
def test_aliases(self) -> None:
|
|
134
|
+
"""test listing aliases"""
|
|
135
|
+
self.assertFalse(self.repl.onecmd("alias x = 1"))
|
|
136
|
+
self.assertFalse(self.repl.onecmd("alias a = x b"))
|
|
137
|
+
self.assertFalse(self.repl.onecmd("alias b = b c"))
|
|
138
|
+
self.assertFalse(self.repl.onecmd("aliases"))
|
|
139
|
+
self.assertEqual(
|
|
140
|
+
self.stdout.getvalue(),
|
|
141
|
+
"x = 1\na = (1 b)\nb = (b c)\n"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def test_clear(self) -> None:
|
|
145
|
+
"""test clearing aliases"""
|
|
146
|
+
self.assertFalse(self.repl.onecmd("alias x = 1"))
|
|
147
|
+
self.assertFalse(self.repl.onecmd("alias a = x b"))
|
|
148
|
+
self.assertFalse(self.repl.onecmd("alias b = b c"))
|
|
149
|
+
self.assertFalse(self.repl.onecmd("clear x"))
|
|
150
|
+
self.assertEqual(
|
|
151
|
+
self.repl.aliases,
|
|
152
|
+
{
|
|
153
|
+
"a": Variable("1").apply_to(Variable("b")),
|
|
154
|
+
"b": Variable("b").apply_to(Variable("c"))
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
self.assertEqual(self.stdout.getvalue(), "")
|
|
158
|
+
|
|
159
|
+
def test_clear_all(self) -> None:
|
|
160
|
+
"""test clearing all aliases"""
|
|
161
|
+
self.assertFalse(self.repl.onecmd("alias x = 1"))
|
|
162
|
+
self.assertFalse(self.repl.onecmd("alias a = x b"))
|
|
163
|
+
self.assertFalse(self.repl.onecmd("alias b = b c"))
|
|
164
|
+
self.assertFalse(self.repl.onecmd("clear"))
|
|
165
|
+
self.assertEqual(self.repl.aliases, {})
|
|
166
|
+
self.assertEqual(self.stdout.getvalue(), "")
|
|
167
|
+
|
|
168
|
+
def test_exit(self) -> None:
|
|
169
|
+
"""test exiting the REPL"""
|
|
170
|
+
self.assertTrue(self.repl.onecmd("exit"))
|
|
171
|
+
self.assertTrue(self.stdout.getvalue().startswith("Exiting "))
|
|
172
|
+
|
|
173
|
+
def test_eof(self) -> None:
|
|
174
|
+
"""test handling EOF"""
|
|
175
|
+
self.assertTrue(self.repl.onecmd("EOF"))
|
|
176
|
+
self.assertTrue(self.stdout.getvalue().startswith("Exiting "))
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
_WHITESPACE: /\s+/
|
|
2
|
-
|
|
3
|
-
VARIABLE: /[^\s().λ\\]+/
|
|
4
|
-
|
|
5
|
-
abstraction: ("\\" | "λ") VARIABLE "." term
|
|
6
|
-
|
|
7
|
-
application: ( abstraction | VARIABLE | brackets ) ( _WHITESPACE ( abstraction | VARIABLE | brackets ) )+
|
|
8
|
-
|
|
9
|
-
?brackets: "(" term ")"
|
|
10
|
-
|
|
11
|
-
?term: abstraction | application | VARIABLE | brackets
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: lambda-repl
|
|
3
|
-
Version: 1.1.0
|
|
4
|
-
Summary: REPL for the lambda calculus
|
|
5
|
-
Author-email: Eric Niklas Wolf <eric_niklas.wolf@mailbox.tu-dresden.de>
|
|
6
|
-
Project-URL: Repository, https://github.com/Deric-W/lambda_repl
|
|
7
|
-
Project-URL: Bugtracker, https://github.com/Deric-W/lambda_repl/issues
|
|
8
|
-
Classifier: Intended Audience :: Education
|
|
9
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
-
Classifier: Topic :: Education
|
|
13
|
-
Classifier: Topic :: Utilities
|
|
14
|
-
Classifier: Typing :: Typed
|
|
15
|
-
Requires-Python: >=3.10
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
License-File: LICENSE
|
|
18
|
-
|
|
19
|
-
# lambda_repl
|
|
20
|
-
|
|
21
|
-

|
|
22
|
-
[](https://codecov.io/gh/Deric-W/lambda_repl)
|
|
23
|
-
|
|
24
|
-
The `lambda_repl` package contains a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) for the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus).
|
|
25
|
-
|
|
26
|
-
To use it, execute `lambda-repl` or `python3 -m lambda_repl` and enter commands.
|
|
27
|
-
|
|
28
|
-
## Requirements
|
|
29
|
-
|
|
30
|
-
Python >= 3.10 and the `lambda_calculus` package are required to use this package.
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
python3 -m pip install lambda-repl
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Examples
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
python3 -m lambda_repl
|
|
42
|
-
Welcome to the the Lambda REPL, type 'help' for help
|
|
43
|
-
λ alias I = \x.x
|
|
44
|
-
λ alias K = λx.λy.x
|
|
45
|
-
λ aliases
|
|
46
|
-
I = (λx.x)
|
|
47
|
-
K = (λx.(λy.x))
|
|
48
|
-
λ trace K a b
|
|
49
|
-
β ((λy.a) b)
|
|
50
|
-
β a
|
|
51
|
-
λ exit
|
|
52
|
-
Exiting REPL...
|
|
53
|
-
```
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
lambda_repl/__init__.py
|
|
5
|
-
lambda_repl/__main__.py
|
|
6
|
-
lambda_repl/aliases.py
|
|
7
|
-
lambda_repl/grammar.lark
|
|
8
|
-
lambda_repl/main.py
|
|
9
|
-
lambda_repl/parsing.py
|
|
10
|
-
lambda_repl/py.typed
|
|
11
|
-
lambda_repl.egg-info/PKG-INFO
|
|
12
|
-
lambda_repl.egg-info/SOURCES.txt
|
|
13
|
-
lambda_repl.egg-info/dependency_links.txt
|
|
14
|
-
lambda_repl.egg-info/entry_points.txt
|
|
15
|
-
lambda_repl.egg-info/requires.txt
|
|
16
|
-
lambda_repl.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
lambda_repl
|
lambda_repl-1.1.0/setup.cfg
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|