├── tests ├── .gitignore ├── expected │ ├── query_none.json │ ├── query_string.json │ ├── query_at.json │ ├── query_2024.json │ └── query_any.json ├── query_any.json ├── query_none.json ├── query_string.json ├── query_2024.json ├── query_at.json ├── input.json ├── README.md └── run.sh ├── .gitignore ├── lean-toolchain ├── Imp ├── Stmt.lean ├── Expr.lean ├── Expr │ ├── Basic.lean │ ├── Optimize.lean │ ├── Eval.lean │ ├── Delab.lean │ └── Syntax.lean └── Stmt │ ├── Optimize.lean │ ├── Basic.lean │ ├── Delab.lean │ └── BigStep.lean ├── Filter.lean ├── lakefile.lean ├── Main.lean ├── Filter ├── Input.lean ├── List.lean ├── Array.lean └── Query.lean ├── lake-manifest.json ├── Imp.lean ├── Intro.lean ├── LICENSE └── README.md /tests/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /tests/expected/query_none.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/query_any.json: -------------------------------------------------------------------------------- 1 | "any" 2 | -------------------------------------------------------------------------------- /tests/query_none.json: -------------------------------------------------------------------------------- 1 | "none" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.lake 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /tests/query_string.json: -------------------------------------------------------------------------------- 1 | "string" 2 | -------------------------------------------------------------------------------- /tests/expected/query_string.json: -------------------------------------------------------------------------------- 1 | "world" 2 | -------------------------------------------------------------------------------- /tests/expected/query_at.json: -------------------------------------------------------------------------------- 1 | ["hello", "world"] 2 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:nightly-2024-05-23 2 | -------------------------------------------------------------------------------- /tests/query_2024.json: -------------------------------------------------------------------------------- 1 | ["object", {"year":["is", 2024]}] 2 | -------------------------------------------------------------------------------- /tests/expected/query_2024.json: -------------------------------------------------------------------------------- 1 | {"year": 2024, "name": "SSFT"} 2 | -------------------------------------------------------------------------------- /tests/query_at.json: -------------------------------------------------------------------------------- 1 | ["array", ["and", {"length": 2}, {"at": 1, "satisfies": ["is","world"]}]] 2 | -------------------------------------------------------------------------------- /tests/input.json: -------------------------------------------------------------------------------- 1 | [] 2 | ["hello"] 3 | "world" 4 | null 5 | {"name": "SSFT", "year": 2024} 6 | ["hello", "world"] 7 | ["world", "hello"] 8 | -------------------------------------------------------------------------------- /tests/expected/query_any.json: -------------------------------------------------------------------------------- 1 | [] 2 | ["hello"] 3 | "world" 4 | null 5 | {"year": 2024, "name": "SSFT"} 6 | ["hello", "world"] 7 | ["world", "hello"] 8 | -------------------------------------------------------------------------------- /Imp/Stmt.lean: -------------------------------------------------------------------------------- 1 | /- 2 | This file exists only to re-export the contents of the `Imp.Stmt` module hierarchy. 3 | -/ 4 | import Imp.Stmt.Basic 5 | import Imp.Stmt.BigStep 6 | import Imp.Stmt.Delab 7 | import Imp.Stmt.Optimize 8 | -------------------------------------------------------------------------------- /Filter.lean: -------------------------------------------------------------------------------- 1 | -- This module serves as the root of the `Filter` library. 2 | -- Import modules here that should be built as part of the library. 3 | import Filter.List 4 | import Filter.Array 5 | import Filter.Query 6 | import Filter.Input 7 | -------------------------------------------------------------------------------- /Imp/Expr.lean: -------------------------------------------------------------------------------- 1 | /- 2 | This file exists only to re-export the contents of the `Imp.Expr` module hierarchy. 3 | -/ 4 | import Imp.Expr.Basic 5 | import Imp.Expr.Delab 6 | import Imp.Expr.Eval 7 | import Imp.Expr.Optimize 8 | import Imp.Expr.Syntax 9 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | A simple test suite 2 | ------------------- 3 | 4 | This test suite applies the queries defined in `query*.json` to the input in 5 | `input.json`, and compares them against the expected output in `expected/`. 6 | 7 | Usage: 8 | 9 | ``` 10 | ./run.sh # runs all tests 11 | ./run.sh query_any.json query_2024.json # runs selective tests 12 | ./run.sh -a # accepts the current output 13 | ``` 14 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | require LeanSAT from git "https://github.com/leanprover/leansat.git"@"main" 5 | 6 | package «ssft24» { 7 | precompileModules := true 8 | -- This package uses a ginormous maximum stack size to avoid 9 | -- worrying about stack overflows during bitblasting. 10 | moreGlobalServerArgs := #["--tstack=32000"] 11 | moreLeanArgs := #["--tstack=32000"] 12 | } 13 | 14 | @[default_target] 15 | lean_lib Intro where 16 | 17 | @[default_target] 18 | lean_lib Filter where 19 | 20 | @[default_target] 21 | lean_exe jsonfilter where 22 | root := `Main 23 | 24 | @[default_target] 25 | lean_lib Imp where 26 | -------------------------------------------------------------------------------- /Main.lean: -------------------------------------------------------------------------------- 1 | import Lean.Data.Json 2 | import Filter 3 | 4 | open Lean (Json) 5 | open Filter 6 | 7 | def main : List String → IO UInt32 8 | | [q] => do 9 | let query ← 10 | match Json.parse q >>= Query.parse (ctxt := .val) with 11 | | .error e => IO.eprintln e; return 1 12 | | .ok q' => pure q' 13 | let input ← readStdin 14 | match readJsonArray input with 15 | | .error e => IO.eprintln e; return 3 16 | | .ok vals => 17 | for v in Filter.Array.filter (query.matches · = true) vals do 18 | IO.println v 19 | return 0 20 | | _ => do 21 | IO.println "Usage: jsonfilterfilter QUERY" 22 | return 2 23 | -------------------------------------------------------------------------------- /Filter/Input.lean: -------------------------------------------------------------------------------- 1 | import Lean.Data.Json 2 | 3 | namespace Filter 4 | 5 | open Lean (Json Parsec) 6 | open Lean.Parsec 7 | 8 | def readJsonArray (input : String) : Except String (Array Json) := do 9 | match jsons input.mkIterator with 10 | | .success _ v => pure v 11 | | .error _ e => throw e 12 | where 13 | jsons : Lean.Parsec (Array Json) := do 14 | ws 15 | let val ← many Json.Parser.anyCore 16 | eof 17 | pure val 18 | 19 | partial def readStdin : IO String := do 20 | let stdin ← IO.getStdin 21 | let mut str := "" 22 | let mut l ← stdin.getLine 23 | while !l.isEmpty do 24 | str := str ++ l 25 | l ← stdin.getLine 26 | pure str 27 | -------------------------------------------------------------------------------- /Imp/Expr/Basic.lean: -------------------------------------------------------------------------------- 1 | namespace Imp.Expr 2 | 3 | /-- Unary operators -/ 4 | inductive UnOp where 5 | | neg 6 | | not 7 | deriving Repr, DecidableEq 8 | 9 | /-- Binary operators -/ 10 | inductive BinOp where 11 | | plus | minus | times | div 12 | | lsh | rsh 13 | | band | bor 14 | | and | or 15 | | lt | le | eq 16 | deriving Repr, DecidableEq 17 | 18 | end Expr 19 | -- The `Expr` namespace is ended here to avoid doubly-nesting the namespace for the constructors of 20 | -- the `Expr` datatype 21 | 22 | /-- Expressions -/ 23 | inductive Expr where 24 | | const (i : BitVec 32) 25 | | var (name : String) 26 | | un (op : Expr.UnOp) (e : Expr) 27 | | bin (op : Expr.BinOp) (e1 e2 : Expr) 28 | deriving Repr, DecidableEq 29 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 7, 2 | "packagesDir": ".lake/packages", 3 | "packages": 4 | [{"url": "https://github.com/leanprover-community/batteries.git", 5 | "type": "git", 6 | "subDir": null, 7 | "rev": "27343fe14c71b5b8e32ef4d04738a1a3d48926c7", 8 | "name": "batteries", 9 | "manifestFile": "lake-manifest.json", 10 | "inputRev": "nightly-testing", 11 | "inherited": true, 12 | "configFile": "lakefile.lean"}, 13 | {"url": "https://github.com/leanprover/leansat.git", 14 | "type": "git", 15 | "subDir": null, 16 | "rev": "07864038c4f9764a6813941f305d881e27810e83", 17 | "name": "LeanSAT", 18 | "manifestFile": "lake-manifest.json", 19 | "inputRev": "main", 20 | "inherited": false, 21 | "configFile": "lakefile.lean"}], 22 | "name": "ssft24", 23 | "lakeDir": ".lake"} 24 | -------------------------------------------------------------------------------- /Imp/Stmt/Optimize.lean: -------------------------------------------------------------------------------- 1 | import Imp.Expr 2 | import Imp.Stmt.Basic 3 | import Imp.Stmt.Delab 4 | 5 | namespace Imp.Stmt 6 | 7 | /-- Optimize a statement -/ 8 | def optimize : Stmt → Stmt 9 | | imp {skip;} => imp {skip;} 10 | | imp {~s1 ~s2} => 11 | match s1.optimize, s2.optimize with 12 | | imp {skip;}, s2' => s2' 13 | | s1', imp {skip;} => s1' 14 | | s1', s2' => imp {~s1' ~s2'} 15 | | imp {if (~c) {~s1} else {~s2}} => 16 | let c' := c.optimize 17 | match c' with 18 | | .const 0 => s2.optimize 19 | | .const _ => s1.optimize 20 | | _ => 21 | let s1' := s1.optimize 22 | let s2' := s2.optimize 23 | if s1' = s2' then 24 | s1' 25 | else imp {if (~c') {~s1.optimize} else {~s2.optimize}} 26 | | imp {while (~c) {~s}} => 27 | let c' := c.optimize 28 | match c' with 29 | | .const 0 => imp {skip;} 30 | | _ => imp {while (~c') {~s.optimize}} 31 | | imp {~x := ~e;} => 32 | imp {~x := ~e.optimize;} 33 | -------------------------------------------------------------------------------- /tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname "$0")" || exit 1 4 | 5 | mkdir -p out expected 6 | 7 | jsonfilter="lake -d=.. exe jsonfilter" 8 | 9 | if [ "$1" = "-a" ] 10 | then 11 | shift 12 | accept=true 13 | fi 14 | 15 | tests="" 16 | if [ $# -eq 0 ] 17 | then 18 | tests=(query*.json) 19 | else 20 | tests=( "$@" ) 21 | fi 22 | 23 | failed=() 24 | 25 | for t in "${tests[@]}" 26 | do 27 | echo -n "Running $t... " 28 | if [ "$accept" = true ] 29 | then 30 | $jsonfilter "$(<"$t")" < input.json > "expected/$t" 31 | echo 32 | else 33 | if $jsonfilter "$(<"$t")" < input.json > "out/$t" 34 | then 35 | if diff -Nu "expected/$t" "out/$t" > "out/$t.diff" 36 | then 37 | echo "✅ $t" 38 | else 39 | failed+=("$t") 40 | echo "❌" 41 | cat "out/$t.diff" 42 | fi 43 | else 44 | failed+=("$t") 45 | echo "❌ $t" 46 | fi 47 | fi 48 | done 49 | 50 | if (( ${#failed[@]} )); then 51 | echo "Some tests failed. Rerun with" 52 | echo "$0 ${failed[*]}" 53 | exit 1 54 | fi 55 | -------------------------------------------------------------------------------- /Imp/Expr/Optimize.lean: -------------------------------------------------------------------------------- 1 | import Imp.Expr.Basic 2 | import Imp.Expr.Eval 3 | 4 | namespace Imp.Expr 5 | 6 | /-- 7 | Optimizes an expression by folding constants. 8 | -/ 9 | def optimize : Expr → Expr 10 | | .const i => .const i 11 | | .var x => .var x 12 | | .un op e => 13 | match optimize e with 14 | | .const i => 15 | if let some v := op.apply i then .const v 16 | else .un op (.const i) 17 | | e' => .un op e' 18 | | .bin op e1 e2 => 19 | match optimize e1, optimize e2 with 20 | | .const i, .const i' => 21 | if let some v := op.apply i i' then .const v 22 | else .bin op (.const i) (.const i') 23 | | e1', e2' => .bin op e1' e2' 24 | 25 | /-- 26 | Optimization doesn't change the meaning of any expression 27 | -/ 28 | theorem optimize_ok (e : Expr) : e.eval σ = e.optimize.eval σ := by 29 | induction e <;> simp [optimize] 30 | case un op e ih => 31 | split <;> simp [eval, *] 32 | cases op <;> simp [UnOp.apply, eval] 33 | case bin op e1 e2 ih1 ih2 => 34 | split <;> simp [eval, *] 35 | cases op <;> simp [BinOp.apply, eval] 36 | split 37 | . simp [eval, BinOp.apply]; split <;> trivial 38 | . simp [eval] 39 | 40 | /-- 41 | Optimization doesn't change the meaning of any expression 42 | -/ 43 | theorem optimize_ok' (e : Expr) : e.eval σ = e.optimize.eval σ := by 44 | induction e using optimize.induct <;> simp [optimize, eval, *] 45 | -------------------------------------------------------------------------------- /Imp.lean: -------------------------------------------------------------------------------- 1 | import Lean.PrettyPrinter.Delaborator 2 | import LeanSAT.Reflect.Tactics.BVDecide 3 | 4 | import Imp.Expr 5 | import Imp.Stmt 6 | 7 | namespace Imp.Stmt 8 | 9 | open BitVec -- Enables bitvector literal syntax and lemmas 10 | 11 | /- 12 | The final demo - read this file last! 13 | -/ 14 | 15 | def popcountLoop : Stmt := imp { 16 | i := 32; 17 | count := 0; 18 | while (i > 0) { 19 | count := count + (x &&& 1); 20 | i := i - 1; 21 | x := x >>> 1; 22 | } 23 | x := count; 24 | } 25 | 26 | /-- 27 | Alternative implementation of popcount from Hacker's Delight, Second Edition, by Henry S. Warren, 28 | Jr. Figure 5-2 on p. 82. 29 | -/ 30 | def popcount : Stmt := imp { 31 | x := x - ((x >>> 1) &&& 0x55555555); 32 | x := (x &&& 0x33333333) + ((x >>> 2) &&& 0x33333333); 33 | x := (x + (x >>> 4)) &&& 0x0F0F0F0F; 34 | x := x + (x >>> 8); 35 | x := x + (x >>> 16); 36 | x := x &&& 0x0000003F; 37 | } 38 | 39 | 40 | def pop_spec (x : BitVec 32) : BitVec 32 := 41 | go x 0 32 42 | where 43 | go (x : BitVec 32) (pop : BitVec 32) (i : Nat) : BitVec 32 := 44 | match i with 45 | | 0 => pop 46 | | i + 1 => 47 | let pop := pop + (x &&& 1#32) 48 | go (x >>> 1#32) pop i 49 | 50 | 51 | theorem popCount_correctBig : 52 | ∃ σ, (run (Env.init x) popcount 8) = some σ ∧ σ "x" = pop_spec x := by 53 | simp [run, popcount, Expr.eval, Expr.BinOp.apply, Env.set, Value, pop_spec, pop_spec.go] 54 | bv_decide 55 | -------------------------------------------------------------------------------- /Imp/Expr/Eval.lean: -------------------------------------------------------------------------------- 1 | import Imp.Expr.Basic 2 | 3 | open Lean 4 | 5 | namespace Imp 6 | 7 | /-- Values stored in memory - 32-bit integers -/ 8 | abbrev Value := BitVec 32 9 | 10 | /-- An environment maps variables names to their values (no pointers) -/ 11 | def Env := String → Value 12 | 13 | namespace Env 14 | 15 | /-- Set a value in an environment -/ 16 | def set (x : String) (v : Value) (σ : Env) : Env := 17 | fun y => if x == y then v else σ y 18 | 19 | /-- Look up a value in an environment -/ 20 | def get (x : String) (σ : Env) : Value := 21 | σ x 22 | 23 | /-- Initialize an environment, setting all uninitialized memory to `i` -/ 24 | def init (i : Value) : Env := fun _ => i 25 | 26 | @[simp] 27 | theorem get_init : (Env.init v).get x = v := by rfl 28 | 29 | @[simp] 30 | theorem get_set_same {σ : Env} : (σ.set x v).get x = v := by 31 | simp [get, set] 32 | 33 | @[simp] 34 | theorem get_set_different {σ : Env} : x ≠ y → (σ.set x v).get y = σ.get y := by 35 | intros 36 | simp [get, set, *] 37 | 38 | end Env 39 | 40 | namespace Expr 41 | 42 | /-- Helper that implements unary operators -/ 43 | def UnOp.apply : UnOp → Value → Option Value 44 | | .neg, x => some (- x) 45 | | .not, x => some (if x == 0 then 1 else 0) 46 | 47 | /-- Helper that implements binary operators -/ 48 | def BinOp.apply : BinOp → Value → Value → Option Value 49 | | .plus, x, y => some (x + y) 50 | | .minus, x, y => some (x - y) 51 | | .times, x, y => some (x * y) 52 | | .lsh, x, y => some (x <<< y) 53 | | .rsh, x, y => some (x >>> y) 54 | | .band, x, y => some (x &&& y) 55 | | .bor, x, y => some (x ||| y) 56 | | .div, x, y => if y == 0 then none else some (x / y) 57 | | .and, x, y => some (if x == 0 then 0 else y) 58 | | .or, x, y => some (if x == 0 then y else x) 59 | | .eq, x, y => some (if x == y then 1 else 0) 60 | | .le, x, y => some (if x ≤ y then 1 else 0) 61 | | .lt, x, y => some (if x < y then 1 else 0) 62 | 63 | /-- 64 | Evaluates an expression, finding the value if it has one. 65 | 66 | Expressions that divide by zero don't have values - the result is undefined. 67 | -/ 68 | def eval (σ : Env) : Expr → Option Value 69 | | .const i => some i 70 | | .var x => σ.get x 71 | | .un op e => do 72 | let v ← e.eval σ 73 | op.apply v 74 | | .bin op e1 e2 => do 75 | let v1 ← e1.eval σ 76 | let v2 ← e2.eval σ 77 | op.apply v1 v2 78 | -------------------------------------------------------------------------------- /Filter/List.lean: -------------------------------------------------------------------------------- 1 | 2 | namespace Filter.List 3 | 4 | -- `DecidablePred p` means that we can use `if` to check whether a value satisfies `p` 5 | -- Example : if p x then ... else ... 6 | def filter (p : α → Prop) [DecidablePred p] (xs : List α) : List α := 7 | match xs with 8 | | [] => [] 9 | | x :: xs' => if p x then x :: filter p xs' else filter p xs' 10 | 11 | def filter_length (p : α → Prop) [DecidablePred p] : (filter p xs).length ≤ xs.length := by 12 | induction xs with 13 | | nil => simp [filter] 14 | | cons x xs ih => 15 | simp only [filter] 16 | split 17 | . simp only [List.length]; 18 | exact Nat.add_le_add_right ih 1 19 | . exact Nat.le_succ_of_le ih 20 | 21 | /- Reminder: 22 | inductive Repeats (x : α) : List α → Prop where 23 | | nil : Repeats x [] 24 | | cons : Repeats x xs → Repeats x (x :: xs) 25 | -/ 26 | /-- `All p xs` states that `p` holds for all entries in the list `xs` -/ 27 | inductive All (p : α → Prop) : List α → Prop where 28 | | /-- `p` certainly holds for all zero entries of the empty list -/ 29 | nil : All p [] 30 | | /-- If `p` holds for the head and holds for all entries in the tail, 31 | then it holds for the combined list -/ 32 | cons : p x → All p xs → All p (x :: xs) 33 | 34 | theorem filter_all (p : α → Prop) [DecidablePred p] 35 | : All p (filter p xs) := by 36 | induction xs with 37 | | nil => constructor 38 | | cons x xs ih => 39 | unfold filter 40 | split 41 | next h => 42 | apply All.cons 43 | . exact h 44 | . exact ih 45 | next => 46 | assumption 47 | 48 | theorem filter_elem (p : α → Prop) [DecidablePred p] : x ∈ xs → p x → x ∈ filter p xs := by 49 | intro hMem 50 | induction hMem with 51 | | head as => 52 | intro hx 53 | unfold filter 54 | split <;> try trivial 55 | constructor 56 | | tail h _ ih => 57 | intro hx 58 | unfold filter 59 | split 60 | . constructor; apply ih; assumption 61 | . apply ih; assumption 62 | 63 | /-- `Sublist xs ys` means that all entries of `xs` occur, in that 64 | order, in `ys`, possibly with extra entries -/ 65 | inductive Sublist : List α → List α → Prop where 66 | | nil : Sublist [] ys 67 | | skip : Sublist xs ys → Sublist xs (y :: ys) 68 | | cons : Sublist xs ys → Sublist (x :: xs) (x :: ys) 69 | 70 | theorem filter_sublist [DecidablePred p] : Sublist (filter p xs) xs := by 71 | induction xs with 72 | | nil => 73 | constructor 74 | | cons head tail ih => 75 | unfold filter 76 | split 77 | . apply Sublist.cons; exact ih 78 | . apply Sublist.skip; exact ih 79 | -------------------------------------------------------------------------------- /Imp/Stmt/Basic.lean: -------------------------------------------------------------------------------- 1 | import Imp.Expr 2 | 3 | namespace Imp 4 | 5 | /-- Statements in Imp -/ 6 | inductive Stmt where 7 | /-- A statement that does nothing -/ 8 | | skip 9 | /-- Executes `stmt1` then `stmt2` -/ 10 | | seq (stmt1 stmt2 : Stmt) 11 | /-- Modifies a variable in the state -/ 12 | | assign (name : String) (val : Expr) 13 | /-- 14 | Conditional: executes `ifTrue` when `cond`'s value is nonzero, `ifFalse` otherwise 15 | -/ 16 | | if (cond : Expr) (ifTrue ifFalse : Stmt) 17 | /-- 18 | Repeats `body` as long as `cond` evaluates to a nonzero value 19 | -/ 20 | | while (cond : Expr) (body : Stmt) 21 | deriving Repr, DecidableEq 22 | 23 | /-- Imp statements -/ 24 | declare_syntax_cat stmt 25 | /-- A statement that does nothing -/ 26 | syntax "skip" ";" : stmt 27 | /-- Sequencing: one statement after another -/ 28 | syntax stmt ppDedent(ppLine stmt) : stmt 29 | /-- Assignment -/ 30 | syntax varname " := " exp ";" : stmt 31 | /-- Conditional statement -/ 32 | syntax "if " "(" exp ")" ppHardSpace "{" ppLine stmt ppDedent(ppLine "}" ppHardSpace "else" ppHardSpace "{") ppLine stmt ppDedent(ppLine "}") : stmt 33 | /-- Loop -/ 34 | syntax "while " "(" exp ")" ppHardSpace "{" ppLine stmt ppDedent(ppLine "}") : stmt 35 | /-- Escape to Lean -/ 36 | syntax:max "~" term:max : stmt 37 | 38 | /-- Include an Imp statement in Lean code -/ 39 | syntax:min "imp" ppHardSpace "{" ppLine stmt ppDedent(ppLine "}") : term 40 | 41 | /- 42 | The above rules are equivalent to the following, but with nicer-looking compiler output: 43 | 44 | syntax "skip" ";" : stmt 45 | syntax stmt stmt : stmt 46 | syntax varname " := " exp ";" : stmt 47 | syntax "if " "(" exp ") " "{" stmt "}" "else" "{" stmt "}" : stmt 48 | syntax "while " "(" exp ") " "{" stmt "}" : stmt 49 | syntax:max "~" term:max : stmt 50 | 51 | syntax:min "imp" "{" stmt "}" : term 52 | -/ 53 | 54 | 55 | open Lean in 56 | macro_rules 57 | | `(imp { skip; }) => 58 | `(Stmt.skip) 59 | | `(imp { $s1 $s2 }) => 60 | `(Stmt.seq (imp {$s1}) (imp {$s2})) 61 | | `(imp { $x:varname := $e;}) => 62 | `(Stmt.assign (var {$x}) (expr {$e})) 63 | | `(imp { if ($c) {$s1} else {$s2} }) => 64 | `(Stmt.if (expr{$c}) (imp{$s1}) (imp{$s2})) 65 | | `(imp { while ($c) {$s} }) => 66 | `(Stmt.while (expr{$c}) (imp{$s})) 67 | | `(imp { ~$stx }) => 68 | pure stx 69 | 70 | 71 | def swap : Stmt := imp { 72 | temp := x; 73 | x := y; 74 | y := temp; 75 | } 76 | 77 | def min : Stmt := imp { 78 | if (x < y) { 79 | min := x; 80 | } else { 81 | min := y; 82 | } 83 | } 84 | 85 | def fact : Stmt := imp { 86 | out := 1; 87 | while (n > 0) { 88 | out := out * n; 89 | n := n - 1; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Intro.lean: -------------------------------------------------------------------------------- 1 | ------------------------- 2 | -- Programming in Lean -- 3 | ------------------------- 4 | 5 | /- 6 | 7 | Lean is very much like other functional programming languages. It has 8 | recursive definitions by pattern matching, user-defined datatypes, 9 | type classes, and do-notation for programming with monads. 10 | 11 | -/ 12 | 13 | -- Here's a function that doubles a natural number. 14 | def double (n : Nat) : Nat := 15 | match n with 16 | | 0 => 0 17 | | n' + 1 => double n' + 2 18 | 19 | -- Test it using `#eval`, which is a bit like an in-editor REPL 20 | #eval double 5 21 | 22 | -- `#check` is used to to check types 23 | #check double 24 | 25 | -- Arguments that come "after the colon" can be matched with top-level 26 | -- patterns - no `match` necessary!. 27 | def double' : Nat → Nat 28 | | 0 => 0 29 | | n' + 1 => double' n' + 2 30 | 31 | -- These may both be used. 32 | def multiply (k : Nat) : Nat → Nat 33 | | 0 => 0 34 | | n' + 1 => multiply k n' + k 35 | 36 | -- If parameter names are just used, then they're treated as parameters to the whole function. Here, 37 | -- α is the type argument, implicitly specified just by using it. 38 | def append : List α → List α → List α 39 | | [], ys => ys 40 | | x :: xs, ys => x :: append xs ys 41 | 42 | -- Datatypes are defined with `inductive` 43 | inductive Tree (α : Type) where 44 | | leaf : Tree α 45 | | branch : Tree α → α → Tree α → Tree α 46 | 47 | -- Putting a function inside a type's namespace allows dot-notation: 48 | def Tree.toList : Tree α → List α 49 | | leaf => [] 50 | | branch l x r => l.toList ++ [x] ++ r.toList 51 | 52 | #eval (Tree.branch (Tree.branch Tree.leaf 1 Tree.leaf) 2 Tree.leaf).toList 53 | 54 | /- 55 | 56 | Lean also has full dependent types, so types can mention values and values in types can compute. 57 | Following the "propositions as types" principle, a proposition (logical statement) is represented as 58 | a type that classifies evidence of its truth. Dependent types allow propositions to include ordinary 59 | values, and thus be useful! 60 | 61 | A predicate is a proposition with a free variable that might hold (or not) for a given value 62 | 63 | -/ 64 | inductive Even : Nat → Prop where 65 | | isEven : (half : Nat) → Even (half + half) 66 | 67 | example : Even 6 := .isEven 3 68 | 69 | inductive Repeats (x : α) : List α → Prop where 70 | | nil : Repeats x [] 71 | | cons : Repeats x xs → Repeats x (x :: xs) -- here xs is implicitly an argument 72 | 73 | example : Repeats 3 [3,3,3] := .cons (.cons (.cons .nil)) 74 | 75 | /- 76 | Lean types come in two flavors: 77 | * Types that contain data 78 | * Propositions 79 | 80 | The difference is that the rules of Lean are set up such that any two proofs of the same proposition 81 | are considered equivalent - we don't get to care which proof we have, so the result of a program had 82 | better not depend on it. This frees us from caring as well, so we don't have to worry _why_ two 83 | things are equal. 84 | 85 | Functions that return types (including propositions) are just as good as any other function. For 86 | instance, this function takes two predicates over some type α and builds a new predicate that 87 | asserts both: 88 | -/ 89 | 90 | def Both (p q : α → Prop) : α → Prop := fun x => p x ∧ q x 91 | 92 | /- 93 | For more details on this, please see _Theorem Proving in Lean 4_ and _Functional Programming in 94 | Lean_. 95 | -/ 96 | -------------------------------------------------------------------------------- /Imp/Stmt/Delab.lean: -------------------------------------------------------------------------------- 1 | /- 2 | This file makes the convenient syntax from `Imp.Stmt.Basic` show up in Lean's output. It's not part 3 | of what's being taught in the lectures. 4 | -/ 5 | import Lean.PrettyPrinter.Delaborator 6 | import Imp.Stmt.Basic 7 | import Imp.Expr 8 | 9 | open Lean PrettyPrinter.Delaborator SubExpr 10 | 11 | namespace Imp.Stmt.Delab 12 | 13 | open Imp.Stmt 14 | open Imp.Expr.Delab 15 | 16 | partial def delabStmtInner : DelabM (TSyntax `stmt) := do 17 | let e ← getExpr 18 | let stx ← 19 | match_expr e with 20 | | Stmt.skip => `(stmt| skip;) 21 | | Stmt.seq _ _ => 22 | let s1 ← withAppFn <| withAppArg delabStmtInner 23 | let s2 ← withAppArg delabStmtInner 24 | `(stmt| $s1 $s2) 25 | | Stmt.assign _ _ => 26 | let x ← withAppFn <| withAppArg delabNameInner 27 | let e ← withAppArg delabExprInner 28 | `(stmt| $x:varname := $e;) 29 | | Stmt.while _ _ => 30 | let c ← withAppFn <| withAppArg delabExprInner 31 | let body ← withAppArg delabStmtInner 32 | `(stmt| while ($c) { $body }) 33 | | Stmt.if _ _ _ => 34 | let c ← withAppFn <| withAppFn <| withAppArg delabExprInner 35 | let t ← withAppFn <| withAppArg delabStmtInner 36 | let f ← withAppArg delabStmtInner 37 | `(stmt| if ($c) { $t } else { $f }) 38 | | _ => 39 | `(stmt| ~$(← delab)) 40 | annAsTerm stx 41 | 42 | @[delab app.Imp.Stmt.skip, delab app.Imp.Stmt.seq, delab app.Imp.Stmt.while, delab app.Imp.Stmt.assign, delab app.Imp.Stmt.if] 43 | partial def delabStmt : Delab := do 44 | -- This delaborator only understands a certain arity - bail if it's incorrect 45 | guard <| match_expr ← getExpr with 46 | | Stmt.skip => true 47 | | Stmt.seq _ _ => true 48 | | Stmt.while _ _ => true 49 | | Stmt.assign _ _ => true 50 | | Stmt.if _ _ _ => true 51 | | _ => false 52 | match ← delabStmtInner with 53 | | `(stmt|~$e) => pure e 54 | | s => `(term|imp{$s}) 55 | 56 | /-- info: Imp.Stmt.skip : Stmt -/ 57 | #guard_msgs in 58 | #check Stmt.skip 59 | 60 | /-- 61 | info: imp { 62 | skip; 63 | } : Stmt 64 | -/ 65 | #guard_msgs in 66 | #check imp {skip;} 67 | 68 | /-- 69 | info: imp { 70 | skip; 71 | skip; 72 | } : Stmt 73 | -/ 74 | #guard_msgs in 75 | #check imp {skip; skip;} 76 | 77 | /-- 78 | info: imp { 79 | skip; 80 | skip; 81 | x := 5; 82 | } : Stmt 83 | -/ 84 | #guard_msgs in 85 | #check imp {skip; skip; x := 5;} 86 | 87 | /-- 88 | info: imp { 89 | skip; 90 | if (0 < x) { 91 | x := x - 1; 92 | } else { 93 | skip; 94 | } 95 | } : Stmt 96 | -/ 97 | #guard_msgs in 98 | #check imp {skip; if (x > 0) { x := x - 1;} else {skip;}} 99 | 100 | /-- 101 | info: imp { 102 | skip; 103 | while (0 < x) { 104 | x := x - 1; 105 | } 106 | if (x) { 107 | skip; 108 | } else { 109 | while (5 < x) { 110 | skip; 111 | skip; 112 | } 113 | } 114 | } : Stmt 115 | -/ 116 | #guard_msgs in 117 | #check imp {skip; while (x > 0) { x := x - 1;} if (x) {skip;} else {while (x > 5) {skip; skip;}}} 118 | 119 | /-- 120 | info: let s := 121 | imp { 122 | skip; 123 | }; 124 | imp { 125 | skip; 126 | while (0 < x) { 127 | x := x - 1; 128 | } 129 | if (x) { 130 | skip; 131 | } else { 132 | while (5 < x) { 133 | skip; 134 | ~s 135 | } 136 | } 137 | } : Stmt 138 | -/ 139 | #guard_msgs in 140 | #check let s := imp {skip;}; imp {skip; while (x > 0) { x := x - 1;} if (x) {skip;} else {while (x > 5) {skip; ~s}}} 141 | -------------------------------------------------------------------------------- /Filter/Array.lean: -------------------------------------------------------------------------------- 1 | namespace Filter.Array 2 | 3 | /- 4 | This implementation of `filter` shows a few interesting aspects of Lean: 5 | 6 | * Lean's `do`-notation contains syntax for mutable variables and iteration. These are translated 7 | behind the scenes to appropriate pure functions. 8 | 9 | * `Array.push` will mutate the array in place if the array value's reference count is exactly 1, so 10 | this code's seeming use of mutation becomes real mutation, with accidental aliasing leading to 11 | extra copying instead of scary bugs 12 | -/ 13 | def filter1 (p : α → Prop) [DecidablePred p] (arr : Array α) : Array α := Id.run do 14 | let mut out := #[] 15 | for x in arr do 16 | if p x then out := out.push x 17 | return out 18 | 19 | /- 20 | This version of `filter` is easier to prove things about. `do` notation is nice, but the desugared 21 | programs are more difficult to use in verification than hand-written loops/tail-recursive functions. 22 | -/ 23 | def filter (p : α → Prop) [DecidablePred p] (arr : Array α) : Array α := 24 | go 0 #[] 25 | where 26 | go (i : Nat) (out : Array α) : Array α := 27 | if h : i < arr.size then 28 | if p arr[i] then 29 | go (i + 1) (out.push arr[i]) 30 | else 31 | go (i + 1) out 32 | else out 33 | termination_by arr.size - i 34 | 35 | def All (p : α → Prop) (arr : Array α) : Prop := 36 | (i : Nat) → (lt : i < arr.size) → p arr[i] 37 | 38 | @[simp] 39 | theorem all_empty (p : α → Prop) : All p #[] := fun i lt => 40 | by contradiction 41 | 42 | @[simp] 43 | theorem push_all (hAll : All p xs) (hx : p x) : All p (xs.push x) := by 44 | intros 45 | intro i lt 46 | by_cases i = xs.size 47 | · simp [*] 48 | · simp at lt 49 | have : i < xs.size := by omega 50 | simp [Array.get_push_lt, *] 51 | apply hAll 52 | 53 | 54 | /- 55 | Prove that the inner loop in `filter` ensures that the predicate holds for its result, on the 56 | assumption that it holds for all elements in the initial accumulator. 57 | 58 | The following tactics may be useful: 59 | * `unfold f` replaces `f` with its definition in the goal 60 | * `split` replaces a goal that contains an `if` or `match` expression with a new goal for each 61 | branch 62 | * `apply` applies an existing lemma 63 | * `assumption` proves the goal using a hypothesis if possible 64 | 65 | Because the proof should follow the recursive structure of the program, you may additionally need to 66 | copy the termination argument from the program to the proof. 67 | -/ 68 | theorem filter_go_all [DecidablePred p] (hAcc : All p acc) 69 | : All p (filter.go p xs i acc) := by 70 | unfold filter.go 71 | split 72 | · split 73 | . apply filter_go_all 74 | apply push_all 75 | . assumption 76 | . assumption 77 | . apply filter_go_all 78 | . assumption 79 | · assumption 80 | termination_by xs.size - i 81 | 82 | 83 | theorem filter_all (p : α → Prop) [DecidablePred p] : All p (filter p xs) := by 84 | simp [filter, filter_go_all] 85 | 86 | 87 | /- 88 | This version of the proof uses Lean's functional induction feature, which allows the `induction` 89 | tactic to automatically follow the recursive structure of a function. Using the induction tactic 90 | also makes it easier to write a highly automated proof that will be more robust in the face of minor 91 | changes to `filter`. 92 | -/ 93 | theorem filter_go_all' [DecidablePred p] (hAcc : All p acc) 94 | : All p (filter.go p xs i acc) := by 95 | induction i, acc using filter.go.induct p xs <;> unfold filter.go <;> simp [*] 96 | 97 | theorem filter_all' (p : α → Prop) [DecidablePred p] : All p (filter p xs) := by 98 | simp [filter, filter_go_all'] 99 | -------------------------------------------------------------------------------- /Imp/Expr/Delab.lean: -------------------------------------------------------------------------------- 1 | /- 2 | This file makes the convenient syntax from `Imp.Expr.Syntax` show up in Lean's output. It's not part 3 | of what's being taught in the lectures. 4 | -/ 5 | import Lean.PrettyPrinter.Delaborator 6 | import Imp.Expr.Basic 7 | import Imp.Expr.Syntax 8 | 9 | open Lean PrettyPrinter Delaborator SubExpr Parenthesizer 10 | 11 | namespace Imp.Expr.Delab 12 | 13 | def annAsTerm {any} (stx : TSyntax any) : DelabM (TSyntax any) := 14 | (⟨·⟩) <$> annotateTermInfo ⟨stx.raw⟩ 15 | 16 | def delabNameInner : DelabM (TSyntax `varname) := do 17 | let e ← getExpr 18 | match e with 19 | | .lit (.strVal s) => 20 | let x := mkIdent <| .mkSimple s 21 | pure <| ⟨x.raw⟩ 22 | | _ => `(varname|~($(← delab))) >>= annAsTerm 23 | 24 | partial def delabExprInner : DelabM (TSyntax `exp) := do 25 | let e ← getExpr 26 | let stx ← 27 | match_expr e with 28 | | Expr.const x => 29 | match_expr x with 30 | | OfNat.ofNat _ n _ => 31 | if let some n' := n.nat? then 32 | pure <| ⟨Syntax.mkNumLit (toString n') |>.raw⟩ 33 | else if let .lit (.natVal n') := n then 34 | pure <| ⟨Syntax.mkNumLit (toString n') |>.raw⟩ 35 | else withAppArg `(exp| ~$(← delab)) 36 | | Int.ofNat n => 37 | if let some n' := n.nat? then 38 | pure <| ⟨Syntax.mkNumLit (toString n') |>.raw⟩ 39 | else if let .lit (.natVal n') := n then 40 | pure <| ⟨Syntax.mkNumLit (toString n') |>.raw⟩ 41 | else withAppArg `(exp| ~$(← delab)) 42 | | BitVec.ofNat _ _ => (⟨·.raw⟩) <$> (withAppArg <| withAppArg <| delab) 43 | | _ => 44 | `(exp| ~(Expr.const $(← withAppArg delab))) 45 | | Expr.var _ => do 46 | let x ← withAppArg delabNameInner 47 | `(exp|$x:varname) 48 | | Expr.bin op _ _ => 49 | let s1 ← withAppFn <| withAppArg delabExprInner 50 | let s2 ← withAppArg delabExprInner 51 | match_expr op with 52 | | BinOp.plus => `(exp| $s1 + $s2) 53 | | BinOp.minus => `(exp| $s1 - $s2) 54 | | BinOp.times => `(exp| $s1 * $s2) 55 | | BinOp.div => `(exp| $s1 / $s2) 56 | | BinOp.and => `(exp| $s1 && $s2) 57 | | BinOp.or => `(exp| $s1 || $s2) 58 | | BinOp.lt => `(exp| $s1 < $s2) 59 | | BinOp.le => `(exp| $s1 ≤ $s2) 60 | | BinOp.eq => `(exp| $s1 == $s2) 61 | | BinOp.lsh => `(exp| $s1 <<< $s2) 62 | | BinOp.rsh => `(exp| $s1 >>> $s2) 63 | | BinOp.band => `(exp| $s1 &&& $s2) 64 | | BinOp.bor => `(exp| $s1 ||| $s2) 65 | | _ => `(exp|~(Expr.bin $(← withAppFn <| withAppFn <| withAppArg delab) $(← withAppFn <| withAppArg delab) $(← withAppArg delab))) 66 | | Expr.un op _ => 67 | let s ← withAppArg delabExprInner 68 | match_expr op with 69 | | UnOp.neg => `(exp|-$s) 70 | | UnOp.not => `(exp|!$s) 71 | | _ => `(exp| ~(Expr.un $(← withAppFn <| withAppArg delab) $(← withAppArg delab))) 72 | | _ => 73 | `(exp| ~$(← delab)) 74 | annAsTerm stx 75 | 76 | @[delab app.Imp.Expr.const, delab app.Imp.Expr.var, delab app.Imp.Expr.un, delab app.Imp.Expr.bin] 77 | partial def delabExpr : Delab := do 78 | -- This delaborator only understands a certain arity - give up if it's incorrect 79 | guard <| match_expr ← getExpr with 80 | | Expr.const _ => true 81 | | Expr.var _ => true 82 | | Expr.un _ _ => true 83 | | Expr.bin _ _ _ => true 84 | | _ => false 85 | match ← delabExprInner with 86 | | `(exp|~$e) => pure e 87 | | e => `(term|expr {$(⟨e⟩)}) 88 | 89 | /-- info: expr { 5 } : Expr -/ 90 | #guard_msgs in 91 | #check Expr.const 5 92 | 93 | /-- info: expr { 5 } : Expr -/ 94 | #guard_msgs in 95 | #check expr { 5 } 96 | 97 | /-- info: expr { x } : Expr -/ 98 | #guard_msgs in 99 | #check expr { x } 100 | 101 | /-- info: expr { 5 + 23 - 10 } : Expr -/ 102 | #guard_msgs in 103 | #check expr { 5 + 23 - 10 } 104 | 105 | /-- info: expr { 5 + (23 - 10) } : Expr -/ 106 | #guard_msgs in 107 | #check expr { 5 + (23 - 10) } 108 | 109 | /-- info: expr { -8 <<< 4 } : Expr -/ 110 | #guard_msgs in 111 | #check expr {-8 <<< 4} 112 | 113 | /-- 114 | info: let x := expr { 23 }; 115 | expr { ~x * ~x } : Expr 116 | -/ 117 | #guard_msgs in 118 | #check let x := expr {23}; expr {~x * ~x} 119 | -------------------------------------------------------------------------------- /Imp/Expr/Syntax.lean: -------------------------------------------------------------------------------- 1 | import Lean.PrettyPrinter.Parenthesizer 2 | import Imp.Expr.Basic 3 | 4 | namespace Imp.Expr 5 | 6 | open Lean PrettyPrinter Parenthesizer 7 | 8 | 9 | /- Add a new nonterminal to Lean's grammar, called `varname` -/ 10 | /-- Variable names in Imp -/ 11 | declare_syntax_cat varname 12 | 13 | /- There are two productions: identifiers and antiquoted terms -/ 14 | syntax ident : varname 15 | syntax:max "~" term:max : varname 16 | 17 | /- `varname`s are included in terms using `var { ... }`, which is a hook on which to hang the macros 18 | that translate the `varname` syntax into ordinary Lean expressions. -/ 19 | syntax "var " "{" varname "}" : term 20 | 21 | /- These macros translate each production of the `varname` nonterminal into Lean expressions -/ 22 | macro_rules 23 | | `(var { $x:ident }) => `(term|$(quote x.getId.toString)) 24 | | `(var { ~$stx }) => pure stx 25 | 26 | /-- Expressions in Imp -/ 27 | declare_syntax_cat exp 28 | 29 | /-- Variable names -/ 30 | syntax varname : exp 31 | 32 | /-- Numeric constant -/ 33 | syntax num : exp 34 | 35 | /-- Arithmetic complement -/ 36 | syntax:75 "-" exp:75 : exp 37 | 38 | /-- Multiplication -/ 39 | syntax:70 exp:70 " * " exp:71 : exp 40 | /-- Division -/ 41 | syntax:70 exp:70 " / " exp:71 : exp 42 | 43 | /-- Addition -/ 44 | syntax:65 exp:65 " + " exp:66 : exp 45 | /-- Subtraction -/ 46 | syntax:65 exp:65 " - " exp:66 : exp 47 | 48 | /-- Left shift -/ 49 | syntax:55 exp:55 " <<< " exp:56 :exp 50 | /-- Right shift -/ 51 | syntax:55 exp:55 " >>> " exp:56 :exp 52 | 53 | /-- Boolean negation -/ 54 | syntax:75 "!" exp:75 : exp 55 | /-- Less than -/ 56 | syntax:50 exp:50 " < " exp:51 : exp 57 | /-- Less than or equal to -/ 58 | syntax:50 exp:50 " ≤ " exp:51 : exp 59 | /-- Greater than or equal to -/ 60 | syntax:50 exp:50 " ≥ " exp:51 : exp 61 | /-- Greater than -/ 62 | syntax:50 exp:50 " > " exp:51 : exp 63 | /-- Equal -/ 64 | syntax:45 exp:45 " == " exp:46 : exp 65 | 66 | /-- Bitwise and -/ 67 | syntax:40 exp:40 " &&& " exp:41 :exp 68 | /-- Bitwise or -/ 69 | syntax:40 exp:40 " ||| " exp:41 :exp 70 | 71 | /-- Boolean conjunction -/ 72 | syntax:35 exp:35 " && " exp:36 : exp 73 | /-- Boolean disjunction -/ 74 | syntax:35 exp:35 " || " exp:36 : exp 75 | 76 | /-- Parentheses for grouping -/ 77 | syntax "(" exp ")" : exp 78 | 79 | /-- Escape to Lean -/ 80 | syntax:max "~" term:max : exp 81 | 82 | /-- Embed an Imp expression into a Lean expression -/ 83 | syntax:min "expr " "{ " exp " }" : term 84 | 85 | open Lean in 86 | macro_rules 87 | | `(expr{$x:ident}) => `(Expr.var $(quote x.getId.toString)) 88 | | `(expr{$n:num}) => `(Expr.const $(quote n.getNat)) 89 | 90 | | `(expr{-$e}) => `(Expr.un .neg (expr{$e})) 91 | | `(expr{!$e}) => `(Expr.un .not (expr{$e})) 92 | 93 | | `(expr{$e1 + $e2}) => `(Expr.bin .plus (expr{$e1}) (expr{$e2})) 94 | | `(expr{$e1 * $e2}) => `(Expr.bin .times (expr{$e1}) (expr{$e2})) 95 | | `(expr{$e1 - $e2}) => `(Expr.bin .minus (expr{$e1}) (expr{$e2})) 96 | | `(expr{$e1 / $e2}) => `(Expr.bin .div (expr{$e1}) (expr{$e2})) 97 | 98 | | `(expr{$e1 >>> $e2}) => `(Expr.bin .rsh (expr{$e1}) (expr{$e2})) 99 | | `(expr{$e1 <<< $e2}) => `(Expr.bin .lsh (expr{$e1}) (expr{$e2})) 100 | | `(expr{$e1 ||| $e2}) => `(Expr.bin .bor (expr{$e1}) (expr{$e2})) 101 | | `(expr{$e1 &&& $e2}) => `(Expr.bin .band (expr{$e1}) (expr{$e2})) 102 | 103 | 104 | | `(expr{$e1 && $e2}) => `(Expr.bin .and (expr{$e1}) (expr{$e2})) 105 | | `(expr{$e1 || $e2}) => `(Expr.bin .or (expr{$e1}) (expr{$e2})) 106 | 107 | | `(expr{$e1 < $e2}) => `(Expr.bin .lt (expr{$e1}) (expr{$e2})) 108 | | `(expr{$e1 ≤ $e2}) => `(Expr.bin .le (expr{$e1}) (expr{$e2})) 109 | | `(expr{$e1 == $e2}) => `(Expr.bin .eq (expr{$e1}) (expr{$e2})) 110 | | `(expr{$e1 ≥ $e2}) => `(Expr.bin .le (expr{$e2}) (expr{$e1})) 111 | | `(expr{$e1 > $e2}) => `(Expr.bin .lt (expr{$e2}) (expr{$e1})) 112 | | `(expr{($e)}) => `(expr{$e}) 113 | | `(expr{~$stx}) => pure stx 114 | 115 | 116 | -- Copied from Lean's term parenthesizer - applies the precedence rules in the grammar to add 117 | -- parentheses as needed. This isn't needed when adding new input syntax to Lean, but because the 118 | -- file `Delab.lean` makes Lean use this syntax in its output, the parentheses are needed. 119 | @[category_parenthesizer exp] 120 | def exp.parenthesizer : CategoryParenthesizer | prec => do 121 | maybeParenthesize `exp true wrapParens prec $ 122 | parenthesizeCategoryCore `exp prec 123 | where 124 | wrapParens (stx : Syntax) : Syntax := Unhygienic.run do 125 | let pstx ← `(($(⟨stx⟩))) 126 | return pstx.raw.setInfo (SourceInfo.fromRef stx) 127 | -------------------------------------------------------------------------------- /Filter/Query.lean: -------------------------------------------------------------------------------- 1 | -- This program uses the compiler's JSON library 2 | import Lean.Data.Json 3 | import Filter.List 4 | import Filter.Array 5 | 6 | namespace Filter 7 | 8 | open Lean (Json FromJson ToJson) 9 | 10 | /-- Where can a query be applied in a JSON value? -/ 11 | inductive Context where 12 | | /-- Querying the fields of an object -/ 13 | object 14 | | /-- Querying the elements of an array -/ 15 | array 16 | | /-- Querying an ordinary JSON value -/ 17 | val 18 | 19 | /-- Queries that apply to a given context -/ 20 | inductive Query : Context → Type where 21 | | /-- Always succeeds -/ 22 | any : Query ctxt 23 | | /-- Always fails -/ 24 | none : Query ctxt 25 | | /-- Succeeds when the value being queried is `v` -/ 26 | is (v : Json) : Query .val 27 | | /-- Succeeds when the value is a string -/ 28 | string : Query .val 29 | | /-- Succeeds when the value is an object that satisfies an object query -/ 30 | obj : Query .object → Query .val 31 | | /-- Succeeds when the object has the given key and the associated value satisfies the query -/ 32 | key : String → Query .val → Query .object 33 | | /-- Succeeds when the object is an array whose elements satisfy a query -/ 34 | array : Query .array → Query .val 35 | | /-- Succeeds when the array being queried has a value at the given index that satisfies the query -/ 36 | at : Nat → Query .val → Query .array 37 | | /-- Succeeds when the array being queried has the given length -/ 38 | length : Nat → Query .array 39 | | /-- Succeeds when both arguments succeed -/ 40 | and : Query ctxt → Query ctxt → Query ctxt 41 | | /-- Succeeds when one of the provided arguments succeeds -/ 42 | or : Query ctxt → Query ctxt → Query ctxt 43 | 44 | -- Which types can the given query context apply to? This is a 45 | -- dependent type where a function is used to compute the type of a 46 | -- later argument from the value of an earlier one. 47 | abbrev Context.Subject : Context → Type 48 | | .val => Json 49 | | .array => Array Json 50 | | .object => Lean.RBNode String (fun _ => Json) 51 | 52 | def Query.matches (q : Query ctxt) (v : ctxt.Subject) : Bool := 53 | match q, v with 54 | | .any, _ => true 55 | | .none, _ => false 56 | | .is v', v => v == v' 57 | | .string, .str _ => true 58 | | .string, _ => false 59 | | .obj q', .obj fields => q'.matches fields 60 | | .obj _, _ => false 61 | | .key k q', fields => 62 | if let some v := fields.find compare k then q'.matches v else false 63 | | .array q', .arr elts => q'.matches elts 64 | | .array _, _ => false 65 | | .at n q', elts => 66 | if let some v := elts[n]? then q'.matches v else false 67 | | .length n, elts => elts.size == n 68 | | .and q1 q2, v => q1.matches v && q2.matches v 69 | | .or q1 q2, v => q1.matches v || q2.matches v 70 | 71 | partial def Query.parse {ctxt: Context} (input : Json) : Except String (Query ctxt) := do 72 | match ctxt, input with 73 | | _, "any" => pure .any 74 | | _, "none" => pure .none 75 | | .val, .arr #["is", o] => pure (.is o) 76 | | .val, "string" => pure .string 77 | | .val, .arr #["object", o] => obj <$> parse o 78 | | .object, .obj fields => 79 | let mut q : Query .object := .any 80 | for ⟨k, v⟩ in fields.toArray do 81 | q := .and q (.key k (← parse v)) 82 | pure q 83 | | .object, other => throw s!"expected object, got {other}" 84 | | .val, .arr #["array", a] => array <$> parse a 85 | | .array, v@(.obj fields) => 86 | match fields.toArray with 87 | | #[⟨"length", n⟩] => 88 | let n' : Nat ← FromJson.fromJson? n 89 | pure (.length n') 90 | | #[⟨"at", n⟩, ⟨"satisfies", q'⟩] => 91 | pure (.at (← FromJson.fromJson? n) (← parse q')) 92 | | _ => throw s!"expected object with single key 'length' or keys 'at' and 'satisfies', got {v}" 93 | | ctxt, .arr more => 94 | if h : more.size > 2 then 95 | match more[0]'(by omega) with 96 | | "and" => 97 | let q1 ← parse (more[1]'(by omega)) 98 | let q2 ← parse (more[2]'(by omega)) 99 | let mut q := .and q1 q2 100 | for h : i in [3:more.size] do 101 | q := .and q (← parse more[i]) 102 | pure q 103 | | "or" => 104 | let q1 ← parse (more[1]'(by omega)) 105 | let q2 ← parse (more[2]'(by omega)) 106 | let mut q := .or q1 q2 107 | for h : i in [3:more.size] do 108 | q := .or q (← parse more[i]) 109 | pure q 110 | | other => throw s!"Expected 'and' or 'or', got {other}" 111 | else 112 | throw s!"Expected at least two elements in {more}" 113 | | ctxt, other => throw s!"didn't understand {other}" 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /Imp/Stmt/BigStep.lean: -------------------------------------------------------------------------------- 1 | import Imp.Expr 2 | 3 | import Imp.Stmt.Delab 4 | import Imp.Stmt.Optimize 5 | 6 | namespace Imp 7 | 8 | /-- 9 | Truthiness: the result of evaluating an expression is "truthy" if it's defined and non-zero. 10 | -/ 11 | def Truthy (v : Option Value) : Prop := 12 | match v with 13 | | some v => v ≠ 0 14 | | none => False 15 | 16 | instance : Decidable (Truthy v) := 17 | match v with 18 | | some v => 19 | if h : v ≠ 0 then .isTrue h else .isFalse h 20 | | none => .isFalse id 21 | 22 | @[simp] 23 | theorem Truthy.some_nonzero : Truthy (some v) = (v ≠ 0) := by 24 | simp [Truthy] 25 | 26 | @[simp] 27 | theorem Truthy.not_none : Truthy none = False := by 28 | simp [Truthy] 29 | 30 | @[simp] 31 | theorem Truthy.eval_const : Truthy (Expr.eval σ (.const v)) = (v ≠ 0) := by 32 | simp [Truthy, Expr.eval] 33 | 34 | /-- 35 | Falsiness: the result of evaluating an expression is "falsy" if it's 0 36 | -/ 37 | def Falsy (v : Option Value) : Prop := v = some 0 38 | 39 | @[simp] 40 | theorem Falsy.eval_const : Falsy (Expr.eval σ (.const v)) = (v = 0) := by 41 | simp [Falsy, Expr.eval] 42 | 43 | @[simp] 44 | theorem Falsy.some_zero : Falsy (some v) = (v = 0) := by 45 | simp [Falsy] 46 | 47 | @[simp] 48 | theorem Falsy.not_none : Falsy none = False := by 49 | simp [Falsy] 50 | 51 | 52 | instance : Decidable (Falsy v) := inferInstanceAs (Decidable (v = some 0)) 53 | 54 | theorem Truthy.not_falsy : Truthy v → ¬Falsy v := by 55 | intro h1 h2 56 | simp [Truthy, Falsy] at * 57 | cases v <;> simp at * <;> contradiction 58 | 59 | 60 | namespace Stmt 61 | 62 | 63 | /-- 64 | Big-step semantics: `BigStep σ s σ'` means that running the program `s` in the starting state `σ` is 65 | termination with the final state `σ'`. 66 | -/ 67 | inductive BigStep : Env → Stmt → Env → Prop where 68 | | skip : 69 | BigStep σ (imp {skip;}) σ 70 | | seq : 71 | BigStep σ s1 σ' → BigStep σ' s2 σ'' → 72 | BigStep σ (imp{ ~s1 ~s2}) σ'' 73 | | assign : 74 | e.eval σ = some v → 75 | BigStep σ (imp {~x := ~e;}) (σ.set x v) 76 | | ifTrue : 77 | Truthy (c.eval σ) → BigStep σ s1 σ' → 78 | BigStep σ (imp {if (~c) {~s1} else {~s2}}) σ' 79 | | ifFalse : 80 | Falsy (c.eval σ) → BigStep σ s2 σ' → 81 | BigStep σ (imp {if (~c) {~s1} else {~s2}}) σ' 82 | | whileTrue : 83 | Truthy (c.eval σ) → 84 | BigStep σ body σ' → 85 | BigStep σ' (imp {while (~c) {~body}}) σ'' → 86 | BigStep σ (imp {while (~c) {~body}}) σ'' 87 | | whileFalse : 88 | Falsy (c.eval σ) → 89 | BigStep σ (imp {while (~c) {~body}}) σ 90 | 91 | attribute [simp] BigStep.skip 92 | 93 | /-- 94 | `swap` terminates, and the resulting environment contains swapped inputs. 95 | -/ 96 | example : ∃σ', BigStep (Env.init 0 |>.set "x" 5 |>.set "y" 22) swap σ' ∧ σ'.get "x" = 22 ∧ σ'.get "y" = 5 := by 97 | unfold swap 98 | apply Exists.intro 99 | constructor 100 | . apply BigStep.seq 101 | . apply BigStep.assign 102 | simp [Expr.eval, Env.get, Env.set] 103 | rfl 104 | . apply BigStep.seq 105 | . apply BigStep.assign 106 | simp [Expr.eval, Env.get, Env.set] 107 | rfl 108 | . simp 109 | apply BigStep.assign 110 | simp [Expr.eval, Env.get, Env.set] 111 | rfl 112 | . simp [Env.get, Env.set] 113 | 114 | /-- 115 | `swap` terminates, and the resulting environment contains swapped inputs. This proof is shorter. 116 | -/ 117 | example : ∃σ', BigStep (Env.init 0 |>.set "x" 5 |>.set "y" 22) swap σ' ∧ σ'.get "x" = 22 ∧ σ'.get "y" = 5 := by 118 | repeat' constructor 119 | 120 | /-- 121 | `swap` terminates, and the resulting environment contains swapped inputs. This version works no 122 | matter what the input values are. 123 | -/ 124 | example : ∃σ', BigStep (Env.init 0 |>.set "x" x |>.set "y" y) swap σ' ∧ σ'.get "x" = y ∧ σ'.get "y" = x := by 125 | repeat' constructor 126 | 127 | /-- 128 | `min` computes the minimum of its inputs. 129 | -/ 130 | example : ∃σ', BigStep (Env.init 0 |>.set "x" x |>.set "y" y) min σ' ∧ if x < y then σ'.get "min" = x else σ'.get "min" = y := by 131 | unfold min 132 | by_cases h : x < y 133 | . apply Exists.intro; constructor 134 | . apply BigStep.ifTrue 135 | . simp [Expr.eval, Expr.BinOp.apply, Env.get, Env.set, *]; decide 136 | . constructor; simp [Expr.eval, Env.get, Env.set]; rfl 137 | . simp [Env.get, Env.set] 138 | intro; contradiction 139 | . apply Exists.intro; constructor 140 | . apply BigStep.ifFalse 141 | . simp [Expr.eval, Expr.BinOp.apply, Env.get, Env.set, *] 142 | . constructor; simp [Expr.eval, Env.get, Env.set]; rfl 143 | . simp [Env.get, Env.set] 144 | intro; contradiction 145 | 146 | def loop := imp {while (1) {skip;}} 147 | 148 | /-- 149 | `loop` is really an infinite loop - there is no final state that it can result in. 150 | -/ 151 | theorem infinite_loop : ¬ BigStep σ loop σ' := by 152 | generalize h' : loop = l 153 | intro h 154 | induction h <;> try (simp [loop, *] at *; done) 155 | case whileTrue => 156 | simp [*] 157 | case whileFalse cFalse => 158 | unfold loop at h' 159 | cases h' 160 | simp at cFalse 161 | contradiction 162 | 163 | /-- Optimizing a program doesn't change its meaning -/ 164 | theorem optimize_ok : BigStep σ s σ' → BigStep σ s.optimize σ' := by 165 | intro h 166 | induction h with simp only [optimize] 167 | | «skip» => constructor 168 | | seq s1 s2 ih1 ih2 => 169 | split 170 | next eq2 => 171 | rw [eq2] at ih1 172 | cases ih1; apply ih2 173 | next eq1 eq2 => 174 | rw [eq1] at ih2 175 | cases ih2; apply ih1 176 | next => 177 | apply BigStep.seq ih1 ih2 178 | | assign m => 179 | constructor 180 | rw [← Expr.optimize_ok] 181 | assumption 182 | | ifTrue isTrue l ih => 183 | split 184 | next isFalse => 185 | rw [Expr.optimize_ok] at isTrue 186 | rw [isFalse] at isTrue 187 | simp [Truthy, Expr.eval] at isTrue 188 | next notFalse _isConst => 189 | apply ih 190 | next => 191 | split 192 | . assumption 193 | . apply BigStep.ifTrue 194 | . rw [← Expr.optimize_ok] 195 | assumption 196 | . assumption 197 | | ifFalse isFalse l ih => 198 | split 199 | next => 200 | apply ih 201 | next nonZero isConst => 202 | rw [Expr.optimize_ok, isConst] at isFalse 203 | simp at isFalse 204 | contradiction 205 | next => 206 | split 207 | . simp [*] 208 | . apply BigStep.ifFalse 209 | . rw [← Expr.optimize_ok] 210 | assumption 211 | . assumption 212 | | whileFalse => 213 | split <;> try simp 214 | apply BigStep.whileFalse 215 | rw [← Expr.optimize_ok] 216 | assumption 217 | | whileTrue isTrue bodyStep nextStep ih1 ih2 => 218 | split 219 | next c isZero => 220 | rw [Expr.optimize_ok, isZero] at isTrue 221 | simp at isTrue 222 | next c isNotZero => 223 | apply BigStep.whileTrue 224 | . rw [← Expr.optimize_ok] 225 | assumption 226 | . apply ih1 227 | . simp [optimize] at ih2 228 | assumption 229 | 230 | /-- 231 | Run a program, with the depth of the recursive calls limited by the `Nat` parameter. Returns `none` 232 | if the program doesn't terminate fast enough or if some other problem means the result is undefined 233 | (e.g. division by zero). 234 | -/ 235 | def run (σ : Env) (s : Stmt) : Nat → Option Env 236 | | 0 => none 237 | | n + 1 => 238 | match s with 239 | | imp {skip;} => 240 | some σ 241 | | imp {~s1 ~s2} => do 242 | let σ' ← run σ s1 n 243 | run σ' s2 n 244 | | imp {~x := ~e;} => do 245 | let v ← e.eval σ 246 | pure (σ.set x v) 247 | | imp {if (~c) {~s1} else {~s2}} => do 248 | let v ← c.eval σ 249 | if v = 0 then 250 | run σ s2 n 251 | else 252 | run σ s1 n 253 | | imp {while (~c) {~s1}} => do 254 | let v ← c.eval σ 255 | if v = 0 then 256 | pure σ 257 | else 258 | let σ' ← run σ s1 n 259 | run σ' (imp {while (~c) {~s1}}) n 260 | 261 | /-- 262 | `run` is correct: if it returns an answer, then that final state can be reached by the big-step 263 | semantics. This is not total correctness - `run` could always return `none` - but it does increase 264 | confidence. 265 | -/ 266 | theorem run_some_implies_big_step : run σ s n = some σ' → BigStep σ s σ' := by 267 | intro term 268 | induction σ, s, n using run.induct generalizing σ' <;> unfold run at term <;> simp_all 269 | case case3 σ n s1 s2 ih1 ih2 => 270 | let ⟨σ'', ⟨st1, st2⟩⟩ := term 271 | constructor 272 | . apply ih1 273 | apply st1 274 | . apply ih2 275 | apply st2 276 | case case4 => 277 | let ⟨σ'', ⟨evEq, setEq⟩⟩ := term 278 | subst_eqs 279 | constructor; assumption 280 | case case5 ih1 ih2 => 281 | let ⟨v, ⟨evEq, step⟩⟩ := term 282 | by_cases h : v = 0 283 | . subst_eqs; simp at * 284 | apply BigStep.ifFalse 285 | . simp [Falsy, *] 286 | . exact ih1 step 287 | . subst_eqs; simp at * 288 | apply BigStep.ifTrue 289 | . simp [Truthy, *] 290 | . simp [*] at step 291 | apply ih2; assumption 292 | case case6 ih1 ih2 => 293 | let ⟨v, ⟨evEq, step⟩⟩ := term 294 | by_cases h : v = 0 295 | . subst_eqs; simp at * 296 | apply BigStep.whileFalse 297 | simp [Falsy, *] 298 | . subst_eqs; simp [*] at * 299 | simp [h] at step 300 | let ⟨σ', ⟨oneStep, loopStep⟩⟩ := step 301 | apply BigStep.whileTrue 302 | . rw [evEq] 303 | simp [*] 304 | . apply ih1 305 | exact oneStep 306 | . apply ih2 307 | exact loopStep 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lean @ SSFT 2024 2 | 3 | This repository contains materials used during Leo de Moura and David 4 | Thrane Christiansen's presentation at SSFT 2024. 5 | 6 | ## Getting Ready 7 | 8 | To prepare for the Lean lectures, please install Lean using the 9 | [official instructions](https://lean-lang.org/lean4/doc/quickstart.html). 10 | This will set up everything you need for the summer school. 11 | 12 | If for some reason you can't follow these instructions, please make 13 | sure that the way you install Lean ends up with an installation of 14 | `elan`, the Lean toolchain version manager, rather than a specific 15 | version of Lean itself (e.g. from `nixpkgs`). The installation of elan 16 | worked if you can type `lean +4.7.0 --version` in your home directory, 17 | and it prints out `Lean (version 4.7.0, ...)`, and typing 18 | `lean +nightly-2024-05-19 --version` prints something like 19 | `Lean (version 4.9.0-nightly-2024-04-19, ...)`. 20 | 21 | In order to follow along in the final demo, you'll also need CaDiCaL - 22 | the code has been tested with version 1.9.5. It is available in many 23 | standard software repositories. For the final demo, you may also need 24 | to increase your stack space - `ulimit -s 65520` seems to reliably 25 | work. 26 | 27 | ## Branches 28 | 29 | In case you want to follow along, the sample code in this repository 30 | is organized into branches that represent checkpoints in the 31 | interactive development: 32 | 33 | * `lec1/start` - the initial state of the repository 34 | * `lec1/step1` - the repository after the introductory code 35 | (`Intro.lean`) has been added and the implementation for `filter` 36 | on lists is no longer a stub 37 | * `lec1/step2` - the repository after the implementation from 38 | `lec1/step1` was verified 39 | * `lec1/step3` - the implementation using packed arrays has been 40 | added, but not verified 41 | * `lec1/step4` - the final state 42 | * `lec2/start` - the code from the first lecture is complete, and the 43 | second lecture is in its starting state (missing some proofs and 44 | definitions) 45 | * `lec2/step1` - the expression language has been implemented, with a 46 | verified optimization pass 47 | * `lec2/step2` - the statements of Imp have been implemented, with a 48 | big-step semantics and a verified optimization pass 49 | * `main` and `lec2/step3` - the final state of the imperative language 50 | 51 | ## First Lecture: Introduction to Lean 52 | 53 | This lecture is an introduction to writing programs, and proving them 54 | correct, in Lean. After an introduction to Lean's syntax and the very 55 | basics of using its type theory as a logic, we interactively write a 56 | program that filters JSON objects, and prove it correct with respect 57 | to a number of specifications. 58 | 59 | The following files are part of the first lecture: 60 | 61 | * `Intro.lean` 62 | * The contents of the `tests/` directory 63 | * `Main.lean` - the entry point for the JSON filter 64 | * `Filter.lean` and the contents of `Filter/`: 65 | - `Filter/List.lean` - a verified implementation of filtering lists 66 | - `Filter/Array.lean` - a verified implementation of filtering arrays 67 | - `Filter/Input.lean` - utilities for reading JSON (not part of 68 | lectures) 69 | - `Filter/Query.lean` - a query language (not part of lectures) 70 | 71 | ### Lab Session 72 | 73 | Please experiment with the Lean features and the ideas that were 74 | presented in the first lecture. We don't expect you to have time to 75 | complete all these exercises, so please do the first one and then pick 76 | what you're interested in to work on: 77 | 78 | * Define a function that appends two lists. Prove that the length of 79 | the output is the sum of the lengths of the input. 80 | * Implement `map` for `List`. What are some properties that it should 81 | satisfy? Prove that it does. 82 | * Define a binary tree datatype with two constructors: one represents 83 | the empty tree, and the other represents a labelled branch with a 84 | data item and two subtrees. 85 | * Define a predicate `All : (α → Prop) → Tree α → Prop` such that 86 | `All p t` is true whenever all data items in `t` satisfy `p` 87 | * Define a predicate `Sorted : Tree Nat → Prop` such that empty 88 | trees are sorted and branches are sorted if their left and right 89 | subtrees are sorted, all elements of the left subtree are less 90 | than or equal to the data item, and all elements of the right 91 | subtree are strictly greater than the data item. 92 | * Define a function `Tree.insert : Nat → Tree Nat → Tree Nat` that 93 | inserts a natural number into the given tree. If the input tree 94 | is sorted, then the output should be. 95 | * Prove that if a predicate holds for all natural numbers in a 96 | tree, and it holds for some new number, then it also holds for 97 | the all numbers in the tree with the new number inserted 98 | * Prove that inserting a number into a sorted tree yields a sorted 99 | tree. 100 | * Write an inductive predicate that holds when a tree contains a 101 | given element. It should have signature 102 | `Tree.Mem (x : α) : Tree α → Prop`. 103 | * Prove that if all data items in a tree satisfy a predicate and 104 | some particular item is in the tree (according to `Tree.Mem`), 105 | then that particular item satisfies the predicate. 106 | * Write a function that determines whether a given `Nat` is 107 | contained within a sorted tree, with type `Nat → Tree Nat → Bool`. 108 | * Prove that if all numbers in a tree `t` satisfy a predicate, and 109 | `contains n t = true`, then `n` satisfies the predicate. 110 | * Prove the correctness of `contains`: if a tree is sorted, then 111 | `Tree.Mem n t` is logically equivalent to `contains n t = true`. 112 | * State and prove some further properties of array filter (see the 113 | list filter file if you need inspiration) 114 | * Implement `map` for arrays. State and prove some properties about it. 115 | 116 | ## Second Lecture: 117 | 118 | This lecture demonstrates how to use Lean for implementing languages 119 | and proving things about programs written in them. The programs are 120 | written in Imp, a minimal imperative language that features while 121 | loops, mutable variables, and conditionals. 122 | 123 | The following files are part of the second lecture: 124 | * `Imp.lean` - the top-level module that exists to import the others; 125 | also contains a final demo. 126 | * `Imp/Expr.lean` - definition of an expression datatype and 127 | convenient syntax for writing it 128 | * `Imp/Expr/` - expressions and evaluation: 129 | - `Imp/Expr/Eval.lean` - Evaluating expressions 130 | - `Imp/Expr/Optimize.lean` - Constant-folding optimization of 131 | expressions 132 | - `Imp/Expr/Delab.lean` - Interactive display of expressions (not 133 | part of lectures) 134 | * `Imp/Stmt.lean` - definition of a statement datatype and convenient 135 | syntax for writing it 136 | - `Imp/Stmt/Basic.lean` - The core datastructure and syntax for statements 137 | - `Imp/Stmt/Optimize.lean` - Optimization of statements 138 | - `Imp/Stmt/BigStep.lean` - Operational semantics and proof that 139 | optimization is correct 140 | - `Imp/Stmt/Delab.lean` - Interactive display of statements (not 141 | part of lectures) 142 | 143 | ### Lab Session 144 | 145 | These exercises are intended to be done in the context of the 146 | development from the second lecture. 147 | 148 | * Add a bitwise xor operator to Expr. Lean's bitwise xor operator is 149 | `^^^`. 150 | * Add a new arithmetic identity to the optimizer for `Expr` and 151 | update the correctness proof accordingly. For example, it could 152 | replace `E + 0` with `E`. Be careful to ensure that it in fact 153 | makes sure that optimizations of programs that divide by zero still 154 | divide by zero. 155 | * These exercises don't require modifications to the `Stmt` datatype: 156 | * Add a unary `if` statement (that is, one without an `else`clause) 157 | to the user-facing syntax for `Stmt` 158 | * Add a `do...while...` statement to the surface syntax that 159 | executes the body at least once 160 | * Add a failure handler expression to `Expr`. The expression 161 | `try E1 then E2` should have `E1`'s value, unless the value is 162 | undefined, in which case it has `E2`'s value. Update the concrete 163 | syntax and the evaluator accordingly. 164 | * Add a random number generator to Imp: 165 | * Add a new statement that assigns a pseudorandom value to a given 166 | variable, and give it a concrete syntax 167 | * Define a pseudorandom number generator as a two-field structure 168 | with a state and a function from a state to a pair of a fresh 169 | state and a value. Implement a generator - there's no need for it 170 | to be secure, and a generator that just counts upwards is fine 171 | for the purposes of this exercise. 172 | * Update the big-step semantics to thread a random number generator 173 | through program execution. Provide a big-step rule for the `rand` 174 | statement and update the rest of Imp as needed so all the proofs 175 | go through. 176 | 177 | 178 | ## Tactic Cheatsheet 179 | 180 | These are a few of the proof tactics that may be useful for the lab 181 | sessions, along with summaries of the arguments and configuration 182 | options that we think are most relevant: 183 | 184 | * `assumption` - if a local hypothesis solves the goal, use it 185 | * `contradiction` - search the local context for an "obvious" 186 | contradiction, and close the goal if it's found 187 | * `omega` - a Presburger arithmetic solver that can take care of many 188 | goals involving arithmetic 189 | * `intro x ...` - Transforms a goal like `A -> ... -> C` into `C`, 190 | with `A ...` made into assumptions named `x ...` 191 | * `x` can be replaced with a pattern when `A` has exactly one 192 | constructor 193 | * `unfold f` - replaces `f` with its definition in the goal 194 | * `unfold f at h` - replaces `f` with its definition in hypothesis 195 | `h` 196 | * `simp` - simplify the goal using a collection of rewrite rules. If 197 | `simp` makes the goal simple enough, it will go ahead and solve it. 198 | * `simp [r, ...]` - simplify the goal using additional rules `r, 199 | ...`. Rule possibilities include definition names, which causes 200 | them to be unfolded; proofs of equalities, which causes the left 201 | side to be replaced with the right side; and `*`, which causes 202 | local assumptions to be taken into account 203 | * `simp at h` - simplify hypothesis `h` instead of the goal (can be 204 | combined with rules list) 205 | * `simp_all` - perform as much simplification as possible 206 | * `rw [r1, ...]` - apply rewrites to the goal, one after the 207 | other. Each rewrite is the name of a theorem or assumption that 208 | proves an equality, and the left side is replaced with the right 209 | side. Preceding the rule with `←` causes the right side to be 210 | replaced with the left. Additional assumptions of the equality 211 | theorems are added as new goals. Can also be used with `at` to 212 | rewrite in an assumption. 213 | * `apply l` - apply the lemma `l` to the goal, matching up the 214 | lemma's conclusion with the goal and creating new goals for each 215 | assumption of the lemma that must be satisfied 216 | * `apply?` - search the Lean libraries for lemmas that might be 217 | relevant 218 | * `exact l` - just like `apply`, except fails if it introduces new 219 | goals 220 | * `exact?` - search the Lean libraries for lemmas that close the 221 | goal 222 | * `constructor` - apply the first type-correct constructor of the 223 | goal type 224 | * `T1 <;> T2` - runs `T1`, then runs `T2` in each subgoal that it 225 | creates 226 | * `repeat T` - runs `T` repeatedly until it fails 227 | * `repeat' T` - runs `T` repeatedly in each subgoal until it fails 228 | * `try T` - runs `T`, resetting the proof state if `T` fails 229 | * `. TACS` - in a context with multiple goals, focuses on the 230 | first. Fails if tactics `TACS` doesn't result in zero open goals. 231 | * `next =>` - in a context with multiple goals, focuses on the 232 | first. Fails if included tactics don't result in zero open goals. 233 | * `next h ...=>` - in a context with multiple goals, focuses on the 234 | first, assigning the names `h ...` to its unnamed 235 | assumptions. Fails if included tactics don't result in zero open 236 | goals. 237 | * `case c h ... =>` - in a context with multiple goals, focuses on 238 | the one named `c`. Otherwise like `next h ... =>`. 239 | * `split` - in a goal that contains an `if` or `match`, creates one 240 | new goal for each possible path of execution. 241 | * `cases e` - create a new goal for each constructor of a datatype or 242 | inductively defined proposition that is `e`'s type 243 | * `cases e with | c h ... => TACS ...` - like `cases`, but requires 244 | an explicit case for each goal `c` with hypotheses `h ...` 245 | * `induction x` - create a new goal for each constructor of a datatype 246 | or inductively defined proposition, assuming the current goal for 247 | each recursive occurrence (that is, with indution hypotheses). 248 | * `induction e with | c h ... => TACS ...` - like `induction`, but 249 | requires an explicit case for each goal `c` with hypotheses `h 250 | ...` 251 | * `induction e using i` - like `induction`, but uses the induction 252 | principle `i` instead of the default one 253 | * `have x : t := e` - introduce a new local assumption `x : t`, where 254 | `e` proves `t` (`e` often begins with `by`) 255 | * `x` can be omitted; in this case, the assumption is named `this` 256 | * `: t` can be omitted when `e`'s type can be inferred 257 | * `x` can be replaced with a pattern when `e`'s type has at most 258 | one constructor 259 | 260 | --------------------------------------------------------------------------------