File cleanup

master
Mark 2022-11-07 19:58:11 -08:00
parent ac08c5be59
commit 231c873b1c
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
7 changed files with 156 additions and 51 deletions

View File

@ -19,6 +19,7 @@
## Usage ## Usage
Type lambda expressions into the prompt, and Lamb will evaluate them. \ Type lambda 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,
@ -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. \ 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. \ 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`. Numbers will automatically be converted to Church numerals. For example, the following line will reduce to `T`.
``` ```
==> 3 NOT F ==> 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 ## 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 `:help` Prints a help message
@ -60,7 +69,9 @@ Lamb comes with a few commands. Prefix them with a `:`
`:clearmacros` Delete all macros `:clearmacros` Delete all macros
`:save [filename]` \ `: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.
------------------------------------------------- -------------------------------------------------

View File

@ -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 import PromptSession
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit import print_formatted_text as printf from prompt_toolkit import print_formatted_text as printf
from prompt_toolkit.formatted_text import FormattedText from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.formatted_text import to_plain_text 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 from pyparsing import exceptions as ppx
import lamb 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() lamb.utils.show_greeting()
# Replace "\" with pretty "λ"s
bindings = KeyBindings()
@bindings.add("\\")
def _(event):
event.current_buffer.insert_text("λ")
r = lamb.Runner( r = lamb.Runner(
prompt_session = PromptSession( prompt_session = PromptSession(
style = lamb.utils.style, style = lamb.utils.style,
lexer = LambdaLexer(), lexer = lamb.utils.LambdaLexer(),
key_bindings = bindings key_bindings = lamb.utils.bindings
), ),
prompt_message = FormattedText([ prompt_message = FormattedText([
("class:prompt", "==> ") ("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: while True:
try: try:
i = r.prompt() i = r.prompt()

View File

@ -101,7 +101,14 @@ def cmd_load(command, runner):
lines = [x.strip() for x in f.readlines()] lines = [x.strip() for x in f.readlines()]
for i in range(len(lines)): 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: try:
x = runner.parse(l)[0] x = runner.parse(l)[0]
except ppx.ParseException as e: except ppx.ParseException as e:

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.alphas + pp.nums + "_.")[0, ...]
self.pp_all = ( self.pp_all = (

View File

@ -1,5 +1,7 @@
from prompt_toolkit.styles import Style from prompt_toolkit.styles import Style
from prompt_toolkit.formatted_text import HTML 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 prompt_toolkit import print_formatted_text as printf
from importlib.metadata import version from importlib.metadata import version
@ -14,6 +16,11 @@ style = Style.from_dict({ # type: ignore
"code": "#AAAAAA italic", "code": "#AAAAAA italic",
"muted": "#AAAAAA", "muted": "#AAAAAA",
# Syntax highlighting colors
"syn_cmd": "#FFFFFF italic",
"syn_lambda": "#AAAAAA",
"syn_paren": "#AAAAAA",
# Command formatting # Command formatting
# cmd_h: section titles # cmd_h: section titles
# cmd_key: keyboard keys, usually one character # 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(): def show_greeting():
# | _.._ _.|_ # | _.._ _.|_
# |_(_|| | ||_) # |_(_|| | ||_)

77
macros.lamb Normal file
View File

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

View File

@ -14,7 +14,7 @@ description = "A lambda calculus engine"
# #
# Patch release: # Patch release:
# Small, compatible fixes. # Small, compatible fixes.
version = "0.1.2" version = "0.1.3"
dependencies = [ dependencies = [
"prompt-toolkit==3.0.31", "prompt-toolkit==3.0.31",