Compare commits

...

23 Commits

Author SHA1 Message Date
5bff77e4a7 Merge branch 'master' of https://github.com/rm-dr/lamb 2023-11-27 14:23:00 -08:00
8c8ea69890 Update README.md 2023-11-27 14:21:13 -08:00
6d14333e52 Update README.md 2023-11-27 14:19:24 -08:00
ab9057148a Merge branch 'master' of ssh://git.betalupi.com:33/Mark/lamb 2023-10-17 18:37:38 -07:00
c07edf1b50 Updated README.md 2023-10-16 21:17:27 -07:00
1bb0b91e20 Fixed a link in README 2023-10-16 15:31:49 -07:00
b20604de5c Fixed a path 2023-10-10 22:08:07 -07:00
d2f8b1c8fb Updated version 2023-04-02 20:54:26 -07:00
3022c2ffc0 Removed type hints to support older python versions 2023-04-02 20:52:59 -07:00
acbc247e10 Merge branch 'master' of ssh://git.betalupi.com:33/Mark/lamb 2023-04-02 07:56:55 -07:00
907d2d9e79 Added links to readme 2023-04-02 07:55:37 -07:00
73f4c60c06 Updated install instructions 2023-03-30 20:29:29 -07:00
fe7e6fca13 Added social media banner 2023-01-09 17:36:49 -08:00
866cb64485 Updated links 2023-01-09 17:15:52 -08:00
09a389857a Hide time when reducing by steps 2022-11-12 19:31:12 -08:00
da997b80c7 Added demo script 2022-11-12 19:15:08 -08:00
1b951813f4 Fixed command parser 2022-11-12 19:14:58 -08:00
12d6176f63 Updated README 2022-11-12 18:56:29 -08:00
787dbd9091 Added vhs tape 2022-11-12 18:16:35 -08:00
67b2332e1c Removed screenshot 2022-11-12 18:16:23 -08:00
115b7289e7 Updated README 2022-11-12 18:07:04 -08:00
69bf43f295 Updated README 2022-11-12 18:02:19 -08:00
495c947441 More reliable ctrl-c 2022-11-12 17:47:41 -08:00
12 changed files with 317 additions and 87 deletions

8
.gitignore vendored
View File

@ -1,7 +1,13 @@
# Python dev files
venv venv
__pycache__ __pycache__
# Python build files
*.egg-info *.egg-info
*.spec *.spec
build build
dist dist
# Misc
*.gif
misc/secrets.sh

View File

@ -8,6 +8,7 @@
"mdel", "mdel",
"onefile", "onefile",
"Packrat", "Packrat",
"printables",
"pyparsing", "pyparsing",
"rlimit", "rlimit",
"runstatus", "runstatus",

View File

