Skip to content

Commit daa13c1

Browse files
authored
Merge pull request #2 from Deric-W/import
add import command
2 parents 6e9a59c + a515cc8 commit daa13c1

File tree

4 files changed

+71
-5
lines changed

4 files changed

+71
-5
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
![Tests](https://github.com/Deric-W/lambda_repl/actions/workflows/Tests.yaml/badge.svg)
44
[![codecov](https://codecov.io/gh/Deric-W/lambda_repl/branch/main/graph/badge.svg?token=SU3982mC17)](https://codecov.io/gh/Deric-W/lambda_repl)
55

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).
6+
The `lambda_repl` package contains a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)
7+
for the [lambda calculus](https://en.wikipedia.org/wiki/Lambda_calculus).
78

89
To use it, execute `lambda-repl` or `python3 -m lambda_repl` and enter commands.
910

1011
## Requirements
1112

12-
Python >= 3.10 and the `lambda_calculus` package are required to use this package.
13+
Python >= 3.10 and the packages [`lambda_calculus`](https://github.com/Deric-W/lambda_calculus)
14+
and [`lark`](https://github.com/lark-parser/lark) are required to use this package.
1315

1416
## Installation
1517

@@ -24,12 +26,14 @@ python3 -m lambda_repl
2426
Welcome to the the Lambda REPL, type 'help' for help
2527
λ alias I = \x.x
2628
λ alias K = λx.λy.x
29+
λ import SUCC = lambda_calculus.terms.arithmetic.SUCCESSOR
2730
λ aliases
2831
I = (λx.x)
2932
K = (λx.(λy.x))
33+
SUCC = (λn.(λf.(λx.(f ((n f) x)))))
3034
λ trace K a b
3135
β ((λy.a) b)
3236
β a
3337
λ exit
3438
Exiting REPL...
35-
```
39+
```

lambda_repl/__init__.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import annotations
66
from cmd import Cmd
7+
from importlib import import_module
78
from typing import Any
89
from lambda_calculus.terms import Term
910
from lambda_calculus.visitors.normalisation import (
@@ -14,7 +15,7 @@
1415
from .parsing import LambdaTransformer
1516
from .aliases import Aliases
1617

17-
__version__ = "1.1.0"
18+
__version__ = "1.2.0"
1819
__author__ = "Eric Niklas Wolf"
1920
__email__ = "eric_niklas.wolf@mailbox.tu-dresden.de"
2021
__all__ = (
@@ -51,6 +52,19 @@ def parse_term(self, term: str) -> Term[str] | None:
5152
self.stdout.write(error.get_context(term))
5253
return None
5354

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:
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+
5468
def emptyline(self) -> bool:
5569
"""ignore empty lines"""
5670
return False
@@ -95,6 +109,17 @@ def do_alias(self, arg: str) -> bool:
95109
self.stdout.write("invalid Command: missing alias value\n")
96110
return False
97111

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+
98123
def do_aliases(self, _: object) -> bool:
99124
"""list defined aliases"""
100125
for alias, term in self.aliases.items():

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "lambda_repl"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
description = "REPL for the lambda calculus"
55
requires-python = ">=3.10"
66
keywords = []

tests/test_repl.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from io import StringIO
66
from unittest import TestCase
77
from lambda_calculus.terms import Variable
8+
from lambda_calculus.terms.arithmetic import SUCCESSOR
89
from lambda_calculus.visitors.substitution.renaming import CountingSubstitution
910
from lambda_calculus.visitors.normalisation import BetaNormalisingVisitor
1011
from lambda_repl import LambdaREPL
@@ -93,6 +94,42 @@ def test_no_alias_value(self) -> None:
9394
self.assertTrue(self.stdout.getvalue().startswith("invalid Command: "))
9495
self.assertTrue(self.stdout.getvalue().endswith("\n"))
9596

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+
96133
def test_aliases(self) -> None:
97134
"""test listing aliases"""
98135
self.assertFalse(self.repl.onecmd("alias x = 1"))

0 commit comments

Comments
 (0)