Skip to content

Commit

Permalink
Merge pull request #33 from pandaman64/regex-literal
Browse files Browse the repository at this point in the history
Compile-time regex compilation
  • Loading branch information
pandaman64 authored Dec 8, 2024
2 parents c326973 + 675fe4f commit b48566e
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 1 deletion.
20 changes: 20 additions & 0 deletions Regex/NFA/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ theorem Node.inBounds_of_inBounds_of_le {n : Node} (h : n.inBounds size) (le : s
next => exact Nat.lt_of_lt_of_le h le
next => exact Nat.lt_of_lt_of_le h le

instance Node.decInBounds {node : Node} {size : Nat} : Decidable (node.inBounds size) :=
match node with
| .done => isTrue trivial
| .fail => isTrue trivial
| .epsilon next => inferInstanceAs (Decidable (next < size))
| .char _ next => inferInstanceAs (Decidable (next < size))
| .sparse _ next => inferInstanceAs (Decidable (next < size))
| .split next₁ next₂ => inferInstanceAs (Decidable (next₁ < size ∧ next₂ < size))
| .save _ next => inferInstanceAs (Decidable (next < size))

end Regex.NFA

namespace Regex
Expand Down Expand Up @@ -151,5 +161,15 @@ theorem done_WellFormed : done.WellFormed :=
| ⟨_ + 1, isLt⟩ => contradiction
⟨start_lt, inBounds⟩

instance decWellFormed (nfa : NFA) : Decidable nfa.WellFormed :=
let decStartLt := inferInstanceAs (Decidable (nfa.start < nfa.nodes.size))
match decStartLt with
| isTrue h₁ =>
let decInBounds := inferInstanceAs (Decidable (∀ i : Fin nfa.nodes.size, nfa[i].inBounds nfa.nodes.size))
match decInBounds with
| isTrue h₂ => isTrue ⟨h₁, h₂⟩
| isFalse h₂ => isFalse (fun h => absurd h.2 h₂)
| isFalse h₁ => isFalse (fun h => absurd h.1 h₁)

end NFA
end Regex
1 change: 1 addition & 0 deletions Regex/Regex.lean
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import Regex.Regex.Basic
import Regex.Regex.Matches
import Regex.Regex.Captures
import Regex.Regex.Utilities
import Regex.Regex.Elab
79 changes: 79 additions & 0 deletions Regex/Regex/Elab.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Regex.Regex.Basic
import Lean

open Regex.Data (PerlClassKind PerlClass Class Classes)
open Regex NFA Node
open Lean Syntax Elab Term

set_option autoImplicit false

namespace Regex.Elab

-- A term representing a proof of `prop` given by letting kernel decide `prop`
-- using an `Decidable` instance `inst`.
private def mkDecidableProof (prop : Expr) (inst : Expr) : Expr :=
let refl := mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Bool) (mkConst ``true)
mkApp3 (mkConst ``of_decide_eq_true) prop inst refl

instance : ToExpr PerlClassKind where
toTypeExpr := mkConst ``PerlClassKind
toExpr
| .digit => mkConst ``PerlClassKind.digit
| .space => mkConst ``PerlClassKind.space
| .word => mkConst ``PerlClassKind.word

instance : ToExpr PerlClass where
toTypeExpr := mkConst ``PerlClass
toExpr pc := mkApp2 (mkConst ``PerlClass.mk) (toExpr pc.negated) (toExpr pc.kind)

instance : ToExpr Class where
toTypeExpr := mkConst ``Class
toExpr
| .single c => .app (mkConst ``Class.single) (toExpr c)
| .range s e _ =>
let s := toExpr s
let e := toExpr e
-- Construct a term representing `s ≤ e` using a decidable instance.
let leType := mkApp4 (mkConst ``LE.le [0]) (mkConst ``Char) (mkConst ``Char.instLE) s e
let leInstance := mkApp2 (mkConst ``Char.instDecidableLe) s e
let le := mkDecidableProof leType leInstance

mkApp3 (mkConst ``Class.range) s e le
| .perl pc => .app (mkConst ``Class.perl) (toExpr pc)

instance : ToExpr Classes where
toTypeExpr := mkConst ``Classes
toExpr cs := mkApp2 (mkConst ``Classes.mk) (toExpr cs.negated) (toExpr cs.classes)

instance : ToExpr Node where
toTypeExpr := mkConst ``Node
toExpr
| .done => mkConst ``Node.done
| .fail => mkConst ``Node.fail
| .epsilon next => .app (mkConst ``Node.epsilon) (toExpr next)
| .char c next => mkApp2 (mkConst ``Node.char) (toExpr c) (toExpr next)
| .sparse cs next => mkApp2 (mkConst ``Node.sparse) (toExpr cs) (toExpr next)
| .split next₁ next₂ => mkApp2 (mkConst ``Node.split) (toExpr next₁) (toExpr next₂)
| .save tag next => mkApp2 (mkConst ``Node.save) (toExpr tag) (toExpr next)

instance : ToExpr NFA where
toTypeExpr := mkConst ``NFA
toExpr nfa := mkApp2 (mkConst ``NFA.mk) (toExpr nfa.nodes) (toExpr nfa.start)

instance : ToExpr Regex where
toTypeExpr := mkConst ``Regex
toExpr re :=
let nfa := toExpr re.nfa
-- Construct a term representing `nfa.WellFormed` using a decidable instance.
let wfType := Expr.app (mkConst ``NFA.WellFormed) nfa
let wfInstance := Expr.app (mkConst ``NFA.decWellFormed) nfa
let wf := mkDecidableProof wfType wfInstance
let maxTag := toExpr re.maxTag
mkApp3 (mkConst ``Regex.mk) nfa wf maxTag

elab "re!" lit:str : term => do
match Regex.parse lit.getString with
| Except.ok re => return toExpr re
| Except.error e => throwError s!"failed to parse regex: {e}"

end Regex.Elab
3 changes: 2 additions & 1 deletion Test.lean
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ def main : IO Unit := do
IO.println replaced

-- Search/replace all non-overlapping matches
let regex := Regex.parse! "もも"
-- The re! literal checks for regex errors at compile time and creates a compiled `Regex` object
let regex := re!"もも"
let haystack := "すもももももももものうち"

-- prints: #[(3, 9), (9, 15), (15, 21), (21, 27)]
Expand Down

0 comments on commit b48566e

Please sign in to comment.