Skip to content

Commit ea03514

Browse files
committed
Add Lean bf
1 parent 048033a commit ea03514

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

brainfuck/lean/Main.lean

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import Std.Internal.UV.TCP
2+
import Std.Internal
3+
open Std.Internal.UV.TCP
4+
open Std.Internal.IO
5+
6+
abbrev N := Nat -- Element type abstracted out, in case `UInt32` is ever faster than `Nat`
7+
8+
inductive Op where | inc | dec | left | right | print | loop (ops : Array Op)
9+
structure Tape where (content : Array N) (pos : Fin content.size)
10+
11+
@[inline] def Tape.current : Tape → N | ⟨bs, i⟩ => bs[i]
12+
13+
@[inline] private def change (δ : N → N): Tape → Tape
14+
| ⟨bs, i⟩ => ⟨bs.set! i (δ bs[i]), ⟨i.val, by simp⟩⟩
15+
@[inline] def inc := change (· + 1)
16+
@[inline] def dec := change (· - 1)
17+
18+
@[inline] def left : Tape → Tape
19+
| ⟨bs, i⟩ => ⟨bs, ⟨i.val - 1, by omega⟩⟩
20+
21+
@[inline] def right : Tape → Tape
22+
| ⟨bs, i⟩ => let i' := i.val + 1
23+
if i' < bs.size then bs else bs.push 0, ⟨i', by grind⟩⟩
24+
25+
partial
26+
def parse (cs : List Char) : Array Op :=
27+
let rec loop : List Char → Array Op → List Char × Array Op
28+
| [], acc => ⟨[], acc⟩
29+
| c :: cs, acc => match c with
30+
| '+' => loop cs (acc.push .inc)
31+
| '-' => loop cs (acc.push .dec)
32+
| '>' => loop cs (acc.push .right)
33+
| '<' => loop cs (acc.push .left)
34+
| '.' => loop cs (acc.push .print)
35+
| '[' => let ⟨cs', body⟩ := loop cs #[]
36+
loop cs' (acc.push (.loop body))
37+
| ']' => ⟨cs, acc⟩
38+
| _ => loop cs acc
39+
loop cs #[] |>.snd
40+
41+
/-- Generic execution environment, with operator for printing character parameterized -/
42+
class Ctx (m : TypeType) extends Monad m where
43+
write : N → m Unit
44+
45+
/-- IO as execution environment for benchmarking, flushing each character -/
46+
instance : Ctx IO where
47+
write n := do let stdout ← IO.getStdout
48+
stdout.putStr (String.ofList [Char.ofNat n])
49+
stdout.flush
50+
51+
def checkSum : N × N → N
52+
| ⟨s₁, s₂⟩ => s₂ * 256 + s₁
53+
54+
def accCheckSum : N → N × N → N × N
55+
| n, ⟨s₁, s₂⟩ => let s₁' := (s₁ + n).mod 255
56+
let s₂' := (s₁' + s₂).mod 255
57+
⟨s₁', s₂'⟩
58+
59+
/-- Checksuming execution environment accumulates the state being the checksum -/
60+
instance : Ctx (StateM (N × N)) where
61+
write := modify ∘ accCheckSum
62+
63+
partial
64+
def run [Ctx m] (ops : Array Op) (i : Nat) (t : Tape) : m Tape :=
65+
if _ : i < ops.size
66+
then match ops[i] with
67+
| .inc => run ops (i + 1) (inc t)
68+
| .dec => run ops (i + 1) (dec t)
69+
| .left => run ops (i + 1) (left t)
70+
| .right => run ops (i + 1) (right t)
71+
| .print => do Ctx.write t.current
72+
run ops (i + 1) t
73+
| .loop body =>
74+
let rec loop (t : Tape) : m Tape :=
75+
match t.current with
76+
| 0 => run ops (i + 1) t
77+
| _ => do loop (← run body 0 t)
78+
loop t
79+
else pure t
80+
81+
def runFresh [Ctx m] (ops : Array Op) : m Tape := run ops 0 ⟨#[0], ⟨0, by simp⟩⟩
82+
83+
def runQuiet (op : Array Op) : N × N := runFresh (m := StateM (N × N)) op |>.run ⟨0, 0⟩ |>.snd
84+
85+
-- Test case verified at compile time
86+
def actual :=
87+
let src := "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."
88+
let ops := parse src.toList
89+
runQuiet ops
90+
def expected := "Hello World!\n".foldl (λ cs c => accCheckSum c.val.toNat cs) ⟨0, 0
91+
#guard actual = expected
92+
93+
def notify (msg : String) : IO Unit := do
94+
let skt ← Socket.new
95+
let _ := (← skt.connect (.v4 ⟨⟨⟨#[127, 0, 0, 1], rfl⟩⟩, 9001⟩)) |>.result? |>.get
96+
let _ := (← skt.send #[msg.toUTF8]) |>.result? |>.get
97+
let _ := (← skt.shutdown) |>.result? |>.get
98+
99+
def main : List String → IO UInt32
100+
| [fn] => do
101+
let src ← IO.FS.readFile fn
102+
let quiet ← IO.getEnv "QUIET"
103+
notify s!"Lean\t {(← Process.getId).toUInt64}"
104+
let ops := parse src.toList
105+
match quiet with
106+
| .some _ => do
107+
let cs := runQuiet ops
108+
notify "stop"
109+
IO.println s!"Output checksum: {checkSum cs}"
110+
| .none => do
111+
let _ ← runFresh ops
112+
notify "stop"
113+
return 0
114+
| _ => do
115+
IO.println "Exactly one argument expected for the benchmark file"
116+
return 1

brainfuck/lean/lake-manifest.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{"version": "1.1.0",
2+
"packagesDir": ".lake/packages",
3+
"packages": [],
4+
"name": "bf",
5+
"lakeDir": ".lake"}

brainfuck/lean/lakefile.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name = "bf"
2+
version = "0.1.0"
3+
defaultTargets = ["bf"]
4+
5+
[[lean_exe]]
6+
name = "bf"
7+
root = "Main"

brainfuck/lean/lean-toolchain

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
leanprover/lean4:v4.26.0

0 commit comments

Comments
 (0)