@ -1,15 +1,29 @@
# Lamb: A Lambda Calculus Engine # 🐑 Lamb: A Lambda Calculus Engine
If you're reading this on PyPi, go [here](https://git.betalupi.com/Mark/lamb). ![Lamb Demo](https://github.com/rm-dr/lamb/assets/96270320/d518e344-e7c8-47ed-89c4-7ce273bf4e2d)
![Lamb screenshot](./misc/screenshot.png)
## Installation
### Method 1: PyPi [here](https://pypi.org/project/lamb-engine) ## :brain: What is lambda calculus?
1. `pip install lamb-engine`
2. `lamb` - [video 1](https://www.youtube.com/watch?v=3VQ382QG-y4): Introduction and boolean logic. The first few minutes are a bit confusing, but it starts to make sense at about [`6:50`](https://youtu.be/3VQ382QG-y4?t=400)
- [video 2](https://www.youtube.com/watch?v=pAnLQ9jwN-E): Continuation of video 1. Features combinators and numerals.
- [blog](https://www.driverlesscrocodile.com/technology/lambda-calculus-for-people-a-step-behind-me-1): Another introduction. Moves slower than the two videos above and doesn't assume CS knowledge. Four-part series.
- [handout](https://static.betalupi.com/ormc/Advanced/Lambda%20Calculus.pdf): A handout I've written on lambda calculus.
## :package: Installation
### Method 1: [PyPi](https://pypi.org/project/lamb-engine)
1. *(Optional but recommended)* make and enter a [venv](https://docs.python.org/3/library/venv.html)
- **On Windows, run the following in cmd or powershell:**
- `cd Desktop`
- `python -m venv lamb`
- `.\Scripts\activate`
2. `pip install lamb-engine`
3. `lamb`
### Method 2: Git ### Method 2: Git
1. Clone this repository. 1. Clone this repository.
@ -20,10 +34,10 @@ If you're reading this on PyPi, go [here](https://git.betalupi.com/Mark/lamb).
------------------------------------------------- -------------------------------------------------
## Usage ## 📖 Usage
Type lambda expressions into the prompt, and Lamb will evaluate them. \ Type expressions into the prompt, and Lamb will evaluate them. \
Use your `\` (backslash) key to type a `λ`. \ Use your `\` (backslash) key to type a `λ`. \
To define macros, use `=`. For example, To define macros, use `=`. For example,
``` ```
@ -62,7 +76,7 @@ Have fun!
------------------------------------------------- -------------------------------------------------
## Commands ## :card_file_box: Commands
Lamb understands many commands. Prefix them with a `:` in the prompt. Lamb understands many commands. Prefix them with a `:` in the prompt.
@ -99,8 +113,7 @@ The lines in a file look exactly the same as regular entries in the prompt, but
- Cleanup warnings - Cleanup warnings
- Truncate long expressions in warnings - Truncate long expressions in warnings
- Loop detection - Loop detection
- α-equivalence check
- Unchurch command: make church numerals human-readable - Unchurch command: make church numerals human-readable
- Better Syntax highlighting - Better syntax highlighting
- Complete file names and commands - Tab-complete file names and commands
- Tests - Load default macros without manually downloading `macros.lamb` (via `requests`, maybe?)

View File

@ -77,8 +77,8 @@ class Node:
self.parent_side: Direction = None # type: ignore self.parent_side: Direction = None # type: ignore
# Left and right nodes, None if empty # Left and right nodes, None if empty
self._left: Node | None = None self._left = None
self._right: Node | None = None self._right = None
# The runner this node is attached to. # The runner this node is attached to.
# Set by Node.set_runner() # Set by Node.set_runner()
@ -341,7 +341,7 @@ class Bound(EndNode):
# The name of the macro this bound came from. # The name of the macro this bound came from.
# Always equal to self.name, unless the macro # Always equal to self.name, unless the macro
# this came from had a subscript. # this came from had a subscript.
self.macro_name: str | None = macro_name self.macro_name = macro_name
if forced_id is None: if forced_id is None:
self.identifier = bound_counter self.identifier = bound_counter
@ -381,9 +381,9 @@ class Func(Node):
Func.from_parse(result) Func.from_parse(result)
) )
def __init__(self, input: Macro | Bound, output: Node, *, runner = None) -> None: def __init__(self, input, output: Node, *, runner = None) -> None:
super().__init__() super().__init__()
self.input: Macro | Bound = input self.input = input
self.left: Node = output self.left: Node = output
self.right: None = None self.right: None = None
self.runner = runner # type: ignore self.runner = runner # type: ignore

View File

@ -56,7 +56,7 @@ class LambdaParser:
(self.lp + self.pp_history + self.rp) (self.lp + self.pp_history + self.rp)
) )
self.pp_command = pp.Suppress(":") + pp.Word(pp.alphas + "_") + pp.Word(pp.alphas + pp.nums + "_.")[0, ...] self.pp_command = pp.Suppress(":") + pp.Word(pp.alphas + "_") + pp.Word(pp.printables)[0, ...]
self.pp_all = ( self.pp_all = (

View File

@ -14,7 +14,7 @@ help_texts = {}
def lamb_command( def lamb_command(
*, *,
command_name: str | None = None, command_name = None,
help_text: str help_text: str
): ):
""" """

View File

@ -46,7 +46,7 @@ class Runner:
# Maximum amount of reductions. # Maximum amount of reductions.
# If None, no maximum is enforced. # If None, no maximum is enforced.
# Must be at least 1. # Must be at least 1.
self.reduction_limit: int | None = 1_000_000 self.reduction_limit = 1_000_000
# Ensure bound variables are unique. # Ensure bound variables are unique.
# This is automatically incremented whenever we make # This is automatically incremented whenever we make
@ -74,7 +74,7 @@ class Runner:
message = self.prompt_message message = self.prompt_message
) )
def parse(self, line) -> tuple[lamb_engine.nodes.Root | MacroDef | Command, list]: def parse(self, line): # -> tuple[lamb_engine.nodes.Root | MacroDef | Command, list]
e = self.parser.parse_line(line) e = self.parser.parse_line(line)
w = [] w = []
@ -127,53 +127,55 @@ class Runner:
skip_to_end = False skip_to_end = False
while ( try:
( while (
(self.reduction_limit is None) or (
(k < self.reduction_limit) (self.reduction_limit is None) or
) and not only_macro (k < self.reduction_limit)
): ) and not only_macro
# Show reduction count
if (
( (k >= self.iter_update) and (k % self.iter_update == 0) )
and not (self.step_reduction and not skip_to_end)
): ):
print(f" Reducing... {k:,}", end = "\r")
try: # Show reduction count
if (
( (k >= self.iter_update) and (k % self.iter_update == 0) )
and not (self.step_reduction and not skip_to_end)
):
print(f" Reducing... {k:,}", end = "\r")
# Reduce
red_type, node = lamb_engine.nodes.reduce(node) red_type, node = lamb_engine.nodes.reduce(node)
except KeyboardInterrupt:
stop_reason = StopReason.INTERRUPT
break
# If we can't reduce this expression anymore, # If we can't reduce this expression anymore,
# it's in beta-normal form. # it's in beta-normal form.
if red_type == lamb_engine.nodes.ReductionType.NOTHING: if red_type == lamb_engine.nodes.ReductionType.NOTHING:
stop_reason = StopReason.BETA_NORMAL stop_reason = StopReason.BETA_NORMAL
break break
# Count reductions # Count reductions
k += 1 k += 1
if red_type == lamb_engine.nodes.ReductionType.FUNCTION_APPLY: if red_type == lamb_engine.nodes.ReductionType.FUNCTION_APPLY:
macro_expansions += 1 macro_expansions += 1
# Pause after step if necessary # Pause after step if necessary
if self.step_reduction and not skip_to_end: if self.step_reduction and not skip_to_end:
try: try:
s = prompt( s = prompt(
message = FormattedText([ message = FormattedText([
("class:prompt", lamb_engine.nodes.reduction_text[red_type]), ("class:prompt", lamb_engine.nodes.reduction_text[red_type]),
("class:prompt", f":{k:03} ") ("class:prompt", f":{k:03} ")
] + lamb_engine.utils.lex_str(str(node))), ] + lamb_engine.utils.lex_str(str(node))),
style = lamb_engine.utils.style, style = lamb_engine.utils.style,
key_bindings = step_bindings key_bindings = step_bindings
) )
except KeyboardInterrupt or EOFError: except KeyboardInterrupt or EOFError:
skip_to_end = True skip_to_end = True
printf(FormattedText([ printf(FormattedText([
("class:warn", "Skipping to end."), ("class:warn", "Skipping to end."),
]), style = lamb_engine.utils.style) ]), style = lamb_engine.utils.style)
# Gracefully catch keyboard interrupts
except KeyboardInterrupt:
stop_reason = StopReason.INTERRUPT
# Print a space between step messages # Print a space between step messages
if self.step_reduction: if self.step_reduction:
@ -194,24 +196,31 @@ class Runner:
] ]
else: else:
if not self.step_reduction:
out_text += [
("class:ok", f"Runtime: "),
("class:text", f"{time.time() - start_time:.03f} seconds"),
("class:text", "\n")
]
out_text += [ out_text += [
("class:ok", f"Runtime: "), ("class:ok", f"Exit reason: "),
("class:text", f"{time.time() - start_time:.03f} seconds"),
("class:ok", f"\nExit reason: "),
stop_reason.value, stop_reason.value,
("class:text", "\n"),
("class:ok", f"\nMacro expansions: "), ("class:ok", f"Macro expansions: "),
("class:text", f"{macro_expansions:,}"), ("class:text", f"{macro_expansions:,}"),
("class:text", "\n"),
("class:ok", f"\nReductions: "), ("class:ok", f"Reductions: "),
("class:text", f"{k:,}\t"), ("class:text", f"{k:,}\t"),
("class:muted", f"(Limit: {self.reduction_limit:,})") ("class:muted", f"(Limit: {self.reduction_limit:,})")
] ]
if self.full_expansion: if self.full_expansion:
out_text += [ out_text += [
("class:ok", "\nAll macros have been expanded") ("class:text", "\n"),
("class:ok", "All macros have been expanded")
] ]
if ( if (

84
misc/demo.tape Normal file
View File

@ -0,0 +1,84 @@
# See makedemo.sh
#Output lambdemo.mp4
Output lambdemo.gif
Set FontSize 30
Set Width 2000
Set Height 1500
Set FontFamily "FantasqueSansMono NF"
Set TypingSpeed 60ms
Set Framerate 30
# Intro
Sleep 2000ms
Type "lamb"
Sleep 1000ms
Enter
Sleep 2000ms
# Demo 1: load
Type ":load ../macros.lamb"
Sleep 500ms
Enter
Sleep 2000ms
Type "NOT T"
Sleep 1000ms
Enter
Sleep 6s
Type ":clear"
Sleep 1000ms
Enter
Sleep 1500ms
# Demo 2: stepping
Type ":step"
Sleep 500ms
Enter
Sleep 1500ms
Type "NOT T"
Sleep 100ms
Enter
Sleep 1500ms
Enter
Sleep 760ms
Enter
Sleep 850ms
Enter
Sleep 650ms
Enter
Sleep 700ms
Enter
Sleep 3000ms
Type ":step"
Sleep 500ms
Enter
Sleep 6s
Type ":clear"
Sleep 1000ms
Enter
Sleep 1500ms
# Demo 3: macros
Type "M = \x.x x"
Sleep 500ms
Enter
Sleep 500ms
Type "M M"
Sleep 500ms
Enter
Sleep 3s
Ctrl+c
Sleep 1000ms
Type "Y FAC 3"
Sleep 500ms
Enter
Sleep 6s

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 30 KiB

34
misc/makedemo.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
# Should be run from the misc directory.
# Will not work with any other root.
# Create this file.
# Should define two variables:
# DAV_USER="name:password"
# DAV_URL="https://site.com/dav-path"
if [[ -f "secrets.sh" ]]; then
source secrets.sh
else
echo "Cannot run without secrets.sh"
exit
fi
# Activate venv if not in venv
if [[ "$VIRTUAL_ENV" == "" ]]; then
source ../venv/bin/activate
fi
# Make sure our venv is running the latest
# version of lamb.
pip install --editable ..
# Make gif
vhs < demo.tape
# Upload
curl \
--user $DAV_USER \
--url $DAV_URL \
--upload-file "lambdemo.gif"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

View File

@ -15,7 +15,7 @@ lamb = "lamb_engine:main"
[project] [project]
name = "lamb_engine" name = "lamb_engine"
description = "A lambda calculus engine" description = "A lambda calculus engine"
version = "1.1.6" version = "1.1.9"
dependencies = [ dependencies = [
"prompt-toolkit==3.0.31", "prompt-toolkit==3.0.31",
"pyparsing==3.0.9" "pyparsing==3.0.9"