File cleanup
parent
ac08c5be59
commit
231c873b1c
21
README.md
21
README.md
|
@ -19,6 +19,7 @@
|
|||
|
||||
## Usage
|
||||
|
||||
|
||||
Type lambda expressions into the prompt, and Lamb will evaluate them. \
|
||||
Use your `\` (backslash) key to type a `λ`. \
|
||||
To define macros, use `=`. For example,
|
||||
|
@ -30,22 +31,30 @@ To define macros, use `=`. For example,
|
|||
|
||||
Note that there are spaces in `λa.a F T`. With no spaces, `aFT` will be parsed as one variable. \
|
||||
Lambda functions can only take single-letter, lowercase arguments. `λA.A` is not valid syntax. \
|
||||
Unbound variables (upper and lower case) that aren't macros will become free variables. Free variables will be shown with a `'`, like `a'`.
|
||||
Free variables will be shown with a `'`, like `a'`.
|
||||
|
||||
Be careful, macros are case-sensitive. If you define a macro `MAC` and accidentally write `mac` in the prompt, `mac` will become a free variable.
|
||||
Macros are case-sensitive. If you define a macro `MAC` and accidentally write `mac` in the prompt, `mac` will become a free variable.
|
||||
|
||||
Numbers will automatically be converted to Church numerals. For example, the following line will reduce to `T`.
|
||||
```
|
||||
==> 3 NOT F
|
||||
```
|
||||
|
||||
If an expression takes too long to evaluate, you may interrupt reduction with `Ctrl-C`.
|
||||
If an expression takes too long to evaluate, you may interrupt reduction with `Ctrl-C`. \
|
||||
Exit the prompt with `Ctrl-C` or `Ctrl-D`.
|
||||
|
||||
There are many useful macros in [macros.lamb](./macros.lamb). Load them with the `:load` command:
|
||||
```
|
||||
==> :load macros.lamb
|
||||
```
|
||||
|
||||
Have fun!
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
## Commands
|
||||
|
||||
Lamb comes with a few commands. Prefix them with a `:`
|
||||
Lamb understands many commands. Prefix them with a `:` in the prompt.
|
||||
|
||||
`:help` Prints a help message
|
||||
|
||||
|
@ -60,7 +69,9 @@ Lamb comes with a few commands. Prefix them with a `:`
|
|||
`:clearmacros` Delete all macros
|
||||
|
||||
`:save [filename]` \
|
||||
`:load [filename]` Save or load the current environment to a file. The lines in a file look exactly the same as regular entries in the prompt, but must only contain macro definitions.
|
||||
`:load [filename]` \
|
||||
Save or load macros from a file.
|
||||
The lines in a file look exactly the same as regular entries in the prompt, but can only contain macro definitions. See [macros.lamb](./macros.lamb) for an example.
|
||||
|
||||
-------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,66 +1,29 @@
|
|||
if __name__ != "__main__":
|
||||
raise ImportError("lamb.__main__ should never be imported. Run it directly.")
|
||||
|
||||
from prompt_toolkit import PromptSession
|
||||
from prompt_toolkit.completion import WordCompleter
|
||||
from prompt_toolkit import print_formatted_text as printf
|
||||
from prompt_toolkit.formatted_text import FormattedText
|
||||
from prompt_toolkit.formatted_text import to_plain_text
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.lexers import Lexer
|
||||
from pyparsing import exceptions as ppx
|
||||
|
||||
import lamb
|
||||
|
||||
|
||||
# Simple lexer for highlighting.
|
||||
# Improve this later.
|
||||
class LambdaLexer(Lexer):
|
||||
def lex_document(self, document):
|
||||
def inner(line_no):
|
||||
return [("class:text", str(document.lines[line_no]))]
|
||||
return inner
|
||||
|
||||
|
||||
lamb.utils.show_greeting()
|
||||
|
||||
|
||||
# Replace "\" with pretty "λ"s
|
||||
bindings = KeyBindings()
|
||||
@bindings.add("\\")
|
||||
def _(event):
|
||||
event.current_buffer.insert_text("λ")
|
||||
|
||||
|
||||
r = lamb.Runner(
|
||||
prompt_session = PromptSession(
|
||||
style = lamb.utils.style,
|
||||
lexer = LambdaLexer(),
|
||||
key_bindings = bindings
|
||||
lexer = lamb.utils.LambdaLexer(),
|
||||
key_bindings = lamb.utils.bindings
|
||||
),
|
||||
prompt_message = FormattedText([
|
||||
("class:prompt", "==> ")
|
||||
])
|
||||
)
|
||||
|
||||
|
||||
r.run_lines([
|
||||
"T = λab.a",
|
||||
"F = λab.b",
|
||||
"NOT = λa.(a F T)",
|
||||
"AND = λab.(a b F)",
|
||||
"OR = λab.(a T b)",
|
||||
"XOR = λab.(a (NOT b) b)",
|
||||
"M = λx.(x x)",
|
||||
"W = M M",
|
||||
"Y = λf.( (λx.(f (x x))) (λx.(f (x x))) )",
|
||||
"PAIR = λabi.( i a b )",
|
||||
"S = λnfa.(f (n f a))",
|
||||
"Z = λn.n (λa.F) T",
|
||||
"MULT = λnmf.n (m f)",
|
||||
"H = λp.((PAIR (p F)) (S (p F)))",
|
||||
"D = λn.n H (PAIR 0 0) T",
|
||||
"FAC = λyn.(Z n)(1)(MULT n (y (D n)))"
|
||||
])
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
i = r.prompt()
|
||||
|
|
|
@ -101,7 +101,14 @@ def cmd_load(command, runner):
|
|||
lines = [x.strip() for x in f.readlines()]
|
||||
|
||||
for i in range(len(lines)):
|
||||
l = lines[i]
|
||||
l = lines[i].strip()
|
||||
|
||||
# Skip comments and empty lines
|
||||
if l.startswith("#"):
|
||||
continue
|
||||
if l == "":
|
||||
continue
|
||||
|
||||
try:
|
||||
x = runner.parse(l)[0]
|
||||
except ppx.ParseException as e:
|
||||
|
|
|
@ -56,7 +56,7 @@ class LambdaParser:
|
|||
(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.alphas + pp.nums + "_.")[0, ...]
|
||||
|
||||
|
||||
self.pp_all = (
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from prompt_toolkit.styles import Style
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
from prompt_toolkit.lexers import Lexer
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit import print_formatted_text as printf
|
||||
from importlib.metadata import version
|
||||
|
||||
|
@ -14,6 +16,11 @@ style = Style.from_dict({ # type: ignore
|
|||
"code": "#AAAAAA italic",
|
||||
"muted": "#AAAAAA",
|
||||
|
||||
# Syntax highlighting colors
|
||||
"syn_cmd": "#FFFFFF italic",
|
||||
"syn_lambda": "#AAAAAA",
|
||||
"syn_paren": "#AAAAAA",
|
||||
|
||||
# Command formatting
|
||||
# cmd_h: section titles
|
||||
# cmd_key: keyboard keys, usually one character
|
||||
|
@ -28,6 +35,46 @@ style = Style.from_dict({ # type: ignore
|
|||
})
|
||||
|
||||
|
||||
# Replace "\" with pretty "λ"s
|
||||
bindings = KeyBindings()
|
||||
@bindings.add("\\")
|
||||
def _(event):
|
||||
event.current_buffer.insert_text("λ")
|
||||
|
||||
# Simple lexer for highlighting.
|
||||
# Improve this later.
|
||||
class LambdaLexer(Lexer):
|
||||
def lex_document(self, document):
|
||||
def inner(line_no):
|
||||
out = []
|
||||
tmp_str = []
|
||||
d = str(document.lines[line_no])
|
||||
|
||||
if d.startswith(":"):
|
||||
return [
|
||||
("class:syn_cmd", d)
|
||||
]
|
||||
|
||||
for c in d:
|
||||
if c in "\\λ.":
|
||||
if len(tmp_str) != 0:
|
||||
out.append(("class:text", "".join(tmp_str)))
|
||||
out.append(("class:syn_lambda", c))
|
||||
tmp_str = []
|
||||
elif c in "()":
|
||||
if len(tmp_str) != 0:
|
||||
out.append(("class:text", "".join(tmp_str)))
|
||||
out.append(("class:syn_paren", c))
|
||||
tmp_str = []
|
||||
else:
|
||||
tmp_str.append(c)
|
||||
|
||||
if len(tmp_str) != 0:
|
||||
out.append(("class:text", "".join(tmp_str)))
|
||||
return out
|
||||
return inner
|
||||
|
||||
|
||||
def show_greeting():
|
||||
# | _.._ _.|_
|
||||
# |_(_|| | ||_)
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
# How to use exported files in lamb:
|
||||
#
|
||||
# [Syntax Highlighting]
|
||||
# Most languages' syntax highlighters will
|
||||
# highlight this code well. Set it manually
|
||||
# in your editor.
|
||||
#
|
||||
# Don't use a language for which you have a
|
||||
# linter installed, you'll get lots of errors.
|
||||
#
|
||||
# Choose a language you don't have extenstions for,
|
||||
# and a language that uses # comments.
|
||||
#
|
||||
# The following worked well in vscode:
|
||||
# - Julia
|
||||
# - Perl
|
||||
# - Coffeescript
|
||||
# - R
|
||||
|
||||
# [Writing macros]
|
||||
# If you don't have a custom keyboard layout that can
|
||||
# create λs, you may use backslashes instead.
|
||||
# (As in `T = \ab.b`)
|
||||
#
|
||||
# This file must only contain macro definitons. Commands will be ignored.
|
||||
# Statements CANNOT be split among multiple lines.
|
||||
# Comments CANNOT be on the same line as macro defintions.
|
||||
# All leading whitespace is ignored.
|
||||
|
||||
|
||||
# Misc Combinators
|
||||
M = λx.(x x)
|
||||
W = (M M)
|
||||
Y = λf.((λx.(f (x x))) (λx.(f (x x))))
|
||||
|
||||
|
||||
# Booleans
|
||||
T = λab.a
|
||||
F = λab.b
|
||||
NOT = λa.(a F T)
|
||||
AND = λab.(a b F)
|
||||
OR = λab.(a T b)
|
||||
XOR = λab.((a (NOT b)) b)
|
||||
|
||||
|
||||
# Numbers
|
||||
# PAIR: prerequisite for H.
|
||||
# Makes a two-value tuple, indexed with T and F.
|
||||
#
|
||||
# H: shift-and-add, prerequisite for D
|
||||
#
|
||||
# S: successor (adds 1)
|
||||
#
|
||||
# D: predecessor (subtracts 1)
|
||||
#
|
||||
# Z: tests if a number is zero
|
||||
# NZ: equivalent to `NOT Z`
|
||||
#
|
||||
# ADD: adds two numbers
|
||||
#
|
||||
# MULT: multiply two numbers
|
||||
#
|
||||
# FAC:
|
||||
# Recursive factorial. Call with `Y FAC <number>`
|
||||
# Don't call this with numbers bigger than 5 unless you're very patient.
|
||||
#
|
||||
# `Y FAC 6` required 867,920 reductions and took 10 minutes to run.
|
||||
|
||||
PAIR = λabi.(i a b)
|
||||
H = λp.((PAIR (p F)) (S (p F)))
|
||||
S = λnfa.(f (n f a))
|
||||
D = λn.((n H) ((PAIR 0) 0) T)
|
||||
Z = λn.(n (λa.F) T)
|
||||
NZ = λn.(n (λa.T) F)
|
||||
ADD = λmn.(m S n)
|
||||
MULT = λnmf.(n (m f))
|
||||
FAC = λyn.( (Z n)(1)((MULT n) (y (D n))) )
|
|
@ -14,7 +14,7 @@ description = "A lambda calculus engine"
|
|||
#
|
||||
# Patch release:
|
||||
# Small, compatible fixes.
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
|
||||
dependencies = [
|
||||
"prompt-toolkit==3.0.31",
|
||||
|
|
Reference in New Issue