Skip to content

Commit

Permalink
Merge pull request #2 from Deric-W/import
Browse files Browse the repository at this point in the history
add import command
  • Loading branch information
Deric-W authored Apr 24, 2023
2 parents 6e9a59c + a515cc8 commit daa13c1
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 5 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
![Tests](https://github.com/Deric-W/lambda_repl/actions/workflows/Tests.yaml/badge.svg)
[![codecov](https://codecov.io/gh/Deric-W/lambda_repl/branch/main/graph/badge.svg?token=SU3982mC17)](https://codecov.io/gh/Deric-W/lambda_repl)

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).
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).

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

## Requirements

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

## Installation

Expand All @@ -24,12 +26,14 @@ python3 -m lambda_repl
Welcome to the the Lambda REPL, type 'help' for help
λ alias I = \x.x
λ alias K = λx.λy.x
λ import SUCC = lambda_calculus.terms.arithmetic.SUCCESSOR
λ aliases
I = (λx.x)
K = (λx.(λy.x))
SUCC = (λn.(λf.(λx.(f ((n f) x)))))
λ trace K a b
β ((λy.a) b)
β a
λ exit
Exiting REPL...
```
```
27 changes: 26 additions & 1 deletion lambda_repl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from __future__ import annotations
from cmd import Cmd
from importlib import import_module
from typing import Any
from lambda_calculus.terms import Term
from lambda_calculus.visitors.normalisation import (
Expand All @@ -14,7 +15,7 @@
from .parsing import LambdaTransformer
from .aliases import Aliases

__version__ = "1.1.0"
__version__ = "1.2.0"
__author__ = "Eric Niklas Wolf"
__email__ = "[email protected]"
__all__ = (
Expand Down Expand Up @@ -51,6 +52,19 @@ def parse_term(self, term: str) -> Term[str] | None:
self.stdout.write(error.get_context(term))
return None

def import_term(self, location: str) -> Term[str] | None:
"""import a term and handle error display"""
module, _, name = location.strip().rpartition(".")
try:
term = getattr(import_module(module), name)
except Exception as error:
self.stdout.write(f"Error while importing: {error}\n")
return None
if not isinstance(term, Term):
self.stdout.write(f"Error: object {term} is not a lambda term\n")
return None
return term

def emptyline(self) -> bool:
"""ignore empty lines"""
return False
Expand Down Expand Up @@ -95,6 +109,17 @@ def do_alias(self, arg: str) -> bool:
self.stdout.write("invalid Command: missing alias value\n")
return False

def do_import(self, arg: str) -> bool:
"""import an alias from a module with name = module.name"""
match arg.partition("="):
case (alias, "=", location):
term = self.import_term(location)
if term is not None:
self.aliases[alias.strip()] = term
case _:
self.stdout.write("invalid Command: missing import location\n")
return False

def do_aliases(self, _: object) -> bool:
"""list defined aliases"""
for alias, term in self.aliases.items():
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lambda_repl"
version = "1.1.0"
version = "1.2.0"
description = "REPL for the lambda calculus"
requires-python = ">=3.10"
keywords = []
Expand Down
37 changes: 37 additions & 0 deletions tests/test_repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from io import StringIO
from unittest import TestCase
from lambda_calculus.terms import Variable
from lambda_calculus.terms.arithmetic import SUCCESSOR
from lambda_calculus.visitors.substitution.renaming import CountingSubstitution
from lambda_calculus.visitors.normalisation import BetaNormalisingVisitor
from lambda_repl import LambdaREPL
Expand Down Expand Up @@ -93,6 +94,42 @@ def test_no_alias_value(self) -> None:
self.assertTrue(self.stdout.getvalue().startswith("invalid Command: "))
self.assertTrue(self.stdout.getvalue().endswith("\n"))

def test_import(self) -> None:
"""test importing aliases"""
self.assertFalse(self.repl.onecmd(
"import SUCC = lambda_calculus.terms.arithmetic.SUCCESSOR"
))
self.assertEqual(
self.repl.aliases,
{
"SUCC": SUCCESSOR
}
)

def test_invalid_import(self) -> None:
"""test handling of invalid imports"""
for location in (
"lambda_calculus.terms.arithmetic.SUCCESSORX",
"lambda_calculus.terms.arithmeticX.SUCCESSOR"
):
self.assertFalse(self.repl.onecmd(f"import SUCC = {location}"))
self.assertEqual(self.repl.aliases, {})
self.assertTrue(self.stdout.getvalue().startswith("Error while importing: "))
self.assertTrue(self.stdout.getvalue().endswith("\n"))
self.stdout.seek(0)
self.stdout.truncate(0)
self.assertFalse(self.repl.onecmd("import SUCC = lambda_calculus.terms.arithmetic.number"))
self.assertEqual(self.repl.aliases, {})
self.assertTrue(self.stdout.getvalue().startswith("Error"))
self.assertTrue(self.stdout.getvalue().endswith("\n"))

def test_no_import_value(self) -> None:
"""test handling missing import values"""
self.assertFalse(self.repl.onecmd("import a"))
self.assertEqual(self.repl.aliases, {})
self.assertTrue(self.stdout.getvalue().startswith("invalid Command: "))
self.assertTrue(self.stdout.getvalue().endswith("\n"))

def test_aliases(self) -> None:
"""test listing aliases"""
self.assertFalse(self.repl.onecmd("alias x = 1"))
Expand Down

0 comments on commit daa13c1

Please sign in to comment.