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.
Files changed (28) hide show
  1. lambda_repl-1.2.1/.flake8 +3 -0
  2. lambda_repl-1.2.1/.gitignore +164 -0
  3. {lambda_repl-1.1.0 → lambda_repl-1.2.1}/PKG-INFO +13 -6
  4. {lambda_repl-1.1.0 → lambda_repl-1.2.1}/README.md +8 -3
  5. lambda_repl-1.2.1/codecov +0 -0
  6. lambda_repl-1.2.1/codecov.SHA256SUM +1 -0
  7. lambda_repl-1.2.1/codecov.SHA256SUM.sig +16 -0
  8. {lambda_repl-1.1.0 → lambda_repl-1.2.1}/pyproject.toml +38 -9
  9. {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/__init__.py +26 -1
  10. lambda_repl-1.2.1/src/lambda_repl/grammar.lark +13 -0
  11. {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/parsing.py +12 -7
  12. lambda_repl-1.2.1/tests/__init__.py +0 -0
  13. lambda_repl-1.2.1/tests/test_aliases.py +68 -0
  14. lambda_repl-1.2.1/tests/test_parsing.py +242 -0
  15. lambda_repl-1.2.1/tests/test_repl.py +176 -0
  16. lambda_repl-1.1.0/lambda_repl/grammar.lark +0 -11
  17. lambda_repl-1.1.0/lambda_repl.egg-info/PKG-INFO +0 -53
  18. lambda_repl-1.1.0/lambda_repl.egg-info/SOURCES.txt +0 -16
  19. lambda_repl-1.1.0/lambda_repl.egg-info/dependency_links.txt +0 -1
  20. lambda_repl-1.1.0/lambda_repl.egg-info/entry_points.txt +0 -2
  21. lambda_repl-1.1.0/lambda_repl.egg-info/requires.txt +0 -2
  22. lambda_repl-1.1.0/lambda_repl.egg-info/top_level.txt +0 -1
  23. lambda_repl-1.1.0/setup.cfg +0 -4
  24. {lambda_repl-1.1.0 → lambda_repl-1.2.1}/LICENSE +0 -0
  25. {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/__main__.py +0 -0
  26. {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/aliases.py +0 -0
  27. {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/main.py +0 -0
  28. {lambda_repl-1.1.0 → lambda_repl-1.2.1/src}/lambda_repl/py.typed +0 -0
@@ -0,0 +1,3 @@
1
+ [flake8]
2
+ max-line-length = 100
3
+ extend-ignore = E221,E501
@@ -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
1
+ Metadata-Version: 2.4
2
2
  Name: lambda_repl
3
- Version: 1.1.0
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
+ [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)
21
24
  ![Tests](https://github.com/Deric-W/lambda_repl/actions/workflows/Tests.yaml/badge.svg)
22
25
  [![codecov](https://codecov.io/gh/Deric-W/lambda_repl/branch/main/graph/badge.svg?token=SU3982mC17)](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) for the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus).
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` package are required to use this package.
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
+ [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch)
3
4
  ![Tests](https://github.com/Deric-W/lambda_repl/actions/workflows/Tests.yaml/badge.svg)
4
5
  [![codecov](https://codecov.io/gh/Deric-W/lambda_repl/branch/main/graph/badge.svg?token=SU3982mC17)](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) for the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus).
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` package are required to use this package.
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.0"
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 ~= 2.0",
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 = ["setuptools >= 61.0.0"]
38
- build-backend = "setuptools.build_meta"
37
+ requires = ["hatchling"]
38
+ build-backend = "hatchling.build"
39
39
 
40
- [tool.setuptools.package-data]
41
- lambda_repl = [
42
- "py.typed",
43
- "grammar.lark"
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.0"
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, Sequence
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: # type: ignore
56
- return self.VARIABLE(name) # type: ignore
57
- case Token() as token: # type: ignore
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
- def application(self, children: Sequence[Term[str]]) -> Application[str]:
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.with_arguments(children[0], children[1:])
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
- ![Tests](https://github.com/Deric-W/lambda_repl/actions/workflows/Tests.yaml/badge.svg)
22
- [![codecov](https://codecov.io/gh/Deric-W/lambda_repl/branch/main/graph/badge.svg?token=SU3982mC17)](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,2 +0,0 @@
1
- [console_scripts]
2
- lambda-repl = lambda_repl.main:main_cli
@@ -1,2 +0,0 @@
1
- lambda-calculus~=2.0
2
- lark~=1.0
@@ -1 +0,0 @@
1
- lambda_repl
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
File without changes