mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-26 23:04:07 +00:00
Compare commits
64 Commits
fix-worktr
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f424b371e | ||
|
|
d56424b587 | ||
|
|
144db355ea | ||
|
|
0975b7136a | ||
|
|
ccef9588ae | ||
|
|
a8bbc95d9f | ||
|
|
a54eafb84f | ||
|
|
6f2745d88b | ||
|
|
25c71d91aa | ||
|
|
db491ddd35 | ||
|
|
e8c3485e08 | ||
|
|
dee571e13b | ||
|
|
51f67be2bd | ||
|
|
c77f2124fb | ||
|
|
d634f80149 | ||
|
|
40cdec76c5 | ||
|
|
4227765e2b | ||
|
|
438d1f1fe1 | ||
|
|
4786e082dc | ||
|
|
bd5fb4e90c | ||
|
|
e60078db3b | ||
|
|
f7102363de | ||
|
|
ce073771b1 | ||
|
|
dec394d3a4 | ||
|
|
6457e3686f | ||
|
|
c14fa66068 | ||
|
|
d0aa7d2faa | ||
|
|
4117ceaf84 | ||
|
|
a824e5b85e | ||
|
|
83c6f6e5ac | ||
|
|
9ffd748104 | ||
|
|
fd8d89853b | ||
|
|
0260c91d03 | ||
|
|
7ef25b8fe3 | ||
|
|
50544489a9 | ||
|
|
e9a8b965aa | ||
|
|
0f277c72bf | ||
|
|
59ce52473a | ||
|
|
2b55144c3f | ||
|
|
c381c62060 | ||
|
|
e6df474dd9 | ||
|
|
e0de32ad48 | ||
|
|
fb1dc9112b | ||
|
|
86175bea00 | ||
|
|
9eb249e38c | ||
|
|
b5036e4d81 | ||
|
|
fb1eb9aaa7 | ||
|
|
33e63bb6c3 | ||
|
|
482d7a11f2 | ||
|
|
aef0cea683 | ||
|
|
720cbd6434 | ||
|
|
26ad4d6972 | ||
|
|
4a17b2f471 | ||
|
|
fcdd9d1ae8 | ||
|
|
47427f8c77 | ||
|
|
08595c5f8f | ||
|
|
019b104a7d | ||
|
|
2e421c9970 | ||
|
|
e381960614 | ||
|
|
346c9cb16a | ||
|
|
189cea9f80 | ||
|
|
b9028fa6e9 | ||
|
|
0c0edcc96c | ||
|
|
9f4db470c4 |
43
.github/workflows/build-template.yml
vendored
43
.github/workflows/build-template.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
include: ${{fromJson(inputs.config)}}
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
runs-on: ${{ endsWith(matrix.os, '-with-cache') && fromJSON(format('["{0}", "nscloud-git-mirror-1gb"]', matrix.os)) || matrix.os }}
|
||||
runs-on: ${{ endsWith(matrix.os, '-with-cache') && fromJSON(format('["{0}", "nscloud-git-mirror-5gb"]', matrix.os)) || matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell || 'nix develop -c bash -euxo pipefail {0}' }}
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
# (needs to be after "Install *" to use the right shell)
|
||||
- name: CI Merge Checkout
|
||||
run: |
|
||||
git fetch --depth=1 origin ${{ github.sha }}
|
||||
git fetch --depth=${{ matrix.name == 'Linux Lake (Cached)' && '10' || '1' }} origin ${{ github.sha }}
|
||||
git checkout FETCH_HEAD flake.nix flake.lock script/prepare-* tests/elab/importStructure.lean
|
||||
if: github.event_name == 'pull_request'
|
||||
# (needs to be after "Checkout" so files don't get overridden)
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
else
|
||||
echo "TARGET_STAGE=stage1" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build
|
||||
- name: Configure Build
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
[ -d build ] || mkdir build
|
||||
@@ -162,7 +162,21 @@ jobs:
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. --preset ${{ matrix.CMAKE_PRESET || 'release' }} -B . ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
time make $TARGET_STAGE -j$NPROC
|
||||
- name: Build Stage 0 & Configure Stage 1
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
time make -C build stage1-configure -j$NPROC
|
||||
- name: Download Lake Cache
|
||||
if: matrix.name == 'Linux Lake (Cached)'
|
||||
run: |
|
||||
cd src
|
||||
../build/stage0/bin/lake cache get --repo=${{ github.repository }}
|
||||
timeout-minutes: 20 # prevent excessive hanging from network issues
|
||||
continue-on-error: true
|
||||
- name: Build Target Stage
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
time make -C build $TARGET_STAGE -j$NPROC
|
||||
# Should be done as early as possible and in particular *before* "Check rebootstrap" which
|
||||
# changes the state of stage1/
|
||||
- name: Save Cache
|
||||
@@ -181,6 +195,21 @@ jobs:
|
||||
build/stage1/**/*.c
|
||||
build/stage1/**/*.c.o*' || '' }}
|
||||
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
|
||||
- name: Upload Lake Cache
|
||||
# Caching on cancellation created some mysterious issues perhaps related to improper build
|
||||
# shutdown. Also, since this needs access to secrets, it cannot be run on forks.
|
||||
if: matrix.name == 'Linux Lake' && !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
run: |
|
||||
curl --version
|
||||
cd src
|
||||
time ../build/stage0/bin/lake build -o ../build/lake-mappings.jsonl
|
||||
time ../build/stage0/bin/lake cache put ../build/lake-mappings.jsonl --repo=${{ github.repository }}
|
||||
env:
|
||||
LAKE_CACHE_KEY: ${{ secrets.LAKE_CACHE_KEY }}
|
||||
LAKE_CACHE_ARTIFACT_ENDPOINT: ${{ vars.LAKE_CACHE_ENDPOINT }}/a1
|
||||
LAKE_CACHE_REVISION_ENDPOINT: ${{ vars.LAKE_CACHE_ENDPOINT }}/r1
|
||||
timeout-minutes: 20 # prevent excessive hanging from network issues
|
||||
continue-on-error: true
|
||||
- name: Install
|
||||
run: |
|
||||
make -C build/$TARGET_STAGE install
|
||||
@@ -247,10 +276,10 @@ jobs:
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
set -e
|
||||
# clean rebuild in case of Makefile changes/Lake does not detect uncommited stage 0
|
||||
# changes yet
|
||||
git config user.email "stage0@lean-fro.org"
|
||||
git config user.name "update-stage0"
|
||||
make -C build update-stage0
|
||||
make -C build/stage1 clean-stdlib
|
||||
git commit --allow-empty -m "chore: update-stage0"
|
||||
time make -C build -j$NPROC
|
||||
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/stage1 -j$NPROC
|
||||
if: matrix.check-rebootstrap
|
||||
|
||||
29
.github/workflows/check-empty-pr.yml
vendored
Normal file
29
.github/workflows/check-empty-pr.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Check for empty PR
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check-empty-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
|
||||
- name: Check for empty diff
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
base=$(git merge-base "origin/${{ github.base_ref }}" HEAD)
|
||||
else
|
||||
base=$(git rev-parse HEAD^1)
|
||||
fi
|
||||
if git diff --quiet "$base" HEAD --; then
|
||||
echo "This PR introduces no changes compared to its base branch." | tee "$GITHUB_STEP_SUMMARY"
|
||||
echo "It may be a duplicate of an already-merged PR." | tee -a "$GITHUB_STEP_SUMMARY"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
@@ -76,9 +76,20 @@ jobs:
|
||||
fi
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Scheduled: do nothing if commit already has a different tag
|
||||
# Scheduled: do nothing if commit already has a different tag (e.g. a release tag)
|
||||
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
|
||||
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
||||
HEAD_TAG="$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || true)"
|
||||
if [[ -n "$HEAD_TAG" && "$HEAD_TAG" != "$LEAN_VERSION_STRING" ]]; then
|
||||
echo "HEAD already tagged as ${HEAD_TAG}, skipping nightly"
|
||||
elif git rev-parse "refs/tags/${LEAN_VERSION_STRING}" >/dev/null 2>&1; then
|
||||
# Today's nightly already exists (e.g. from a manual release), create a revision
|
||||
REV=1
|
||||
while git rev-parse "refs/tags/${LEAN_VERSION_STRING}-rev${REV}" >/dev/null 2>&1; do
|
||||
REV=$((REV + 1))
|
||||
done
|
||||
LEAN_VERSION_STRING="${LEAN_VERSION_STRING}-rev${REV}"
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
@@ -244,7 +255,7 @@ jobs:
|
||||
// portable release build: use channel with older glibc (2.26)
|
||||
"name": "Linux release",
|
||||
// usually not a bottleneck so make exclusive to `fast-ci`
|
||||
"os": large && fast ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"os": large && fast ? "nscloud-ubuntu-24.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"release": true,
|
||||
// Special handling for release jobs. We want:
|
||||
// 1. To run it in PRs so developers get PR toolchains (so secondary without tests is sufficient)
|
||||
@@ -265,7 +276,7 @@ jobs:
|
||||
},
|
||||
{
|
||||
"name": "Linux Lake",
|
||||
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"os": large ? "nscloud-ubuntu-24.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"enabled": true,
|
||||
"check-rebootstrap": level >= 1,
|
||||
"check-stage3": level >= 2,
|
||||
@@ -273,7 +284,19 @@ jobs:
|
||||
// NOTE: `test-bench` currently seems to be broken on `ubuntu-latest`
|
||||
"test-bench": large && level >= 2,
|
||||
// We are not warning-free yet on all platforms, start here
|
||||
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror",
|
||||
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror -DUSE_LAKE_CACHE=ON",
|
||||
},
|
||||
{
|
||||
"name": "Linux Lake (Cached)",
|
||||
"os": large ? "nscloud-ubuntu-24.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"enabled": true,
|
||||
"check-rebootstrap": level >= 1,
|
||||
"check-stage3": level >= 2,
|
||||
"test": true,
|
||||
"secondary": true,
|
||||
// NOTE: `test-bench` currently seems to be broken on `ubuntu-latest`
|
||||
"test-bench": large && level >= 2,
|
||||
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror -DUSE_LAKE_CACHE=ON",
|
||||
},
|
||||
{
|
||||
"name": "Linux Reldebug",
|
||||
@@ -287,7 +310,7 @@ jobs:
|
||||
{
|
||||
"name": "Linux fsanitize",
|
||||
// Always run on large if available, more reliable regarding timeouts
|
||||
"os": large ? "nscloud-ubuntu-22.04-amd64-16x32-with-cache" : "ubuntu-latest",
|
||||
"os": large ? "nscloud-ubuntu-24.04-amd64-16x32-with-cache" : "ubuntu-latest",
|
||||
"enabled": level >= 2,
|
||||
// do not fail nightlies on this for now
|
||||
"secondary": level <= 2,
|
||||
|
||||
@@ -236,7 +236,7 @@ def parse_version(version_str):
|
||||
def is_version_gte(version1, version2):
|
||||
"""Check if version1 >= version2, including proper handling of release candidates."""
|
||||
# Check if version1 is a nightly toolchain
|
||||
if version1.startswith("leanprover/lean4:nightly-"):
|
||||
if version1.startswith("leanprover/lean4:nightly-") or version1.startswith("leanprover/lean4-nightly:"):
|
||||
return False
|
||||
return parse_version(version1) >= parse_version(version2)
|
||||
|
||||
|
||||
@@ -14,13 +14,6 @@ repositories:
|
||||
bump-branch: true
|
||||
dependencies: []
|
||||
|
||||
- name: lean4checker
|
||||
url: https://github.com/leanprover/lean4checker
|
||||
toolchain-tag: true
|
||||
stable-branch: true
|
||||
branch: master
|
||||
dependencies: []
|
||||
|
||||
- name: quote4
|
||||
url: https://github.com/leanprover-community/quote4
|
||||
toolchain-tag: true
|
||||
|
||||
@@ -37,7 +37,7 @@ set_option linter.unusedVariables false in -- `s` unused
|
||||
Use a monadic action that may throw an exception by providing explicit success and failure
|
||||
continuations.
|
||||
-/
|
||||
@[always_inline, inline]
|
||||
@[always_inline, inline, expose]
|
||||
def runK {ε α : Type u} (x : ExceptCpsT ε m α) (s : ε) (ok : α → m β) (error : ε → m β) : m β :=
|
||||
x _ ok error
|
||||
|
||||
@@ -83,6 +83,8 @@ of `True`.
|
||||
-/
|
||||
instance : MonadAttach (ExceptCpsT ε m) := .trivial
|
||||
|
||||
@[simp] theorem throw_bind [Monad m] (e : ε) (f : α → ExceptCpsT ε m β) : (throw e >>= f : ExceptCpsT ε m β) = throw e := rfl
|
||||
|
||||
@[simp] theorem run_pure [Monad m] : run (pure x : ExceptCpsT ε m α) = pure (Except.ok x) := rfl
|
||||
|
||||
@[simp] theorem run_lift {α ε : Type u} [Monad m] (x : m α) : run (ExceptCpsT.lift x : ExceptCpsT ε m α) = (x >>= fun a => pure (Except.ok a) : m (Except ε α)) := rfl
|
||||
@@ -91,7 +93,20 @@ instance : MonadAttach (ExceptCpsT ε m) := .trivial
|
||||
|
||||
@[simp] theorem run_bind_lift [Monad m] (x : m α) (f : α → ExceptCpsT ε m β) : run (ExceptCpsT.lift x >>= f : ExceptCpsT ε m β) = x >>= fun a => run (f a) := rfl
|
||||
|
||||
@[simp] theorem run_bind_throw [Monad m] (e : ε) (f : α → ExceptCpsT ε m β) : run (throw e >>= f : ExceptCpsT ε m β) = run (throw e) := rfl
|
||||
@[deprecated throw_bind (since := "2026-03-13")]
|
||||
theorem run_bind_throw [Monad m] (e : ε) (f : α → ExceptCpsT ε m β) : run (throw e >>= f : ExceptCpsT ε m β) = run (throw e) := rfl
|
||||
|
||||
@[simp] theorem runK_pure :
|
||||
runK (pure x : ExceptCpsT ε m α) s ok error = ok x := rfl
|
||||
|
||||
@[simp] theorem runK_lift {α ε : Type u} [Monad m] (x : m α) (s : ε) (ok : α → m β) (error : ε → m β) :
|
||||
runK (ExceptCpsT.lift x : ExceptCpsT ε m α) s ok error = x >>= ok := rfl
|
||||
|
||||
@[simp] theorem runK_throw [Monad m] :
|
||||
runK (throw e : ExceptCpsT ε m β) s ok error = error e := rfl
|
||||
|
||||
@[simp] theorem runK_bind_lift [Monad m] (x : m α) (f : α → ExceptCpsT ε m β) :
|
||||
runK (ExceptCpsT.lift x >>= f : ExceptCpsT ε m β) s ok error = x >>= fun a => runK (f a) s ok error := rfl
|
||||
|
||||
@[simp] theorem runCatch_pure [Monad m] : runCatch (pure x : ExceptCpsT α m α) = pure x := rfl
|
||||
|
||||
@@ -102,6 +117,7 @@ instance : MonadAttach (ExceptCpsT ε m) := .trivial
|
||||
|
||||
@[simp] theorem runCatch_bind_lift [Monad m] (x : m α) (f : α → ExceptCpsT β m β) : runCatch (ExceptCpsT.lift x >>= f : ExceptCpsT β m β) = x >>= fun a => runCatch (f a) := rfl
|
||||
|
||||
@[simp] theorem runCatch_bind_throw [Monad m] (e : β) (f : α → ExceptCpsT β m β) : runCatch (throw e >>= f : ExceptCpsT β m β) = pure e := rfl
|
||||
@[deprecated throw_bind (since := "2026-03-13")]
|
||||
theorem runCatch_bind_throw [Monad m] (e : β) (f : α → ExceptCpsT β m β) : runCatch (throw e >>= f : ExceptCpsT β m β) = pure e := rfl
|
||||
|
||||
end ExceptCpsT
|
||||
|
||||
@@ -113,7 +113,7 @@ public theorem _root_.List.min?_toArray [Min α] {l : List α} :
|
||||
· simp [List.min_toArray, List.min_eq_get_min?, - List.get_min?]
|
||||
· simp_all
|
||||
|
||||
@[simp, grind =]
|
||||
@[simp, grind =, cbv_eval ←]
|
||||
public theorem min?_toList [Min α] {xs : Array α} :
|
||||
xs.toList.min? = xs.min? := by
|
||||
cases xs; simp
|
||||
@@ -153,7 +153,7 @@ public theorem _root_.List.max?_toArray [Max α] {l : List α} :
|
||||
· simp [List.max_toArray, List.max_eq_get_max?, - List.get_max?]
|
||||
· simp_all
|
||||
|
||||
@[simp, grind =]
|
||||
@[simp, grind =, cbv_eval ←]
|
||||
public theorem max?_toList [Max α] {xs : Array α} :
|
||||
xs.toList.max? = xs.max? := by
|
||||
cases xs; simp
|
||||
|
||||
@@ -66,3 +66,8 @@ theorem BEq.neq_of_beq_of_neq [BEq α] [PartialEquivBEq α] {a b c : α} :
|
||||
instance (priority := low) [BEq α] [LawfulBEq α] : EquivBEq α where
|
||||
symm h := beq_iff_eq.2 <| Eq.symm <| beq_iff_eq.1 h
|
||||
trans hab hbc := beq_iff_eq.2 <| (beq_iff_eq.1 hab).trans <| beq_iff_eq.1 hbc
|
||||
|
||||
theorem equivBEq_of_iff_apply_eq [BEq α] (f : α → β) (hf : ∀ a b, a == b ↔ f a = f b) : EquivBEq α where
|
||||
rfl := by simp [hf]
|
||||
symm := by simp [hf, eq_comm]
|
||||
trans hab hbc := (hf _ _).2 (Eq.trans ((hf _ _).1 hab) ((hf _ _).1 hbc))
|
||||
|
||||
@@ -98,4 +98,8 @@ theorem toNat_inj {c d : Char} : c.toNat = d.toNat ↔ c = d := by
|
||||
theorem isDigit_iff_toNat {c : Char} : c.isDigit ↔ '0'.toNat ≤ c.toNat ∧ c.toNat ≤ '9'.toNat := by
|
||||
simp [isDigit, UInt32.le_iff_toNat_le]
|
||||
|
||||
@[simp]
|
||||
theorem toNat_mk {val : UInt32} {h} : (Char.mk val h).toNat = val.toNat := by
|
||||
simp [← toNat_val]
|
||||
|
||||
end Char
|
||||
|
||||
@@ -66,7 +66,7 @@ lists are prepend-only, this `toListRev` is usually more efficient that `toList`
|
||||
If the iterator is not finite, this function might run forever. The variant
|
||||
`it.ensureTermination.toListRev` always terminates after finitely many steps.
|
||||
-/
|
||||
@[always_inline, inline]
|
||||
@[always_inline, inline, cbv_opaque]
|
||||
def Iter.toListRev {α : Type w} {β : Type w}
|
||||
[Iterator α Id β] (it : Iter (α := α) β) : List β :=
|
||||
it.toIterM.toListRev.run
|
||||
|
||||
@@ -226,7 +226,7 @@ any element emitted by the iterator {name}`it`.
|
||||
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
|
||||
examined in order of iteration.
|
||||
-/
|
||||
@[inline]
|
||||
@[inline, cbv_opaque]
|
||||
def Iter.any {α β : Type w}
|
||||
[Iterator α Id β] [IteratorLoop α Id Id]
|
||||
(p : β → Bool) (it : Iter (α := α) β) : Bool :=
|
||||
@@ -292,7 +292,7 @@ all element emitted by the iterator {name}`it`.
|
||||
{lit}`O(|xs|)`. Short-circuits upon encountering the first match. The elements in {name}`it` are
|
||||
examined in order of iteration.
|
||||
-/
|
||||
@[inline]
|
||||
@[inline, cbv_opaque]
|
||||
def Iter.all {α β : Type w}
|
||||
[Iterator α Id β] [IteratorLoop α Id Id]
|
||||
(p : β → Bool) (it : Iter (α := α) β) : Bool :=
|
||||
@@ -644,7 +644,7 @@ Examples:
|
||||
* `[7, 6].iter.first? = some 7`
|
||||
* `[].iter.first? = none`
|
||||
-/
|
||||
@[inline]
|
||||
@[inline, cbv_opaque]
|
||||
def Iter.first? {α β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
|
||||
(it : Iter (α := α) β) : Option β :=
|
||||
it.toIterM.first?.run
|
||||
|
||||
@@ -110,6 +110,7 @@ theorem Iter.reverse_toListRev_ensureTermination [Iterator α Id β] [Finite α
|
||||
it.ensureTermination.toListRev.reverse = it.toList := by
|
||||
simp
|
||||
|
||||
@[cbv_eval]
|
||||
theorem Iter.toListRev_eq {α β} [Iterator α Id β] [Finite α Id]
|
||||
{it : Iter (α := α) β} :
|
||||
it.toListRev = it.toList.reverse := by
|
||||
|
||||
@@ -637,6 +637,7 @@ theorem Iter.any_eq_forIn {α β : Type w} [Iterator α Id β]
|
||||
return .yield false)).run := by
|
||||
simp [any_eq_anyM, anyM_eq_forIn]
|
||||
|
||||
@[cbv_eval ←]
|
||||
theorem Iter.any_toList {α β : Type w} [Iterator α Id β]
|
||||
[Finite α Id] [IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
|
||||
{it : Iter (α := α) β} {p : β → Bool} :
|
||||
@@ -727,6 +728,7 @@ theorem Iter.all_eq_forIn {α β : Type w} [Iterator α Id β]
|
||||
return .done false)).run := by
|
||||
simp [all_eq_allM, allM_eq_forIn]
|
||||
|
||||
@[cbv_eval ←]
|
||||
theorem Iter.all_toList {α β : Type w} [Iterator α Id β]
|
||||
[Finite α Id] [IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
|
||||
{it : Iter (α := α) β} {p : β → Bool} :
|
||||
@@ -954,7 +956,7 @@ theorem Iter.first?_eq_match_step {α β : Type w} [Iterator α Id β] [Iterator
|
||||
generalize it.toIterM.step.run.inflate = s
|
||||
rcases s with ⟨_|_|_, _⟩ <;> simp [Iter.first?_eq_first?_toIterM]
|
||||
|
||||
@[simp, grind =]
|
||||
@[simp, grind =, cbv_eval ←]
|
||||
theorem Iter.head?_toList {α β : Type w} [Iterator α Id β] [IteratorLoop α Id Id]
|
||||
[Finite α Id] [LawfulIteratorLoop α Id Id] {it : Iter (α := α) β} :
|
||||
it.toList.head? = it.first? := by
|
||||
|
||||
@@ -298,7 +298,7 @@ theorem ofDigitChars_cons {c : Char} {cs : List Char} {init : Nat} :
|
||||
simp [ofDigitChars]
|
||||
|
||||
theorem ofDigitChars_cons_digitChar_of_lt_ten {n : Nat} (hn : n < 10) {cs : List Char} {init : Nat} :
|
||||
ofDigitChars 10 (n.digitChar :: cs) init = ofDigitChars 10 cs (10 * init + n) := by
|
||||
ofDigitChars b (n.digitChar :: cs) init = ofDigitChars b cs (b * init + n) := by
|
||||
simp [ofDigitChars_cons, Nat.toNat_digitChar_sub_48_of_lt_ten hn]
|
||||
|
||||
theorem ofDigitChars_eq_ofDigitChars_zero {l : List Char} {init : Nat} :
|
||||
@@ -320,15 +320,17 @@ theorem ofDigitChars_replicate_zero {n : Nat} : ofDigitChars b (List.replicate n
|
||||
| zero => simp
|
||||
| succ n ih => simp [List.replicate_succ, ofDigitChars_cons, ih, Nat.pow_succ, Nat.mul_assoc]
|
||||
|
||||
@[simp]
|
||||
theorem ofDigitChars_toDigits {n : Nat} : ofDigitChars 10 (toDigits 10 n) 0 = n := by
|
||||
have : 1 < 10 := by decide
|
||||
induction n using base_induction 10 this with
|
||||
theorem ofDigitChars_toDigits {b n : Nat} (hb' : 1 < b) (hb : b ≤ 10) : ofDigitChars b (toDigits b n) 0 = n := by
|
||||
induction n using base_induction b hb' with
|
||||
| single m hm =>
|
||||
simp [Nat.toDigits_of_lt_base hm, ofDigitChars_cons_digitChar_of_lt_ten hm]
|
||||
simp [Nat.toDigits_of_lt_base hm, ofDigitChars_cons_digitChar_of_lt_ten (by omega : m < 10)]
|
||||
| digit m k hk hm ih =>
|
||||
rw [← Nat.toDigits_append_toDigits this hm hk,
|
||||
rw [← Nat.toDigits_append_toDigits hb' hm hk,
|
||||
ofDigitChars_append, ih, Nat.toDigits_of_lt_base hk,
|
||||
ofDigitChars_cons_digitChar_of_lt_ten hk, ofDigitChars_nil]
|
||||
ofDigitChars_cons_digitChar_of_lt_ten (Nat.lt_of_lt_of_le hk hb), ofDigitChars_nil]
|
||||
|
||||
@[simp]
|
||||
theorem ofDigitChars_ten_toDigits {n : Nat} : ofDigitChars 10 (toDigits 10 n) 0 = n :=
|
||||
ofDigitChars_toDigits (by decide) (by decide)
|
||||
|
||||
end Nat
|
||||
|
||||
@@ -193,6 +193,7 @@ public theorem Array.toSubarray_eq_toSubarray_of_min_eq_min {xs : Array α}
|
||||
simp [*]; omega
|
||||
· simp
|
||||
|
||||
@[cbv_eval]
|
||||
public theorem Array.toSubarray_eq_min {xs : Array α} {lo hi : Nat} :
|
||||
xs.toSubarray lo hi = ⟨⟨xs, min lo (min hi xs.size), min hi xs.size, Nat.min_le_right _ _,
|
||||
Nat.min_le_right _ _⟩⟩ := by
|
||||
|
||||
@@ -852,6 +852,10 @@ theorem Slice.rawEndPos_copy {s : Slice} : s.copy.rawEndPos = s.rawEndPos := by
|
||||
theorem copy_toSlice {s : String} : s.toSlice.copy = s := by
|
||||
simp [← toByteArray_inj, Slice.toByteArray_copy, ← size_toByteArray]
|
||||
|
||||
@[simp]
|
||||
theorem copy_comp_toSlice : String.Slice.copy ∘ String.toSlice = id := by
|
||||
ext; simp
|
||||
|
||||
theorem Slice.getUTF8Byte_eq_getUTF8Byte_copy {s : Slice} {p : Pos.Raw} {h : p < s.rawEndPos} :
|
||||
s.getUTF8Byte p h = s.copy.getUTF8Byte p (by simpa) := by
|
||||
simp [getUTF8Byte, String.getUTF8Byte, toByteArray_copy, ByteArray.getElem_extract]
|
||||
|
||||
@@ -187,6 +187,9 @@ theorem append_right_inj (s : String) {t₁ t₂ : String} :
|
||||
theorem append_assoc {s₁ s₂ s₃ : String} : s₁ ++ s₂ ++ s₃ = s₁ ++ (s₂ ++ s₃) := by
|
||||
simp [← toByteArray_inj, ByteArray.append_assoc]
|
||||
|
||||
instance : Std.Associative (α := String) (· ++ ·) where
|
||||
assoc _ _ _ := append_assoc
|
||||
|
||||
@[simp]
|
||||
theorem utf8ByteSize_eq_zero_iff {s : String} : s.utf8ByteSize = 0 ↔ s = "" := by
|
||||
refine ⟨fun h => ?_, fun h => h ▸ utf8ByteSize_empty⟩
|
||||
|
||||
@@ -6,29 +6,5 @@ Authors: Markus Himmel
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Iterators.Combinators.FilterMap
|
||||
public import Init.Data.Iterators.Consumers.Collect
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
namespace Std
|
||||
|
||||
/--
|
||||
Convenience function for turning an iterator into a list of strings, provided the output of the
|
||||
iterator implements {name}`ToString`.
|
||||
-/
|
||||
@[inline]
|
||||
public abbrev Iter.toStringList {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(it : Iter (α := α) β) : List String :=
|
||||
it.map toString |>.toList
|
||||
|
||||
/--
|
||||
Convenience function for turning an iterator into an array of strings, provided the output of the
|
||||
iterator implements {name}`ToString`.
|
||||
-/
|
||||
@[inline]
|
||||
public abbrev Iter.toStringArray {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(it : Iter (α := α) β) : Array String :=
|
||||
it.map toString |>.toArray
|
||||
|
||||
end Std
|
||||
public import Init.Data.String.Iter.Basic
|
||||
public import Init.Data.String.Iter.Intercalate
|
||||
|
||||
34
src/Init/Data/String/Iter/Basic.lean
Normal file
34
src/Init/Data/String/Iter/Basic.lean
Normal file
@@ -0,0 +1,34 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Iterators.Combinators.FilterMap
|
||||
public import Init.Data.Iterators.Consumers.Collect
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
namespace Std
|
||||
|
||||
/--
|
||||
Convenience function for turning an iterator into a list of strings, provided the output of the
|
||||
iterator implements {name}`ToString`.
|
||||
-/
|
||||
@[inline]
|
||||
public abbrev Iter.toStringList {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(it : Iter (α := α) β) : List String :=
|
||||
it.map toString |>.toList
|
||||
|
||||
/--
|
||||
Convenience function for turning an iterator into an array of strings, provided the output of the
|
||||
iterator implements {name}`ToString`.
|
||||
-/
|
||||
@[inline]
|
||||
public abbrev Iter.toStringArray {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(it : Iter (α := α) β) : Array String :=
|
||||
it.map toString |>.toArray
|
||||
|
||||
end Std
|
||||
36
src/Init/Data/String/Iter/Intercalate.lean
Normal file
36
src/Init/Data/String/Iter/Intercalate.lean
Normal file
@@ -0,0 +1,36 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Julia Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Iterators.Combinators.Monadic.FilterMap
|
||||
public import Init.Data.String.Basic
|
||||
import Init.Data.String.Slice
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
namespace Std
|
||||
|
||||
/--
|
||||
Appends all the elements in the iterator, in order.
|
||||
-/
|
||||
public def Iter.joinString {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(it : Std.Iter (α := α) β) : String :=
|
||||
(it.map toString).fold (init := "") (· ++ ·)
|
||||
|
||||
/--
|
||||
Appends the elements of the iterator into a string, placing the separator {name}`s` between them.
|
||||
-/
|
||||
@[inline]
|
||||
public def Iter.intercalateString {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(s : String.Slice) (it : Std.Iter (α := α) β) : String :=
|
||||
it.map toString
|
||||
|>.fold (init := none) (fun
|
||||
| none, sl => some sl
|
||||
| some str, sl => some (str ++ s ++ sl))
|
||||
|>.getD ""
|
||||
|
||||
end Std
|
||||
@@ -17,6 +17,9 @@ public import Init.Data.String.Lemmas.Pattern
|
||||
public import Init.Data.String.Lemmas.Slice
|
||||
public import Init.Data.String.Lemmas.Iterate
|
||||
public import Init.Data.String.Lemmas.Intercalate
|
||||
public import Init.Data.String.Lemmas.Iter
|
||||
public import Init.Data.String.Lemmas.Hashable
|
||||
public import Init.Data.String.Lemmas.TakeDrop
|
||||
import Init.Data.Order.Lemmas
|
||||
public import Init.Data.String.Basic
|
||||
import Init.Data.Char.Lemmas
|
||||
|
||||
@@ -7,6 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Basic
|
||||
import all Init.Data.String.Basic
|
||||
import Init.Data.ByteArray.Lemmas
|
||||
import Init.Data.Nat.MinMax
|
||||
|
||||
@@ -56,6 +57,11 @@ theorem singleton_ne_empty {c : Char} : singleton c ≠ "" := by
|
||||
theorem empty_ne_singleton {c : Char} : "" ≠ singleton c := by
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem ofList_cons {c : Char} {l : List Char} :
|
||||
String.ofList (c :: l) = String.singleton c ++ String.ofList l := by
|
||||
simp [← toList_inj]
|
||||
|
||||
@[simp]
|
||||
theorem Slice.Pos.copy_inj {s : Slice} {p₁ p₂ : s.Pos} : p₁.copy = p₂.copy ↔ p₁ = p₂ := by
|
||||
simp [String.Pos.ext_iff, Pos.ext_iff]
|
||||
@@ -244,4 +250,46 @@ theorem Pos.get_ofToSlice {s : String} {p : (s.toSlice).Pos} {h} :
|
||||
@[simp]
|
||||
theorem push_empty {c : Char} : "".push c = singleton c := rfl
|
||||
|
||||
namespace Slice.Pos
|
||||
|
||||
@[simp]
|
||||
theorem nextn_zero {s : Slice} {p : s.Pos} : p.nextn 0 = p := by
|
||||
simp [nextn]
|
||||
|
||||
theorem nextn_add_one {s : Slice} {p : s.Pos} :
|
||||
p.nextn (n + 1) = if h : p = s.endPos then p else (p.next h).nextn n := by
|
||||
simp [nextn]
|
||||
|
||||
@[simp]
|
||||
theorem nextn_endPos {s : Slice} : s.endPos.nextn n = s.endPos := by
|
||||
cases n <;> simp [nextn_add_one]
|
||||
|
||||
end Slice.Pos
|
||||
|
||||
namespace Pos
|
||||
|
||||
theorem nextn_eq_nextn_toSlice {s : String} {p : s.Pos} : p.nextn n = Pos.ofToSlice (p.toSlice.nextn n) :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem nextn_zero {s : String} {p : s.Pos} : p.nextn 0 = p := by
|
||||
simp [nextn_eq_nextn_toSlice]
|
||||
|
||||
theorem nextn_add_one {s : String} {p : s.Pos} :
|
||||
p.nextn (n + 1) = if h : p = s.endPos then p else (p.next h).nextn n := by
|
||||
simp only [nextn_eq_nextn_toSlice, Slice.Pos.nextn_add_one, endPos_toSlice, toSlice_inj]
|
||||
split <;> simp [Pos.next_toSlice]
|
||||
|
||||
theorem nextn_toSlice {s : String} {p : s.Pos} : p.toSlice.nextn n = (p.nextn n).toSlice := by
|
||||
induction n generalizing p with simp_all [nextn_add_one, Slice.Pos.nextn_add_one, apply_dite Pos.toSlice, next_toSlice]
|
||||
|
||||
theorem toSlice_nextn {s : String} {p : s.Pos} : (p.nextn n).toSlice = p.toSlice.nextn n :=
|
||||
nextn_toSlice.symm
|
||||
|
||||
@[simp]
|
||||
theorem nextn_endPos {s : String} : s.endPos.nextn n = s.endPos := by
|
||||
cases n <;> simp [nextn_add_one]
|
||||
|
||||
end Pos
|
||||
|
||||
end String
|
||||
|
||||
@@ -11,6 +11,8 @@ import all Init.Data.String.FindPos
|
||||
import Init.Data.String.OrderInstances
|
||||
import Init.Data.String.Lemmas.Order
|
||||
import Init.Data.Order.Lemmas
|
||||
import Init.Data.Option.Lemmas
|
||||
import Init.ByCases
|
||||
|
||||
public section
|
||||
|
||||
@@ -199,6 +201,10 @@ theorem Pos.prev_eq_iff {s : Slice} {p q : s.Pos} {h} :
|
||||
theorem Pos.prev_lt {s : Slice} {p : s.Pos} {h} : p.prev h < p := by
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev_le {s : Slice} {p : s.Pos} {h} : p.prev h ≤ p :=
|
||||
Std.le_of_lt (by simp)
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev_ne_endPos {s : Slice} {p : s.Pos} {h} : p.prev h ≠ s.endPos :=
|
||||
ne_endPos_of_lt prev_lt
|
||||
@@ -209,6 +215,29 @@ theorem Pos.prevn_le {s : Slice} {p : s.Pos} {n : Nat} : p.prevn n ≤ p := by
|
||||
| case2 p n h ih => exact Std.le_of_lt (by simpa using ih)
|
||||
| case3 => simp
|
||||
|
||||
theorem Pos.ofSliceTo_prev {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} :
|
||||
Pos.ofSliceTo (p.prev h) = (Pos.ofSliceTo p).prev (by simpa [← Pos.ofSliceTo_inj] using h) := by
|
||||
rw [eq_comm, Pos.prev_eq_iff]
|
||||
simp only [Pos.ofSliceTo_lt_ofSliceTo_iff, Pos.le_ofSliceTo_iff]
|
||||
simp [Pos.lt_ofSliceTo_iff]
|
||||
|
||||
theorem Pos.prev_ofSliceTo {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} :
|
||||
(Pos.ofSliceTo p).prev h = Pos.ofSliceTo (p.prev (by simpa [← Pos.ofSliceTo_inj])) := by
|
||||
simp [ofSliceTo_prev]
|
||||
|
||||
theorem Pos.ofSliceFrom_prev {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} :
|
||||
Pos.ofSliceFrom (p.prev h) = (Pos.ofSliceFrom p).prev (by exact ofSliceFrom_ne_startPos h) := by
|
||||
rw [eq_comm, Pos.prev_eq_iff]
|
||||
simp only [Pos.ofSliceFrom_lt_ofSliceFrom_iff, Pos.le_ofSliceFrom_iff]
|
||||
simp [Pos.lt_ofSliceFrom_iff]
|
||||
|
||||
theorem Pos.ofSlice_prev {s : Slice} {p₀ p₁ : s.Pos} {h}
|
||||
{p : (s.slice p₀ p₁ h).Pos} {h'} :
|
||||
Pos.ofSlice (p.prev h') = (Pos.ofSlice p).prev (by exact ofSlice_ne_startPos h') := by
|
||||
rw [eq_comm, Pos.prev_eq_iff]
|
||||
simp only [ofSlice_lt_ofSlice_iff, le_ofSlice_iff]
|
||||
simpa +contextual [← ofSlice_lt_ofSlice_iff] using fun q hq => Std.le_of_lt (Std.lt_of_lt_of_le hq ofSlice_le)
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev_next {s : Slice} {p : s.Pos} {h} : (p.next h).prev (by simp) = p :=
|
||||
prev_eq_iff.2 (by simp)
|
||||
@@ -217,6 +246,23 @@ theorem Pos.prev_next {s : Slice} {p : s.Pos} {h} : (p.next h).prev (by simp) =
|
||||
theorem Pos.next_prev {s : Slice} {p : s.Pos} {h} : (p.prev h).next (by simp) = p :=
|
||||
next_eq_iff.2 (by simp)
|
||||
|
||||
theorem Pos.prev?_eq_dif {s : Slice} {p : s.Pos} : p.prev? = if h : p = s.startPos then none else some (p.prev h) :=
|
||||
(rfl)
|
||||
|
||||
theorem Pos.prev?_eq_some_prev {s : Slice} {p : s.Pos} (h : p ≠ s.startPos) : p.prev? = some (p.prev h) := by
|
||||
simp [Pos.prev?, h]
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev?_eq_none_iff {s : Slice} {p : s.Pos} : p.prev? = none ↔ p = s.startPos := by
|
||||
simp [Pos.prev?]
|
||||
|
||||
theorem Pos.prev?_eq_none {s : Slice} {p : s.Pos} (h : p = s.startPos) : p.prev? = none :=
|
||||
prev?_eq_none_iff.2 h
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev?_startPos {s : Slice} : s.startPos.prev? = none := by
|
||||
simp
|
||||
|
||||
end Slice
|
||||
|
||||
@[simp]
|
||||
@@ -420,6 +466,10 @@ theorem Pos.prev_eq_iff {s : String} {p q : s.Pos} {h} :
|
||||
theorem Pos.prev_lt {s : String} {p : s.Pos} {h} : p.prev h < p := by
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev_le {s : String} {p : s.Pos} {h} : p.prev h ≤ p :=
|
||||
Std.le_of_lt (by simp)
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev_ne_endPos {s : String} {p : s.Pos} {h} : p.prev h ≠ s.endPos :=
|
||||
ne_endPos_of_lt prev_lt
|
||||
@@ -428,14 +478,45 @@ theorem Pos.toSlice_prev {s : String} {p : s.Pos} {h} :
|
||||
(p.prev h).toSlice = p.toSlice.prev (by simpa [toSlice_inj]) := by
|
||||
simp [prev]
|
||||
|
||||
theorem Pos.ofToSlice_prev {s : String} {p : s.toSlice.Pos} {h} :
|
||||
Pos.ofToSlice (p.prev h) = (Pos.ofToSlice p).prev (by simpa [← toSlice_inj]) := by
|
||||
simp [prev]
|
||||
|
||||
theorem Pos.prev_toSlice {s : String} {p : s.Pos} {h} :
|
||||
p.toSlice.prev h = (p.prev (by simpa [← toSlice_inj])).toSlice := by
|
||||
simp [prev]
|
||||
|
||||
theorem Pos.prev_ofToSlice {s : String} {p : s.toSlice.Pos} {h} :
|
||||
(Pos.ofToSlice p).prev h = Pos.ofToSlice (p.prev (by simpa [← ofToSlice_inj])) := by
|
||||
simp [prev]
|
||||
|
||||
theorem Pos.prevn_le {s : String} {p : s.Pos} {n : Nat} :
|
||||
p.prevn n ≤ p := by
|
||||
simpa [Pos.le_iff, ← offset_toSlice] using Slice.Pos.prevn_le
|
||||
|
||||
theorem Pos.ofSliceTo_prev {s : String} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} :
|
||||
Pos.ofSliceTo (p.prev h) = (Pos.ofSliceTo p).prev (by simpa [← Pos.ofSliceTo_inj] using h) := by
|
||||
rw [eq_comm, Pos.prev_eq_iff]
|
||||
simp only [Pos.ofSliceTo_lt_ofSliceTo_iff, Pos.le_ofSliceTo_iff]
|
||||
simp [Pos.lt_ofSliceTo_iff]
|
||||
|
||||
theorem Pos.prev_ofSliceTo {s : String} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} :
|
||||
(Pos.ofSliceTo p).prev h = Pos.ofSliceTo (p.prev (by simpa [← Pos.ofSliceTo_inj])) := by
|
||||
simp [ofSliceTo_prev]
|
||||
|
||||
theorem Pos.ofSliceFrom_prev {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} :
|
||||
Pos.ofSliceFrom (p.prev h) = (Pos.ofSliceFrom p).prev (by exact ofSliceFrom_ne_startPos h) := by
|
||||
rw [eq_comm, Pos.prev_eq_iff]
|
||||
simp only [Pos.ofSliceFrom_lt_ofSliceFrom_iff, Pos.le_ofSliceFrom_iff]
|
||||
simp [Pos.lt_ofSliceFrom_iff]
|
||||
|
||||
theorem Pos.ofSlice_prev {s : String} {p₀ p₁ : s.Pos} {h}
|
||||
{p : (s.slice p₀ p₁ h).Pos} {h'} :
|
||||
Pos.ofSlice (p.prev h') = (Pos.ofSlice p).prev (by exact ofSlice_ne_startPos h') := by
|
||||
rw [eq_comm, Pos.prev_eq_iff]
|
||||
simp only [ofSlice_lt_ofSlice_iff, le_ofSlice_iff]
|
||||
simpa +contextual [← ofSlice_lt_ofSlice_iff] using fun q hq => Std.le_of_lt (Std.lt_of_lt_of_le hq ofSlice_le)
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev_next {s : String} {p : s.Pos} {h} : (p.next h).prev (by simp) = p :=
|
||||
prev_eq_iff.2 (by simp)
|
||||
@@ -444,4 +525,71 @@ theorem Pos.prev_next {s : String} {p : s.Pos} {h} : (p.next h).prev (by simp) =
|
||||
theorem Pos.next_prev {s : String} {p : s.Pos} {h} : (p.prev h).next (by simp) = p :=
|
||||
next_eq_iff.2 (by simp)
|
||||
|
||||
theorem Pos.prev?_eq_prev?_toSlice {s : String} {p : s.Pos} : p.prev? = p.toSlice.prev?.map Pos.ofToSlice :=
|
||||
(rfl)
|
||||
|
||||
theorem Pos.prev?_toSlice {s : String} {p : s.Pos} : p.toSlice.prev? = p.prev?.map Pos.toSlice := by
|
||||
simp [prev?_eq_prev?_toSlice]
|
||||
|
||||
theorem Pos.prev?_eq_dif {s : String} {p : s.Pos} : p.prev? = if h : p = s.startPos then none else some (p.prev h) := by
|
||||
simp [prev?_eq_prev?_toSlice, Slice.Pos.prev?_eq_dif, apply_dite (Option.map Pos.ofToSlice),
|
||||
ofToSlice_prev]
|
||||
|
||||
theorem Pos.prev?_eq_some_prev {s : String} {p : s.Pos} (h : p ≠ s.startPos) : p.prev? = some (p.prev h) := by
|
||||
simp [prev?_eq_prev?_toSlice, Slice.Pos.prev?_eq_some_prev (by simpa : p.toSlice ≠ s.toSlice.startPos),
|
||||
ofToSlice_prev]
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev?_eq_none_iff {s : String} {p : s.Pos} : p.prev? = none ↔ p = s.startPos := by
|
||||
simp [prev?_eq_prev?_toSlice]
|
||||
|
||||
theorem Pos.prev?_eq_none {s : String} {p : s.Pos} (h : p = s.startPos) : p.prev? = none :=
|
||||
prev?_eq_none_iff.2 h
|
||||
|
||||
@[simp]
|
||||
theorem Pos.prev?_startPos {s : String} : s.startPos.prev? = none := by
|
||||
simp
|
||||
|
||||
namespace Slice.Pos
|
||||
|
||||
@[simp]
|
||||
theorem prevn_zero {s : Slice} {p : s.Pos} : p.prevn 0 = p := by
|
||||
simp [prevn]
|
||||
|
||||
theorem prevn_add_one {s : Slice} {p : s.Pos} :
|
||||
p.prevn (n + 1) = if h : p = s.startPos then p else (p.prev h).prevn n := by
|
||||
simp [prevn]
|
||||
|
||||
@[simp]
|
||||
theorem prevn_startPos {s : Slice} : s.startPos.prevn n = s.startPos := by
|
||||
cases n <;> simp [prevn_add_one]
|
||||
|
||||
end Slice.Pos
|
||||
|
||||
namespace Pos
|
||||
|
||||
theorem prevn_eq_prevn_toSlice {s : String} {p : s.Pos} : p.prevn n = Pos.ofToSlice (p.toSlice.prevn n) :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem prevn_zero {s : String} {p : s.Pos} : p.prevn 0 = p := by
|
||||
simp [prevn_eq_prevn_toSlice]
|
||||
|
||||
theorem prevn_add_one {s : String} {p : s.Pos} :
|
||||
p.prevn (n + 1) = if h : p = s.startPos then p else (p.prev h).prevn n := by
|
||||
simp only [prevn_eq_prevn_toSlice, Slice.Pos.prevn_add_one, startPos_toSlice, toSlice_inj]
|
||||
split <;> simp [Pos.prev_toSlice]
|
||||
|
||||
theorem prevn_toSlice {s : String} {p : s.Pos} : p.toSlice.prevn n = (p.prevn n).toSlice := by
|
||||
induction n generalizing p with simp_all [prevn_add_one, Slice.Pos.prevn_add_one, apply_dite Pos.toSlice, prev_toSlice]
|
||||
|
||||
theorem toSlice_prevn {s : String} {p : s.Pos} : (p.prevn n).toSlice = p.toSlice.prevn n :=
|
||||
prevn_toSlice.symm
|
||||
|
||||
@[simp]
|
||||
theorem prevn_startPos {s : String} : s.startPos.prevn n = s.startPos := by
|
||||
cases n <;> simp [prevn_add_one]
|
||||
|
||||
end Pos
|
||||
|
||||
end String
|
||||
|
||||
25
src/Init/Data/String/Lemmas/Hashable.lean
Normal file
25
src/Init/Data/String/Lemmas/Hashable.lean
Normal file
@@ -0,0 +1,25 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Julia Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Slice
|
||||
public import Init.Data.LawfulHashable
|
||||
import all Init.Data.String.Slice
|
||||
import Init.Data.String.Lemmas.Slice
|
||||
|
||||
namespace String
|
||||
|
||||
public theorem hash_eq {s : String} : hash s = String.hash s := rfl
|
||||
|
||||
namespace Slice
|
||||
|
||||
public theorem hash_eq {s : String.Slice} : hash s = String.hash s.copy := (rfl)
|
||||
|
||||
public instance : LawfulHashable String.Slice where
|
||||
hash_eq a b hab := by simp [hash_eq, beq_eq_true_iff.1 hab]
|
||||
|
||||
end String.Slice
|
||||
@@ -10,6 +10,7 @@ public import Init.Data.String.Defs
|
||||
import all Init.Data.String.Defs
|
||||
public import Init.Data.String.Slice
|
||||
import all Init.Data.String.Slice
|
||||
import Init.ByCases
|
||||
|
||||
public section
|
||||
|
||||
@@ -42,6 +43,16 @@ theorem intercalate_cons_of_ne_nil {s t : String} {l : List String} (h : l ≠ [
|
||||
match l, h with
|
||||
| u::l, _ => by simp
|
||||
|
||||
theorem intercalate_append_of_ne_nil {l m : List String} {s : String} (hl : l ≠ []) (hm : m ≠ []) :
|
||||
s.intercalate (l ++ m) = s.intercalate l ++ s ++ s.intercalate m := by
|
||||
induction l with
|
||||
| nil => simp_all
|
||||
| cons hd tl ih =>
|
||||
rw [List.cons_append, intercalate_cons_of_ne_nil (by simp_all)]
|
||||
by_cases ht : tl = []
|
||||
· simp_all
|
||||
· simp [ih ht, intercalate_cons_of_ne_nil ht, String.append_assoc]
|
||||
|
||||
@[simp]
|
||||
theorem toList_intercalate {s : String} {l : List String} :
|
||||
(s.intercalate l).toList = s.toList.intercalate (l.map String.toList) := by
|
||||
@@ -49,6 +60,23 @@ theorem toList_intercalate {s : String} {l : List String} :
|
||||
| nil => simp
|
||||
| cons hd tl ih => cases tl <;> simp_all
|
||||
|
||||
theorem join_eq_foldl : join l = l.foldl (fun r s => r ++ s) "" :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem join_nil : join [] = "" := by
|
||||
simp [join]
|
||||
|
||||
@[simp]
|
||||
theorem join_cons : join (s :: l) = s ++ join l := by
|
||||
simp only [join, List.foldl_cons, empty_append]
|
||||
conv => lhs; rw [← String.append_empty (s := s)]
|
||||
rw [List.foldl_assoc]
|
||||
|
||||
@[simp]
|
||||
theorem toList_join {l : List String} : (String.join l).toList = l.flatMap String.toList := by
|
||||
induction l <;> simp_all
|
||||
|
||||
namespace Slice
|
||||
|
||||
@[simp]
|
||||
@@ -65,6 +93,10 @@ theorem intercalate_eq {s : Slice} {l : List Slice} :
|
||||
| nil => simp [intercalate]
|
||||
| cons hd tl ih => cases tl <;> simp_all [intercalate, intercalate.go, intercalateGo_append]
|
||||
|
||||
@[simp]
|
||||
theorem join_eq {l : List Slice} : join l = String.join (l.map copy) := by
|
||||
simp [join, String.join, List.foldl_map]
|
||||
|
||||
end Slice
|
||||
|
||||
end String
|
||||
|
||||
@@ -204,7 +204,7 @@ theorem Slice.copy_sliceTo_startPos {s : Slice} : (s.sliceTo s.startPos).copy =
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem Slice.copy_sliceFrom_startPos {s : Slice} : (s.sliceFrom s.endPos).copy = "" := by
|
||||
theorem Slice.copy_sliceFrom_endPos {s : Slice} : (s.sliceFrom s.endPos).copy = "" := by
|
||||
simp
|
||||
|
||||
end CopyEqEmpty
|
||||
|
||||
50
src/Init/Data/String/Lemmas/Iter.lean
Normal file
50
src/Init/Data/String/Lemmas/Iter.lean
Normal file
@@ -0,0 +1,50 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Julia Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Iter.Intercalate
|
||||
public import Init.Data.String.Slice
|
||||
import all Init.Data.String.Iter.Intercalate
|
||||
import all Init.Data.String.Defs
|
||||
import Init.Data.String.Lemmas.Intercalate
|
||||
import Init.Data.Iterators.Lemmas.Consumers.Loop
|
||||
import Init.Data.Iterators.Lemmas.Combinators.FilterMap
|
||||
|
||||
namespace Std.Iter
|
||||
|
||||
@[simp]
|
||||
public theorem joinString_eq {α β : Type} [Std.Iterator α Id β] [Std.Iterators.Finite α Id]
|
||||
[ToString β] {it : Std.Iter (α := α) β} :
|
||||
it.joinString = String.join (it.toList.map toString) := by
|
||||
rw [joinString, String.join, ← foldl_toList, toList_map]
|
||||
|
||||
@[simp]
|
||||
public theorem intercalateString_eq {α β : Type} [Std.Iterator α Id β] [Std.Iterators.Finite α Id]
|
||||
[ToString β] {s : String.Slice} {it : Std.Iter (α := α) β} :
|
||||
it.intercalateString s = s.copy.intercalate (it.toList.map toString) := by
|
||||
simp only [intercalateString, String.appendSlice_eq, ← foldl_toList, toList_map]
|
||||
generalize s.copy = s
|
||||
suffices ∀ (l m : List String),
|
||||
(l.foldl (init := if m = [] then none else some (s.intercalate m))
|
||||
(fun | none, sl => some sl | some str, sl => some (str ++ s ++ sl))).getD ""
|
||||
= s.intercalate (m ++ l) by
|
||||
simpa [-foldl_toList] using this (it.toList.map toString) []
|
||||
intro l m
|
||||
induction l generalizing m with
|
||||
| nil => cases m <;> simp
|
||||
| cons hd tl ih =>
|
||||
rw [List.append_cons, ← ih, List.foldl_cons]
|
||||
congr
|
||||
simp only [List.append_eq_nil_iff, List.cons_ne_self, and_false, ↓reduceIte]
|
||||
match m with
|
||||
| [] => simp
|
||||
| x::xs =>
|
||||
simp only [reduceCtorEq, ↓reduceIte, List.cons_append, Option.some.injEq]
|
||||
rw [← List.cons_append, String.intercalate_append_of_ne_nil (by simp) (by simp),
|
||||
String.intercalate_singleton]
|
||||
|
||||
end Std.Iter
|
||||
@@ -11,6 +11,7 @@ import Init.Data.String.OrderInstances
|
||||
import Init.Data.String.Lemmas.Basic
|
||||
import Init.Data.Order.Lemmas
|
||||
import Init.Omega
|
||||
import Init.ByCases
|
||||
|
||||
public section
|
||||
|
||||
@@ -70,7 +71,7 @@ theorem Pos.le_startPos {s : String} (p : s.Pos) : p ≤ s.startPos ↔ p = s.st
|
||||
⟨fun h => Std.le_antisymm h (startPos_le _), by simp +contextual⟩
|
||||
|
||||
@[simp]
|
||||
theorem Pos.startPos_lt_iff {s : String} {p : s.Pos} : s.startPos < p ↔ p ≠ s.startPos := by
|
||||
theorem Pos.startPos_lt_iff {s : String} (p : s.Pos) : s.startPos < p ↔ p ≠ s.startPos := by
|
||||
simp [← le_startPos, Std.not_le]
|
||||
|
||||
@[simp]
|
||||
@@ -235,6 +236,10 @@ theorem Slice.Pos.ofSliceFrom_next {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom
|
||||
Pos.next_le_iff_lt, true_and]
|
||||
simp [Pos.ofSliceFrom_lt_iff]
|
||||
|
||||
theorem Slice.Pos.next_ofSliceFrom {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} :
|
||||
(Pos.ofSliceFrom p).next h = Pos.ofSliceFrom (p.next (by simpa [← Pos.ofSliceFrom_inj])) := by
|
||||
simp [ofSliceFrom_next]
|
||||
|
||||
theorem Pos.ofSliceFrom_next {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} :
|
||||
Pos.ofSliceFrom (p.next h) = (Pos.ofSliceFrom p).next (by simpa [← Pos.ofSliceFrom_inj] using h) := by
|
||||
rw [eq_comm, Pos.next_eq_iff]
|
||||
@@ -242,6 +247,10 @@ theorem Pos.ofSliceFrom_next {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀)
|
||||
Slice.Pos.next_le_iff_lt, true_and]
|
||||
simp [Pos.ofSliceFrom_lt_iff]
|
||||
|
||||
theorem Pos.next_ofSliceFrom {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos} {h} :
|
||||
(Pos.ofSliceFrom p).next h = Pos.ofSliceFrom (p.next (by simpa [← Pos.ofSliceFrom_inj])) := by
|
||||
simp [Pos.ofSliceFrom_next]
|
||||
|
||||
theorem Slice.Pos.le_ofSliceTo_iff {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {q : s.Pos} :
|
||||
q ≤ Pos.ofSliceTo p ↔ ∃ h, Slice.Pos.sliceTo p₀ q h ≤ p := by
|
||||
refine ⟨fun h => ⟨Slice.Pos.le_trans h Pos.ofSliceTo_le, ?_⟩, fun ⟨h, h'⟩ => ?_⟩
|
||||
@@ -359,11 +368,21 @@ theorem Slice.Pos.ofSliceTo_ne_endPos {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo
|
||||
refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₀))
|
||||
simpa [← lt_endPos_iff, ← ofSliceTo_lt_ofSliceTo_iff] using h
|
||||
|
||||
theorem Slice.Pos.ofSliceFrom_ne_startPos {s : Slice} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos}
|
||||
(h : p ≠ (s.sliceFrom p₀).startPos) : Pos.ofSliceFrom p ≠ s.startPos := by
|
||||
refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_)
|
||||
simpa [← startPos_lt_iff, ← ofSliceFrom_lt_ofSliceFrom_iff] using h
|
||||
|
||||
theorem Pos.ofSliceTo_ne_endPos {s : String} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos}
|
||||
(h : p ≠ (s.sliceTo p₀).endPos) : Pos.ofSliceTo p ≠ s.endPos := by
|
||||
refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₀))
|
||||
simpa [← Slice.Pos.lt_endPos_iff, ← ofSliceTo_lt_ofSliceTo_iff] using h
|
||||
|
||||
theorem Pos.ofSliceFrom_ne_startPos {s : String} {p₀ : s.Pos} {p : (s.sliceFrom p₀).Pos}
|
||||
(h : p ≠ (s.sliceFrom p₀).startPos) : Pos.ofSliceFrom p ≠ s.startPos := by
|
||||
refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_)
|
||||
simpa [← Slice.Pos.startPos_lt_iff, ← ofSliceFrom_lt_ofSliceFrom_iff] using h
|
||||
|
||||
theorem Slice.Pos.ofSliceTo_next {s : Slice} {p₀ : s.Pos} {p : (s.sliceTo p₀).Pos} {h} :
|
||||
Pos.ofSliceTo (p.next h) = (Pos.ofSliceTo p).next (ofSliceTo_ne_endPos h) := by
|
||||
rw [eq_comm, Pos.next_eq_iff]
|
||||
@@ -406,16 +425,110 @@ theorem Pos.slice_le_slice_iff {s : String} {p₀ p₁ : s.Pos} {q r : s.Pos}
|
||||
simp [Slice.Pos.le_iff, Pos.le_iff, Pos.Raw.le_iff] at h₁ h₁' ⊢
|
||||
omega
|
||||
|
||||
theorem Slice.Pos.le_ofSlice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
q ≤ Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Slice.Pos.slice q p₀ p₁ h₀ h₁ ≤ p := by
|
||||
refine ⟨fun h => ⟨Std.le_trans h ofSlice_le, fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩
|
||||
· simp only [← Slice.Pos.slice_ofSlice (pos := p), slice_le_slice_iff]
|
||||
simpa
|
||||
· by_cases h₀ : p₀ ≤ q
|
||||
· simpa only [← Slice.Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_le_ofSlice_iff] using h h₀
|
||||
· exact Std.le_of_lt (Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice)
|
||||
|
||||
theorem Slice.Pos.ofSlice_lt_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
Pos.ofSlice p < q ↔ ∀ h₁, ∃ h₀, p < Slice.Pos.slice q p₀ p₁ h₀ h₁ := by
|
||||
simp [← Std.not_le, le_ofSlice_iff]
|
||||
|
||||
theorem Slice.Pos.lt_ofSlice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
q < Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Slice.Pos.slice q p₀ p₁ h₀ h₁ < p := by
|
||||
refine ⟨fun h => ⟨Std.le_of_lt (Std.lt_of_lt_of_le h ofSlice_le), fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩
|
||||
· simp only [← Slice.Pos.slice_ofSlice (pos := p), slice_lt_slice_iff]
|
||||
simpa
|
||||
· by_cases h₀ : p₀ ≤ q
|
||||
· simpa only [← Slice.Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_lt_ofSlice_iff] using h h₀
|
||||
· exact Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice
|
||||
|
||||
theorem Slice.Pos.ofSlice_le_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
Pos.ofSlice p ≤ q ↔ ∀ h₁, ∃ h₀, p ≤ Slice.Pos.slice q p₀ p₁ h₀ h₁ := by
|
||||
simp [← Std.not_lt, lt_ofSlice_iff]
|
||||
|
||||
theorem Pos.le_ofSlice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
q ≤ Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Pos.slice q p₀ p₁ h₀ h₁ ≤ p := by
|
||||
refine ⟨fun h => ⟨Std.le_trans h ofSlice_le, fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩
|
||||
· simp only [← Pos.slice_ofSlice (pos := p), slice_le_slice_iff]
|
||||
simpa
|
||||
· by_cases h₀ : p₀ ≤ q
|
||||
· simpa only [← Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_le_ofSlice_iff] using h h₀
|
||||
· exact Std.le_of_lt (Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice)
|
||||
|
||||
theorem Pos.ofSlice_lt_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
Pos.ofSlice p < q ↔ ∀ h₁, ∃ h₀, p < Pos.slice q p₀ p₁ h₀ h₁ := by
|
||||
simp [← Std.not_le, le_ofSlice_iff]
|
||||
|
||||
theorem Pos.lt_ofSlice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
q < Pos.ofSlice p ↔ ∃ h₁, ∀ h₀, Pos.slice q p₀ p₁ h₀ h₁ < p := by
|
||||
refine ⟨fun h => ⟨Std.le_of_lt (Std.lt_of_lt_of_le h ofSlice_le), fun h' => ?_⟩, fun ⟨h₁, h⟩ => ?_⟩
|
||||
· simp only [← Pos.slice_ofSlice (pos := p), slice_lt_slice_iff]
|
||||
simpa
|
||||
· by_cases h₀ : p₀ ≤ q
|
||||
· simpa only [← Pos.ofSlice_slice (h₁ := h₀) (h₂ := h₁), ofSlice_lt_ofSlice_iff] using h h₀
|
||||
· exact Std.lt_of_lt_of_le (Std.not_le.1 h₀) le_ofSlice
|
||||
|
||||
theorem Pos.ofSlice_le_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} :
|
||||
Pos.ofSlice p ≤ q ↔ ∀ h₁, ∃ h₀, p ≤ Pos.slice q p₀ p₁ h₀ h₁ := by
|
||||
simp [← Std.not_lt, lt_ofSlice_iff]
|
||||
|
||||
theorem Slice.Pos.slice_le_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
Slice.Pos.slice q p₀ p₁ h₀ h₁ ≤ p ↔ q ≤ Pos.ofSlice p := by
|
||||
simp [le_ofSlice_iff, h₀, h₁]
|
||||
|
||||
theorem Slice.Pos.lt_slice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
p < Slice.Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p < q := by
|
||||
simp [ofSlice_lt_iff, h₀, h₁]
|
||||
|
||||
theorem Slice.Pos.slice_lt_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
Slice.Pos.slice q p₀ p₁ h₀ h₁ < p ↔ q < Pos.ofSlice p := by
|
||||
simp [lt_ofSlice_iff, h₀, h₁]
|
||||
|
||||
theorem Slice.Pos.le_slice_iff {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
p ≤ Slice.Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p ≤ q := by
|
||||
simp [ofSlice_le_iff, h₀, h₁]
|
||||
|
||||
theorem Pos.slice_le_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
Pos.slice q p₀ p₁ h₀ h₁ ≤ p ↔ q ≤ Pos.ofSlice p := by
|
||||
simp [le_ofSlice_iff, h₀, h₁]
|
||||
|
||||
theorem Pos.lt_slice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
p < Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p < q := by
|
||||
simp [ofSlice_lt_iff, h₀, h₁]
|
||||
|
||||
theorem Pos.slice_lt_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
Pos.slice q p₀ p₁ h₀ h₁ < p ↔ q < Pos.ofSlice p := by
|
||||
simp [lt_ofSlice_iff, h₀, h₁]
|
||||
|
||||
theorem Pos.le_slice_iff {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos} {q : s.Pos} {h₀ h₁} :
|
||||
p ≤ Pos.slice q p₀ p₁ h₀ h₁ ↔ Pos.ofSlice p ≤ q := by
|
||||
simp [ofSlice_le_iff, h₀, h₁]
|
||||
|
||||
theorem Slice.Pos.ofSlice_ne_endPos {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos}
|
||||
(h : p ≠ (s.slice p₀ p₁ h).endPos) : Pos.ofSlice p ≠ s.endPos := by
|
||||
refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₁))
|
||||
simpa [← lt_endPos_iff, ← ofSlice_lt_ofSlice_iff] using h
|
||||
|
||||
theorem Slice.Pos.ofSlice_ne_startPos {s : Slice} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos}
|
||||
(h : p ≠ (s.slice p₀ p₁ h).startPos) : Pos.ofSlice p ≠ s.startPos := by
|
||||
refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_)
|
||||
simpa [← startPos_lt_iff, ← ofSlice_lt_ofSlice_iff] using h
|
||||
|
||||
theorem Pos.ofSlice_ne_endPos {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos}
|
||||
(h : p ≠ (s.slice p₀ p₁ h).endPos) : Pos.ofSlice p ≠ s.endPos := by
|
||||
refine (lt_endPos_iff _).1 (Std.lt_of_lt_of_le ?_ (le_endPos p₁))
|
||||
simpa [← Slice.Pos.lt_endPos_iff, ← ofSlice_lt_ofSlice_iff] using h
|
||||
|
||||
theorem Pos.ofSlice_ne_startPos {s : String} {p₀ p₁ : s.Pos} {h} {p : (s.slice p₀ p₁ h).Pos}
|
||||
(h : p ≠ (s.slice p₀ p₁ h).startPos) : Pos.ofSlice p ≠ s.startPos := by
|
||||
refine (startPos_lt_iff _).1 (Std.lt_of_le_of_lt (startPos_le p₀) ?_)
|
||||
simpa [← Slice.Pos.startPos_lt_iff, ← ofSlice_lt_ofSlice_iff] using h
|
||||
|
||||
@[simp]
|
||||
theorem Slice.Pos.offset_le_rawEndPos {s : Slice} {p : s.Pos} :
|
||||
p.offset ≤ s.rawEndPos :=
|
||||
|
||||
@@ -19,6 +19,7 @@ import Init.Data.Order.Lemmas
|
||||
import Init.ByCases
|
||||
import Init.Data.Option.Lemmas
|
||||
import Init.Data.Iterators.Lemmas.Consumers.Collect
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
@@ -31,19 +32,20 @@ This file develops basic theory around searching in strings.
|
||||
|
||||
We provide a typeclass for providing semantics to a pattern and then define the relevant notions
|
||||
of matching a pattern that let us state compatibility typeclasses for {name}`ForwardPattern` and
|
||||
{name}`ToForwardSearcher`. These typeclasses can then be required by correctness results for
|
||||
string functions which are implemented using the pattern framework.
|
||||
{name}`ToForwardSearcher` as well as their backwards variants. These typeclasses can then be
|
||||
required by correctness results for string functions which are implemented using the pattern
|
||||
framework.
|
||||
-/
|
||||
|
||||
/--
|
||||
This data-carrying typeclass is used to give semantics to a pattern type that implements
|
||||
{name}`ForwardPattern` and/or {name}`ToForwardSearcher` by providing an abstract, not necessarily
|
||||
decidable {name}`ForwardPatternModel.Matches` predicate that implementates of {name}`ForwardPattern`
|
||||
decidable {name}`PatternModel.Matches` predicate that implementates of {name}`ForwardPattern`
|
||||
and {name}`ToForwardSearcher` can be validated against.
|
||||
|
||||
Correctness results for generic functions relying on the pattern infrastructure, for example the
|
||||
correctness result for {name (scope := "Init.Data.String.Slice")}`String.Slice.split`, are then
|
||||
stated in terms of {name}`ForwardPatternModel.Matches`, and can be specialized to specific patterns
|
||||
stated in terms of {name}`PatternModel.Matches`, and can be specialized to specific patterns
|
||||
from there.
|
||||
|
||||
The corresponding compatibility typeclasses are
|
||||
@@ -59,7 +61,7 @@ searching.
|
||||
This means that pattern types that allow searching for the empty string will have to special-case
|
||||
the empty string in their correctness statements.
|
||||
-/
|
||||
class ForwardPatternModel {ρ : Type} (pat : ρ) : Type where
|
||||
class PatternModel {ρ : Type} (pat : ρ) : Type where
|
||||
/-- The predicate that says which strings match the pattern. -/
|
||||
Matches : String → Prop
|
||||
not_matches_empty : ¬ Matches ""
|
||||
@@ -69,49 +71,72 @@ Predicate stating that the region between the start of the slice {name}`s` and t
|
||||
{name}`endPos` matches the pattern {name}`pat`. Note that there might be a longer match, see
|
||||
{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.IsLongestMatch`.
|
||||
-/
|
||||
structure IsMatch (pat : ρ) [ForwardPatternModel pat] {s : Slice} (endPos : s.Pos) : Prop where
|
||||
matches_copy : ForwardPatternModel.Matches pat (s.sliceTo endPos).copy
|
||||
structure IsMatch (pat : ρ) [PatternModel pat] {s : Slice} (endPos : s.Pos) : Prop where
|
||||
matches_copy : PatternModel.Matches pat (s.sliceTo endPos).copy
|
||||
|
||||
theorem IsMatch.ne_startPos {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos}
|
||||
theorem IsMatch.ne_startPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos}
|
||||
(h : IsMatch pat pos) : pos ≠ s.startPos := by
|
||||
intro hc
|
||||
apply ForwardPatternModel.not_matches_empty (pat := pat)
|
||||
apply PatternModel.not_matches_empty (pat := pat)
|
||||
simpa [hc] using h.matches_copy
|
||||
|
||||
theorem isMatch_iff {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch pat pos ↔ ForwardPatternModel.Matches pat (s.sliceTo pos).copy :=
|
||||
theorem isMatch_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch pat pos ↔ PatternModel.Matches pat (s.sliceTo pos).copy :=
|
||||
⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩
|
||||
|
||||
theorem isMatch_iff_exists_splits {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ t₂ ∧ ForwardPatternModel.Matches pat t₁ := by
|
||||
theorem isMatch_iff_exists_splits {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ t₂ ∧ PatternModel.Matches pat t₁ := by
|
||||
rw [isMatch_iff]
|
||||
refine ⟨fun h => ⟨_, _, pos.splits, h⟩, fun ⟨t₁, t₂, h₁, h₂⟩ => ?_⟩
|
||||
rwa [h₁.eq_left pos.splits] at h₂
|
||||
|
||||
/--
|
||||
Predicate stating that the region between the position {name}`startPos` and the end of the slice
|
||||
{name}`s` matches the pattern {name}`pat`. Note that there might be a longer match.
|
||||
-/
|
||||
structure IsRevMatch (pat : ρ) [PatternModel pat] {s : Slice} (startPos : s.Pos) : Prop where
|
||||
matches_copy : PatternModel.Matches pat (s.sliceFrom startPos).copy
|
||||
|
||||
theorem IsRevMatch.ne_endPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos}
|
||||
(h : IsRevMatch pat pos) : pos ≠ s.endPos := by
|
||||
intro hc
|
||||
apply PatternModel.not_matches_empty (pat := pat)
|
||||
simpa [hc] using h.matches_copy
|
||||
|
||||
theorem isRevMatch_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch pat pos ↔ PatternModel.Matches pat (s.sliceFrom pos).copy :=
|
||||
⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩
|
||||
|
||||
theorem isRevMatch_iff_exists_splits {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ t₂ ∧ PatternModel.Matches pat t₂ := by
|
||||
rw [isRevMatch_iff]
|
||||
refine ⟨fun h => ⟨_, _, pos.splits, h⟩, fun ⟨t₁, t₂, h₁, h₂⟩ => ?_⟩
|
||||
rwa [h₁.eq_right pos.splits] at h₂
|
||||
|
||||
/--
|
||||
Predicate stating that the region between the start of the slice {name}`s` and the position
|
||||
{name}`endPos` matches that pattern {name}`pat`, and that there is no longer match starting at the
|
||||
{name}`pos` matches the pattern {name}`pat`, and that there is no longer match starting at the
|
||||
beginning of the slice. This is what a correct matcher should match.
|
||||
|
||||
In some cases, being a match and being a longest match will coincide, see
|
||||
{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixForwardPatternModel`.
|
||||
{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixPatternModel`.
|
||||
-/
|
||||
structure IsLongestMatch (pat : ρ) [ForwardPatternModel pat] {s : Slice} (pos : s.Pos) where
|
||||
structure IsLongestMatch (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) where
|
||||
isMatch : IsMatch pat pos
|
||||
not_isMatch : ∀ pos', pos < pos' → ¬ IsMatch pat pos'
|
||||
|
||||
theorem IsLongestMatch.ne_startPos {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos}
|
||||
theorem IsLongestMatch.ne_startPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos}
|
||||
(h : IsLongestMatch pat pos) : pos ≠ s.startPos :=
|
||||
h.isMatch.ne_startPos
|
||||
|
||||
theorem IsLongestMatch.eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos pos' : s.Pos}
|
||||
theorem IsLongestMatch.eq {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos}
|
||||
(h : IsLongestMatch pat pos) (h' : IsLongestMatch pat pos') : pos = pos' := by
|
||||
apply Std.le_antisymm
|
||||
· exact Std.not_lt.1 (fun hlt => h'.not_isMatch _ hlt h.isMatch)
|
||||
· exact Std.not_lt.1 (fun hlt => h.not_isMatch _ hlt h'.isMatch)
|
||||
|
||||
open Classical in
|
||||
theorem IsMatch.exists_isLongestMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
theorem IsMatch.exists_isLongestMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch pat pos → ∃ (pos' : s.Pos), IsLongestMatch pat pos' := by
|
||||
induction pos using WellFounded.induction Pos.wellFounded_gt with | h pos ih
|
||||
intro h₁
|
||||
@@ -120,61 +145,118 @@ theorem IsMatch.exists_isLongestMatch {pat : ρ} [ForwardPatternModel pat] {s :
|
||||
exact ih _ hp₁ hp₂
|
||||
· exact ⟨pos, ⟨h₁, fun p' hp₁ hp₂ => h₂ ⟨_, hp₁, hp₂⟩⟩⟩
|
||||
|
||||
theorem IsLongestMatch.le_of_isMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos pos' : s.Pos}
|
||||
theorem IsLongestMatch.le_of_isMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos}
|
||||
(h : IsLongestMatch pat pos) (h' : IsMatch pat pos') : pos' ≤ pos :=
|
||||
Std.not_lt.1 (fun hlt => h.not_isMatch _ hlt h')
|
||||
|
||||
/--
|
||||
Predicate stating that the region between the start of the slice {name}`s` and the position
|
||||
{name}`pos` matches the patten {name}`pat`, and that there is no longer match starting at the
|
||||
beginning of the slice. This is what a correct matcher should match.
|
||||
|
||||
In some cases, being a match and being a longest match will coincide, see
|
||||
{name (scope := "Init.Data.String.Lemmas.Pattern.Basic")}`String.Slice.Pattern.Model.NoPrefixPatternModel`.
|
||||
-/
|
||||
structure IsLongestRevMatch (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) where
|
||||
isRevMatch : IsRevMatch pat pos
|
||||
not_isRevMatch : ∀ pos', pos' < pos → ¬ IsRevMatch pat pos'
|
||||
|
||||
theorem IsLongestRevMatch.ne_endPos {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos}
|
||||
(h : IsLongestRevMatch pat pos) : pos ≠ s.endPos :=
|
||||
h.isRevMatch.ne_endPos
|
||||
|
||||
theorem IsLongestRevMatch.eq {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos}
|
||||
(h : IsLongestRevMatch pat pos) (h' : IsLongestRevMatch pat pos') : pos = pos' := by
|
||||
apply Std.le_antisymm
|
||||
· exact Std.not_lt.1 (fun hlt => h.not_isRevMatch _ hlt h'.isRevMatch)
|
||||
· exact Std.not_lt.1 (fun hlt => h'.not_isRevMatch _ hlt h.isRevMatch)
|
||||
|
||||
open Classical in
|
||||
theorem IsRevMatch.exists_isLongestRevMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch pat pos → ∃ (pos' : s.Pos), IsLongestRevMatch pat pos' := by
|
||||
induction pos using WellFounded.induction Pos.wellFounded_lt with | h pos ih
|
||||
intro h₁
|
||||
by_cases h₂ : ∃ pos', pos' < pos ∧ IsRevMatch pat pos'
|
||||
· obtain ⟨pos', hp₁, hp₂⟩ := h₂
|
||||
exact ih _ hp₁ hp₂
|
||||
· exact ⟨pos, ⟨h₁, fun p' hp₁ hp₂ => h₂ ⟨_, hp₁, hp₂⟩⟩⟩
|
||||
|
||||
theorem IsLongestRevMatch.le_of_isRevMatch {pat : ρ} [PatternModel pat] {s : Slice} {pos pos' : s.Pos}
|
||||
(h : IsLongestRevMatch pat pos) (h' : IsRevMatch pat pos') : pos ≤ pos' :=
|
||||
Std.not_lt.1 (fun hlt => h.not_isRevMatch _ hlt h')
|
||||
|
||||
/--
|
||||
Predicate stating that a match for a given pattern is never a proper prefix of another match.
|
||||
|
||||
This implies that the notion of match and longest match coincide.
|
||||
-/
|
||||
class NoPrefixForwardPatternModel {ρ : Type} (pat : ρ) [ForwardPatternModel pat] : Prop where
|
||||
eq_empty (s t) : ForwardPatternModel.Matches pat s → ForwardPatternModel.Matches pat (s ++ t) → t = ""
|
||||
class NoPrefixPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where
|
||||
eq_empty (s t) : PatternModel.Matches pat s → PatternModel.Matches pat (s ++ t) → t = ""
|
||||
|
||||
theorem NoPrefixForwardPatternModel.of_length_eq {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
(h : ∀ s t, ForwardPatternModel.Matches pat s → ForwardPatternModel.Matches pat t → s.length = t.length) :
|
||||
NoPrefixForwardPatternModel pat where
|
||||
theorem NoPrefixPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
(h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) :
|
||||
NoPrefixPatternModel pat where
|
||||
eq_empty s t hs ht := by simpa using h s _ hs ht
|
||||
|
||||
theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [ForwardPatternModel pat] [NoPrefixForwardPatternModel pat]
|
||||
theorem isLongestMatch_iff_isMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoPrefixPatternModel pat]
|
||||
{s : Slice} {pos : s.Pos} : IsLongestMatch pat pos ↔ IsMatch pat pos := by
|
||||
refine ⟨fun h => h.isMatch, fun h => ⟨h, fun pos' hpos' hm => ?_⟩⟩
|
||||
obtain ⟨t₁, t₂, ht₁, ht₂⟩ := isMatch_iff_exists_splits.1 h
|
||||
obtain ⟨t₁', t₂', ht₁', ht₂'⟩ := isMatch_iff_exists_splits.1 hm
|
||||
obtain ⟨t₅, ht₅, ht₅', ht₅''⟩ := (ht₁.lt_iff_exists_eq_append ht₁').1 hpos'
|
||||
exact ht₅ (NoPrefixForwardPatternModel.eq_empty _ _ ht₂ (ht₅' ▸ ht₂'))
|
||||
exact ht₅ (NoPrefixPatternModel.eq_empty _ _ ht₂ (ht₅' ▸ ht₂'))
|
||||
|
||||
/--
|
||||
Predicate stating that a match for a given pattern is never a proper suffix of another match.
|
||||
|
||||
This implies that the notion of reverse match and longest reverse match coincide.
|
||||
-/
|
||||
class NoSuffixPatternModel {ρ : Type} (pat : ρ) [PatternModel pat] : Prop where
|
||||
eq_empty (s t) : PatternModel.Matches pat t → PatternModel.Matches pat (s ++ t) → s = ""
|
||||
|
||||
theorem NoSuffixPatternModel.of_length_eq {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
(h : ∀ s t, PatternModel.Matches pat s → PatternModel.Matches pat t → s.length = t.length) :
|
||||
NoSuffixPatternModel pat where
|
||||
eq_empty s t hs ht := by simpa using h t _ hs ht
|
||||
|
||||
theorem isLongestRevMatch_iff_isRevMatch {ρ : Type} (pat : ρ) [PatternModel pat] [NoSuffixPatternModel pat]
|
||||
{s : Slice} {pos : s.Pos} : IsLongestRevMatch pat pos ↔ IsRevMatch pat pos := by
|
||||
refine ⟨fun h => h.isRevMatch, fun h => ⟨h, fun pos' hpos' hm => ?_⟩⟩
|
||||
obtain ⟨t₁, t₂, ht₁, ht₂⟩ := isRevMatch_iff_exists_splits.1 h
|
||||
obtain ⟨t₁', t₂', ht₁', ht₂'⟩ := isRevMatch_iff_exists_splits.1 hm
|
||||
obtain ⟨t₅, ht₅, ht₅', ht₅''⟩ := (ht₁'.lt_iff_exists_eq_append ht₁).1 hpos'
|
||||
exact ht₅ (NoSuffixPatternModel.eq_empty _ _ ht₂ (ht₅'' ▸ ht₂'))
|
||||
|
||||
/--
|
||||
Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match
|
||||
of {name}`pat` in {name}`s` and it is longest among matches starting at {name}`startPos`.
|
||||
-/
|
||||
structure IsLongestMatchAt (pat : ρ) [ForwardPatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where
|
||||
structure IsLongestMatchAt (pat : ρ) [PatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where
|
||||
le : startPos ≤ endPos
|
||||
isLongestMatch_sliceFrom : IsLongestMatch pat (Slice.Pos.sliceFrom _ _ le)
|
||||
|
||||
theorem isLongestMatchAt_iff {pat : ρ} [ForwardPatternModel pat] {s : Slice} {pos₁ pos₂ : s.Pos} :
|
||||
theorem isLongestMatchAt_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos₁ pos₂ : s.Pos} :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔
|
||||
∃ (h : pos₁ ≤ pos₂), IsLongestMatch pat (Slice.Pos.sliceFrom _ _ h) :=
|
||||
⟨fun ⟨h, h'⟩ => ⟨h, h'⟩, fun ⟨h, h'⟩ => ⟨h, h'⟩⟩
|
||||
|
||||
theorem IsLongestMatchAt.lt {pat : ρ} [ForwardPatternModel pat] {s : Slice} {startPos endPos : s.Pos}
|
||||
theorem IsLongestMatchAt.lt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos}
|
||||
(h : IsLongestMatchAt pat startPos endPos) : startPos < endPos := by
|
||||
have := h.isLongestMatch_sliceFrom.ne_startPos
|
||||
rw [← Pos.startPos_lt_iff, ← Slice.Pos.ofSliceFrom_lt_ofSliceFrom_iff] at this
|
||||
simpa
|
||||
|
||||
theorem IsLongestMatchAt.eq {pat : ρ} [ForwardPatternModel pat] {s : Slice} {startPos endPos endPos' : s.Pos}
|
||||
theorem IsLongestMatchAt.eq {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos endPos' : s.Pos}
|
||||
(h : IsLongestMatchAt pat startPos endPos) (h' : IsLongestMatchAt pat startPos endPos') :
|
||||
endPos = endPos' := by
|
||||
simpa using h.isLongestMatch_sliceFrom.eq h'.isLongestMatch_sliceFrom
|
||||
|
||||
private theorem isLongestMatch_of_eq {pat : ρ} [ForwardPatternModel pat] {s t : Slice}
|
||||
private theorem isLongestMatch_of_eq {pat : ρ} [PatternModel pat] {s t : Slice}
|
||||
{pos : s.Pos} {pos' : t.Pos} (h_eq : s = t) (h_pos : pos.offset = pos'.offset)
|
||||
(hm : IsLongestMatch pat pos) : IsLongestMatch pat pos' := by
|
||||
subst h_eq; exact (Slice.Pos.ext h_pos) ▸ hm
|
||||
|
||||
theorem isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat]
|
||||
theorem isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {base : s.Pos} {startPos endPos : (s.sliceFrom base).Pos} :
|
||||
IsLongestMatchAt pat startPos endPos ↔ IsLongestMatchAt pat (Pos.ofSliceFrom startPos) (Pos.ofSliceFrom endPos) := by
|
||||
constructor
|
||||
@@ -187,35 +269,88 @@ theorem isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom {pat : ρ} [ForwardPat
|
||||
exact isLongestMatch_of_eq Slice.sliceFrom_sliceFrom.symm
|
||||
(by simp [Pos.Raw.ext_iff]; omega) h.isLongestMatch_sliceFrom
|
||||
|
||||
theorem IsLongestMatch.isLongestMatchAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem IsLongestMatch.isLongestMatchAt_ofSliceFrom {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{p₀ : s.Pos} {pos : (s.sliceFrom p₀).Pos} (h : IsLongestMatch pat pos) :
|
||||
IsLongestMatchAt pat p₀ (Slice.Pos.ofSliceFrom pos) where
|
||||
le := Slice.Pos.le_ofSliceFrom
|
||||
isLongestMatch_sliceFrom := by simpa
|
||||
|
||||
@[simp]
|
||||
theorem isLongestMatchAt_startPos_iff {pat : ρ} [ForwardPatternModel pat] {s : Slice} {endPos : s.Pos} :
|
||||
theorem isLongestMatchAt_startPos_iff {pat : ρ} [PatternModel pat] {s : Slice} {endPos : s.Pos} :
|
||||
IsLongestMatchAt pat s.startPos endPos ↔ IsLongestMatch pat endPos := by
|
||||
simpa [isLongestMatchAt_iff] using
|
||||
⟨fun h => isLongestMatch_of_eq (by simp) (by simp) h,
|
||||
fun h => isLongestMatch_of_eq (by simp) (by simp) h⟩
|
||||
|
||||
/--
|
||||
Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match
|
||||
of {name}`pat` in {name}`s` and it is longest among matches ending at {name}`endPos`.
|
||||
-/
|
||||
structure IsLongestRevMatchAt (pat : ρ) [PatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where
|
||||
le : startPos ≤ endPos
|
||||
isLongestRevMatch_sliceTo : IsLongestRevMatch pat (Slice.Pos.sliceTo _ _ le)
|
||||
|
||||
theorem isLongestRevMatchAt_iff {pat : ρ} [PatternModel pat] {s : Slice} {pos₁ pos₂ : s.Pos} :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔
|
||||
∃ (h : pos₁ ≤ pos₂), IsLongestRevMatch pat (Slice.Pos.sliceTo _ _ h) :=
|
||||
⟨fun ⟨h, h'⟩ => ⟨h, h'⟩, fun ⟨h, h'⟩ => ⟨h, h'⟩⟩
|
||||
|
||||
theorem IsLongestRevMatchAt.lt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos}
|
||||
(h : IsLongestRevMatchAt pat startPos endPos) : startPos < endPos := by
|
||||
have := h.isLongestRevMatch_sliceTo.ne_endPos
|
||||
rw [← Pos.lt_endPos_iff, ← Slice.Pos.ofSliceTo_lt_ofSliceTo_iff] at this
|
||||
simpa
|
||||
|
||||
theorem IsLongestRevMatchAt.eq {pat : ρ} [PatternModel pat] {s : Slice} {startPos startPos' endPos : s.Pos}
|
||||
(h : IsLongestRevMatchAt pat startPos endPos) (h' : IsLongestRevMatchAt pat startPos' endPos) :
|
||||
startPos = startPos' := by
|
||||
simpa using h.isLongestRevMatch_sliceTo.eq h'.isLongestRevMatch_sliceTo
|
||||
|
||||
private theorem isLongestRevMatch_of_eq {pat : ρ} [PatternModel pat] {s t : Slice}
|
||||
{pos : s.Pos} {pos' : t.Pos} (h_eq : s = t) (h_pos : pos.offset = pos'.offset)
|
||||
(hm : IsLongestRevMatch pat pos) : IsLongestRevMatch pat pos' := by
|
||||
subst h_eq; exact (Slice.Pos.ext h_pos) ▸ hm
|
||||
|
||||
theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_ofSliceTo {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {base : s.Pos} {startPos endPos : (s.sliceTo base).Pos} :
|
||||
IsLongestRevMatchAt pat startPos endPos ↔ IsLongestRevMatchAt pat (Pos.ofSliceTo startPos) (Pos.ofSliceTo endPos) := by
|
||||
constructor
|
||||
· intro h
|
||||
refine ⟨Slice.Pos.ofSliceTo_le_ofSliceTo_iff.mpr h.le, ?_⟩
|
||||
exact isLongestRevMatch_of_eq Slice.sliceTo_sliceTo (by simp) h.isLongestRevMatch_sliceTo
|
||||
· intro h
|
||||
refine ⟨Slice.Pos.ofSliceTo_le_ofSliceTo_iff.mp h.le, ?_⟩
|
||||
exact isLongestRevMatch_of_eq Slice.sliceTo_sliceTo.symm (by simp) h.isLongestRevMatch_sliceTo
|
||||
|
||||
theorem IsLongestRevMatch.isLongestRevMatchAt_ofSliceTo {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{p₀ : s.Pos} {pos : (s.sliceTo p₀).Pos} (h : IsLongestRevMatch pat pos) :
|
||||
IsLongestRevMatchAt pat (Slice.Pos.ofSliceTo pos) p₀ where
|
||||
le := Slice.Pos.ofSliceTo_le
|
||||
isLongestRevMatch_sliceTo := by simpa
|
||||
|
||||
@[simp]
|
||||
theorem isLongestRevMatchAt_endPos_iff {pat : ρ} [PatternModel pat] {s : Slice} {startPos : s.Pos} :
|
||||
IsLongestRevMatchAt pat startPos s.endPos ↔ IsLongestRevMatch pat startPos := by
|
||||
simpa [isLongestRevMatchAt_iff] using
|
||||
⟨fun h => isLongestRevMatch_of_eq (by simp) (by simp) h,
|
||||
fun h => isLongestRevMatch_of_eq (by simp) (by simp) h⟩
|
||||
|
||||
/--
|
||||
Predicate stating that there is a (longest) match starting at the given position.
|
||||
-/
|
||||
structure MatchesAt (pat : ρ) [ForwardPatternModel pat] {s : Slice} (pos : s.Pos) : Prop where
|
||||
structure MatchesAt (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) : Prop where
|
||||
exists_isLongestMatchAt : ∃ endPos, IsLongestMatchAt pat pos endPos
|
||||
|
||||
theorem matchesAt_iff_exists_isLongestMatchAt {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem matchesAt_iff_exists_isLongestMatchAt {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{pos : s.Pos} : MatchesAt pat pos ↔ ∃ endPos, IsLongestMatchAt pat pos endPos :=
|
||||
⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩
|
||||
|
||||
theorem matchesAt_iff_exists_isLongestMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem matchesAt_iff_exists_isLongestMatch {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{pos : s.Pos} :
|
||||
MatchesAt pat pos ↔ ∃ (endPos : s.Pos), ∃ h, IsLongestMatch pat (pos.sliceFrom endPos h) :=
|
||||
⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestMatch_sliceFrom⟩, fun ⟨p, h₁, h₂⟩ => ⟨p, ⟨h₁, h₂⟩⟩⟩
|
||||
|
||||
theorem matchesAt_iff_exists_isMatch {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem matchesAt_iff_exists_isMatch {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{pos : s.Pos} :
|
||||
MatchesAt pat pos ↔ ∃ (endPos : s.Pos), ∃ h, IsMatch pat (pos.sliceFrom endPos h) := by
|
||||
refine ⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestMatch_sliceFrom.isMatch⟩, fun ⟨p, h₁, h₂⟩ => ?_⟩
|
||||
@@ -225,13 +360,13 @@ theorem matchesAt_iff_exists_isMatch {pat : ρ} [ForwardPatternModel pat] {s : S
|
||||
by simpa using hq⟩⟩
|
||||
|
||||
@[simp]
|
||||
theorem not_matchesAt_endPos {pat : ρ} [ForwardPatternModel pat] {s : Slice} :
|
||||
theorem not_matchesAt_endPos {pat : ρ} [PatternModel pat] {s : Slice} :
|
||||
¬ MatchesAt pat s.endPos := by
|
||||
simp only [matchesAt_iff_exists_isMatch, Pos.endPos_le, exists_prop_eq]
|
||||
intro h
|
||||
simpa [← Pos.ofSliceFrom_inj] using h.ne_startPos
|
||||
|
||||
theorem matchesAt_iff_matchesAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat] {s : Slice} {base : s.Pos}
|
||||
theorem matchesAt_iff_matchesAt_ofSliceFrom {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos}
|
||||
{pos : (s.sliceFrom base).Pos} : MatchesAt pat pos ↔ MatchesAt pat (Pos.ofSliceFrom pos) := by
|
||||
simp only [matchesAt_iff_exists_isLongestMatchAt]
|
||||
constructor
|
||||
@@ -241,21 +376,66 @@ theorem matchesAt_iff_matchesAt_ofSliceFrom {pat : ρ} [ForwardPatternModel pat]
|
||||
exact ⟨base.sliceFrom endPos (Std.le_trans Slice.Pos.le_ofSliceFrom h.le),
|
||||
isLongestMatchAt_iff_isLongestMatchAt_ofSliceFrom.mpr (by simpa using h)⟩
|
||||
|
||||
theorem IsLongestMatchAt.matchesAt {pat : ρ} [ForwardPatternModel pat] {s : Slice} {startPos endPos : s.Pos}
|
||||
theorem IsLongestMatchAt.matchesAt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos}
|
||||
(h : IsLongestMatchAt pat startPos endPos) : MatchesAt pat startPos where
|
||||
exists_isLongestMatchAt := ⟨_, h⟩
|
||||
|
||||
/--
|
||||
Predicate stating that there is a (longest) match ending at the given position.
|
||||
-/
|
||||
structure RevMatchesAt (pat : ρ) [PatternModel pat] {s : Slice} (pos : s.Pos) : Prop where
|
||||
exists_isLongestRevMatchAt : ∃ startPos, IsLongestRevMatchAt pat startPos pos
|
||||
|
||||
theorem revMatchesAt_iff_exists_isLongestRevMatchAt {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{pos : s.Pos} : RevMatchesAt pat pos ↔ ∃ startPos, IsLongestRevMatchAt pat startPos pos :=
|
||||
⟨fun ⟨h⟩ => h, fun h => ⟨h⟩⟩
|
||||
|
||||
theorem revMatchesAt_iff_exists_isLongestRevMatch {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{pos : s.Pos} :
|
||||
RevMatchesAt pat pos ↔ ∃ (startPos : s.Pos), ∃ h, IsLongestRevMatch pat (pos.sliceTo startPos h) :=
|
||||
⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestRevMatch_sliceTo⟩, fun ⟨p, h₁, h₂⟩ => ⟨p, ⟨h₁, h₂⟩⟩⟩
|
||||
|
||||
theorem revMatchesAt_iff_exists_isRevMatch {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{pos : s.Pos} :
|
||||
RevMatchesAt pat pos ↔ ∃ (startPos : s.Pos), ∃ h, IsRevMatch pat (pos.sliceTo startPos h) := by
|
||||
refine ⟨fun ⟨p, h⟩ => ⟨p, h.le, h.isLongestRevMatch_sliceTo.isRevMatch⟩, fun ⟨p, h₁, h₂⟩ => ?_⟩
|
||||
obtain ⟨q, hq⟩ := h₂.exists_isLongestRevMatch
|
||||
exact ⟨Pos.ofSliceTo q,
|
||||
⟨Std.le_trans (by simpa [← Pos.ofSliceTo_le_ofSliceTo_iff] using hq.le_of_isRevMatch h₂) h₁,
|
||||
by simpa using hq⟩⟩
|
||||
|
||||
@[simp]
|
||||
theorem not_revMatchesAt_startPos {pat : ρ} [PatternModel pat] {s : Slice} :
|
||||
¬ RevMatchesAt pat s.startPos := by
|
||||
simp only [revMatchesAt_iff_exists_isRevMatch, Pos.le_startPos, exists_prop_eq]
|
||||
intro h
|
||||
simpa [← Pos.ofSliceTo_inj] using h.ne_endPos
|
||||
|
||||
theorem revMatchesAt_iff_revMatchesAt_ofSliceto {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos}
|
||||
{pos : (s.sliceTo base).Pos} : RevMatchesAt pat pos ↔ RevMatchesAt pat (Pos.ofSliceTo pos) := by
|
||||
simp only [revMatchesAt_iff_exists_isLongestRevMatchAt]
|
||||
constructor
|
||||
· rintro ⟨startPos, h⟩
|
||||
exact ⟨Pos.ofSliceTo startPos, isLongestRevMatchAt_iff_isLongestRevMatchAt_ofSliceTo.mp h⟩
|
||||
· rintro ⟨startPos, h⟩
|
||||
exact ⟨base.sliceTo startPos (Std.le_trans h.le Slice.Pos.ofSliceTo_le),
|
||||
isLongestRevMatchAt_iff_isLongestRevMatchAt_ofSliceTo.mpr (by simpa using h)⟩
|
||||
|
||||
theorem IsLongestRevMatchAt.revMatchesAt {pat : ρ} [PatternModel pat] {s : Slice} {startPos endPos : s.Pos}
|
||||
(h : IsLongestRevMatchAt pat startPos endPos) : RevMatchesAt pat endPos where
|
||||
exists_isLongestRevMatchAt := ⟨_, h⟩
|
||||
|
||||
open Classical in
|
||||
/--
|
||||
Noncomputable model function returning the end point of the longest match starting at the given
|
||||
position, or {lean}`none` if there is no match.
|
||||
-/
|
||||
noncomputable def matchAt? {ρ : Type} (pat : ρ) [ForwardPatternModel pat]
|
||||
noncomputable def matchAt? {ρ : Type} (pat : ρ) [PatternModel pat]
|
||||
{s : Slice} (startPos : s.Pos) : Option s.Pos :=
|
||||
if h : ∃ endPos, IsLongestMatchAt pat startPos endPos then some h.choose else none
|
||||
|
||||
@[simp]
|
||||
theorem matchAt?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
theorem matchAt?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {startPos endPos : s.Pos} :
|
||||
matchAt? pat startPos = some endPos ↔ IsLongestMatchAt pat startPos endPos := by
|
||||
fun_cases matchAt? with
|
||||
@@ -263,40 +443,92 @@ theorem matchAt?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
| case2 => simp_all
|
||||
|
||||
@[simp]
|
||||
theorem matchAt?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
theorem matchAt?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {startPos : s.Pos} :
|
||||
matchAt? pat startPos = none ↔ ¬ MatchesAt pat startPos := by
|
||||
fun_cases matchAt? with
|
||||
| case1 h => simpa using ⟨h⟩
|
||||
| case2 h => simpa using fun ⟨h'⟩ => h h'
|
||||
|
||||
open Classical in
|
||||
/--
|
||||
Predicate stating compatibility between {name}`ForwardPatternModel` and {name}`ForwardPattern`.
|
||||
Noncomputable model function returning the start point of the longest match ending at the given
|
||||
position, or {lean}`none` if there is no match.
|
||||
-/
|
||||
noncomputable def revMatchAt? {ρ : Type} (pat : ρ) [PatternModel pat]
|
||||
{s : Slice} (endPos : s.Pos) : Option s.Pos :=
|
||||
if h : ∃ startPos, IsLongestRevMatchAt pat startPos endPos then some h.choose else none
|
||||
|
||||
@[simp]
|
||||
theorem revMatchAt?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {startPos endPos : s.Pos} :
|
||||
revMatchAt? pat endPos = some startPos ↔ IsLongestRevMatchAt pat startPos endPos := by
|
||||
fun_cases revMatchAt? with
|
||||
| case1 h => simpa using ⟨by rintro rfl; exact h.choose_spec, fun h' => h.choose_spec.eq h'⟩
|
||||
| case2 => simp_all
|
||||
|
||||
@[simp]
|
||||
theorem revMatchAt?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {endPos : s.Pos} :
|
||||
revMatchAt? pat endPos = none ↔ ¬ RevMatchesAt pat endPos := by
|
||||
fun_cases revMatchAt? with
|
||||
| case1 h => simpa using ⟨h⟩
|
||||
| case2 h => simpa using fun ⟨h'⟩ => h h'
|
||||
|
||||
/--
|
||||
Predicate stating compatibility between {name}`PatternModel` and {name}`ForwardPattern`.
|
||||
|
||||
This extends {name}`LawfulForwardPattern`, but it is much stronger because it forces the
|
||||
{name}`ForwardPattern` to match the longest prefix of the given slice that matches the property
|
||||
supplied by the {name}`ForwardPatternModel` instance.
|
||||
supplied by the {name}`PatternModel` instance.
|
||||
-/
|
||||
class LawfulForwardPatternModel {ρ : Type} (pat : ρ) [ForwardPattern pat]
|
||||
[ForwardPatternModel pat] : Prop extends LawfulForwardPattern pat where
|
||||
[PatternModel pat] : Prop extends LawfulForwardPattern pat where
|
||||
skipPrefix?_eq_some_iff (pos) : ForwardPattern.skipPrefix? pat s = some pos ↔ IsLongestMatch pat pos
|
||||
|
||||
open Classical in
|
||||
theorem LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [ForwardPatternModel pat]
|
||||
theorem LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [PatternModel pat]
|
||||
[LawfulForwardPatternModel pat] {s : Slice} {p₀ : s.Pos} :
|
||||
ForwardPattern.skipPrefix? pat (s.sliceFrom p₀) = none ↔ ¬ MatchesAt pat p₀ := by
|
||||
classical
|
||||
rw [← Decidable.not_iff_not]
|
||||
simp [Option.ne_none_iff_exists', LawfulForwardPatternModel.skipPrefix?_eq_some_iff]
|
||||
refine ⟨fun ⟨p, hp⟩ => ?_, fun ⟨p, hp⟩ => ?_⟩
|
||||
· exact ⟨Slice.Pos.ofSliceFrom p, hp.isLongestMatchAt_ofSliceFrom⟩
|
||||
· exact ⟨p₀.sliceFrom p hp.le, hp.isLongestMatch_sliceFrom⟩
|
||||
|
||||
theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [ForwardPatternModel pat]
|
||||
theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [PatternModel pat]
|
||||
[LawfulForwardPatternModel pat] {s : Slice} :
|
||||
ForwardPattern.skipPrefix? pat s = none ↔ ¬ MatchesAt pat s.startPos := by
|
||||
conv => lhs; rw [← sliceFrom_startPos (s := s)]
|
||||
simp [skipPrefix?_sliceFrom_eq_none_iff]
|
||||
|
||||
/--
|
||||
Predicate stating compatibility between {name}`PatternModel` and {name}`BackwardPattern`.
|
||||
|
||||
This extends {name}`LawfulForwardPattern`, but it is much stronger because it forces the
|
||||
{name}`ForwardPattern` to match the longest prefix of the given slice that matches the property
|
||||
supplied by the {name}`PatternModel` instance.
|
||||
-/
|
||||
class LawfulBackwardPatternModel {ρ : Type} (pat : ρ) [BackwardPattern pat]
|
||||
[PatternModel pat] : Prop extends LawfulBackwardPattern pat where
|
||||
skipSuffix?_eq_some_iff (pos) : BackwardPattern.skipSuffix? pat s = some pos ↔ IsLongestRevMatch pat pos
|
||||
|
||||
theorem LawfulBackwardPatternModel.skipSuffix?_sliceTo_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [PatternModel pat]
|
||||
[LawfulBackwardPatternModel pat] {s : Slice} {p₀ : s.Pos} :
|
||||
BackwardPattern.skipSuffix? pat (s.sliceTo p₀) = none ↔ ¬ RevMatchesAt pat p₀ := by
|
||||
classical
|
||||
rw [← Decidable.not_iff_not]
|
||||
simp [Option.ne_none_iff_exists', LawfulBackwardPatternModel.skipSuffix?_eq_some_iff]
|
||||
refine ⟨fun ⟨p, hp⟩ => ?_, fun ⟨p, hp⟩ => ?_⟩
|
||||
· exact ⟨Slice.Pos.ofSliceTo p, hp.isLongestRevMatchAt_ofSliceTo⟩
|
||||
· exact ⟨p₀.sliceTo p hp.le, hp.isLongestRevMatch_sliceTo⟩
|
||||
|
||||
theorem LawfulBackwardPatternModel.skipSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [PatternModel pat]
|
||||
[LawfulBackwardPatternModel pat] {s : Slice} :
|
||||
BackwardPattern.skipSuffix? pat s = none ↔ ¬ RevMatchesAt pat s.endPos := by
|
||||
conv => lhs; rw [← sliceTo_endPos (s := s)]
|
||||
simp [skipSuffix?_sliceTo_eq_none_iff]
|
||||
|
||||
/--
|
||||
Inductive predicate stating that a list of search steps represents a valid search from a given
|
||||
position in a slice.
|
||||
@@ -306,7 +538,7 @@ matches.
|
||||
|
||||
Hence, this predicate determines the list of search steps up to grouping of rejections.
|
||||
-/
|
||||
inductive IsValidSearchFrom (pat : ρ) [ForwardPatternModel pat] {s : Slice} :
|
||||
inductive IsValidSearchFrom (pat : ρ) [PatternModel pat] {s : Slice} :
|
||||
s.Pos → List (SearchStep s) → Prop where
|
||||
| endPos : IsValidSearchFrom pat s.endPos []
|
||||
| matched {startPos endPos : s.Pos} :
|
||||
@@ -316,14 +548,14 @@ inductive IsValidSearchFrom (pat : ρ) [ForwardPatternModel pat] {s : Slice} :
|
||||
(∀ pos, startPos ≤ pos → pos < endPos → ¬ MatchesAt pat pos) →
|
||||
IsValidSearchFrom pat endPos l → IsValidSearchFrom pat startPos (.rejected startPos endPos :: l)
|
||||
|
||||
theorem IsValidSearchFrom.matched_of_eq {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem IsValidSearchFrom.matched_of_eq {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{startPos startPos' endPos : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidSearchFrom pat endPos l)
|
||||
(h₂ : IsLongestMatchAt pat startPos' endPos)
|
||||
(h₃ : startPos = startPos') : IsValidSearchFrom pat startPos' (.matched startPos endPos :: l) := by
|
||||
cases h₃
|
||||
exact IsValidSearchFrom.matched h₂ h₁
|
||||
|
||||
theorem IsValidSearchFrom.mismatched_of_eq {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem IsValidSearchFrom.mismatched_of_eq {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{startPos startPos' endPos : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidSearchFrom pat endPos l)
|
||||
(h₀ : startPos' < endPos)
|
||||
(h₂ : ∀ pos, startPos' ≤ pos → pos < endPos → ¬ MatchesAt pat pos) (h₃ : startPos = startPos') :
|
||||
@@ -331,7 +563,7 @@ theorem IsValidSearchFrom.mismatched_of_eq {pat : ρ} [ForwardPatternModel pat]
|
||||
cases h₃
|
||||
exact IsValidSearchFrom.mismatched h₀ h₂ h₁
|
||||
|
||||
theorem IsValidSearchFrom.endPos_of_eq {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
theorem IsValidSearchFrom.endPos_of_eq {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{p : s.Pos} {l : List (SearchStep s)} (hp : p = s.endPos) (hl : l = []) :
|
||||
IsValidSearchFrom pat p l := by
|
||||
cases hp
|
||||
@@ -339,18 +571,18 @@ theorem IsValidSearchFrom.endPos_of_eq {pat : ρ} [ForwardPatternModel pat] {s :
|
||||
exact IsValidSearchFrom.endPos
|
||||
|
||||
/--
|
||||
Predicate stating compatibility between {name}`ForwardPatternModel` and {name}`ToForwardSearcher`.
|
||||
Predicate stating compatibility between {name}`PatternModel` and {name}`ToForwardSearcher`.
|
||||
|
||||
We require the searcher to always match the longest match at the first position where the pattern
|
||||
matches; see {name}`IsValidSearchFrom`.
|
||||
-/
|
||||
class LawfulToForwardSearcherModel {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
class LawfulToForwardSearcherModel {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type}
|
||||
[ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)]
|
||||
[∀ s, Std.Iterators.Finite (σ s) Id] : Prop where
|
||||
isValidSearchFrom_toList (s) : IsValidSearchFrom pat s.startPos (ToForwardSearcher.toSearcher pat s).toList
|
||||
|
||||
theorem LawfulToForwardSearcherModel.defaultImplementation {pat : ρ} [ForwardPattern pat] [StrictForwardPattern pat]
|
||||
[ForwardPatternModel pat] [LawfulForwardPatternModel pat] :
|
||||
[PatternModel pat] [LawfulForwardPatternModel pat] :
|
||||
letI : ToForwardSearcher pat (ToForwardSearcher.DefaultForwardSearcher pat) := .defaultImplementation
|
||||
LawfulToForwardSearcherModel pat := by
|
||||
let inst : ToForwardSearcher pat (ToForwardSearcher.DefaultForwardSearcher pat) := .defaultImplementation
|
||||
@@ -390,4 +622,97 @@ theorem LawfulToForwardSearcherModel.defaultImplementation {pat : ρ} [ForwardPa
|
||||
· split at heq <;> simp at heq
|
||||
· split at heq <;> simp at heq
|
||||
|
||||
/--
|
||||
Inductive predicate stating that a list of search steps represents a valid backwards search from a
|
||||
given position in a slice.
|
||||
|
||||
"Searching" here means always taking the longest match at the first position where the pattern
|
||||
matches.
|
||||
|
||||
Hence, this predicate determines the list of search steps up to grouping of rejections.
|
||||
-/
|
||||
inductive IsValidRevSearchFrom (pat : ρ) [PatternModel pat] {s : Slice} :
|
||||
s.Pos → List (SearchStep s) → Prop where
|
||||
| startPos : IsValidRevSearchFrom pat s.startPos []
|
||||
| matched {startPos endPos : s.Pos} :
|
||||
IsLongestRevMatchAt pat startPos endPos → IsValidRevSearchFrom pat startPos l →
|
||||
IsValidRevSearchFrom pat endPos (.matched startPos endPos :: l)
|
||||
| mismatched {startPos endPos : s.Pos} : startPos < endPos →
|
||||
(∀ pos, startPos < pos → pos ≤ endPos → ¬ RevMatchesAt pat pos) →
|
||||
IsValidRevSearchFrom pat startPos l → IsValidRevSearchFrom pat endPos (.rejected startPos endPos :: l)
|
||||
|
||||
theorem IsValidRevSearchFrom.matched_of_eq {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{startPos endPos endPos' : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidRevSearchFrom pat startPos l)
|
||||
(h₂ : IsLongestRevMatchAt pat startPos endPos')
|
||||
(h₃ : endPos = endPos') : IsValidRevSearchFrom pat endPos' (.matched startPos endPos :: l) := by
|
||||
cases h₃
|
||||
exact IsValidRevSearchFrom.matched h₂ h₁
|
||||
|
||||
theorem IsValidRevSearchFrom.mismatched_of_eq {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{startPos endPos endPos' : s.Pos} {l : List (SearchStep s)} (h₁ : IsValidRevSearchFrom pat startPos l)
|
||||
(h₀ : startPos < endPos')
|
||||
(h₂ : ∀ pos, startPos < pos → pos ≤ endPos' → ¬ RevMatchesAt pat pos) (h₃ : endPos = endPos') :
|
||||
IsValidRevSearchFrom pat endPos' (.rejected startPos endPos :: l) := by
|
||||
cases h₃
|
||||
exact IsValidRevSearchFrom.mismatched h₀ h₂ h₁
|
||||
|
||||
theorem IsValidRevSearchFrom.startPos_of_eq {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{p : s.Pos} {l : List (SearchStep s)} (hp : p = s.startPos) (hl : l = []) :
|
||||
IsValidRevSearchFrom pat p l := by
|
||||
cases hp
|
||||
cases hl
|
||||
exact IsValidRevSearchFrom.startPos
|
||||
|
||||
/--
|
||||
Predicate stating compatibility between {name}`PatternModel` and {name}`ToBackwardSearcher`.
|
||||
|
||||
We require the searcher to always match the longest match at the first position where the pattern
|
||||
matches; see {name}`IsValidRevSearchFrom`.
|
||||
-/
|
||||
class LawfulToBackwardSearcherModel {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type}
|
||||
[ToBackwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)]
|
||||
[∀ s, Std.Iterators.Finite (σ s) Id] : Prop where
|
||||
isValidRevSearchFrom_toList (s) : IsValidRevSearchFrom pat s.endPos (ToBackwardSearcher.toSearcher pat s).toList
|
||||
|
||||
theorem LawfulToBackwardSearcherModel.defaultImplementation {pat : ρ} [BackwardPattern pat] [StrictBackwardPattern pat]
|
||||
[PatternModel pat] [LawfulBackwardPatternModel pat] :
|
||||
letI : ToBackwardSearcher pat (ToBackwardSearcher.DefaultBackwardSearcher pat) := .defaultImplementation
|
||||
LawfulToBackwardSearcherModel pat := by
|
||||
let inst : ToBackwardSearcher pat (ToBackwardSearcher.DefaultBackwardSearcher pat) := .defaultImplementation
|
||||
refine ⟨fun s => ?_⟩
|
||||
suffices ∀ (pos : s.Pos),
|
||||
IsValidRevSearchFrom pat pos (Std.Iter.mk (α := ToBackwardSearcher.DefaultBackwardSearcher pat s) ⟨pos⟩).toList from
|
||||
this s.endPos
|
||||
intro pos
|
||||
induction pos using WellFounded.induction Slice.Pos.wellFounded_lt with | h pos ih
|
||||
rw [Std.Iter.toList_eq_match_step, Std.Iter.step_eq]
|
||||
simp only [Std.Iter.toIterM, ne_eq]
|
||||
by_cases h : pos = s.startPos
|
||||
· simpa [h] using IsValidRevSearchFrom.startPos
|
||||
· simp only [h, ↓reduceDIte]
|
||||
split <;> rename_i heq
|
||||
· split at heq <;> rename_i pos' heq'
|
||||
· simp only [Id.run_pure, Std.Shrink.inflate_deflate, Std.IterM.Step.toPure_yield,
|
||||
Std.PlausibleIterStep.yield, Std.IterStep.yield.injEq] at heq
|
||||
rw [← heq.1, ← heq.2]
|
||||
apply IsValidRevSearchFrom.matched
|
||||
· rw [LawfulBackwardPattern.skipSuffixOfNonempty?_eq,
|
||||
LawfulBackwardPatternModel.skipSuffix?_eq_some_iff] at heq'
|
||||
exact heq'.isLongestRevMatchAt_ofSliceTo
|
||||
· simp only [Std.IterM.toIter]
|
||||
apply ih
|
||||
refine Std.lt_of_lt_of_le (Slice.Pos.ofSliceTo_lt_ofSliceTo_iff.2 ?_)
|
||||
(Slice.Pos.ofSliceTo_le (pos := Slice.endPos _))
|
||||
simpa using StrictBackwardPattern.ne_endPos _ _ heq'
|
||||
· simp only [Id.run_pure, Std.Shrink.inflate_deflate, Std.IterM.Step.toPure_yield,
|
||||
Std.PlausibleIterStep.yield, Std.IterStep.yield.injEq] at heq
|
||||
rw [← heq.1, ← heq.2]
|
||||
apply IsValidRevSearchFrom.mismatched (by simp) _ (ih _ (by simp))
|
||||
intro p' hp' hp''
|
||||
obtain rfl : pos = p' := Std.le_antisymm (by simpa using hp') hp''
|
||||
rwa [LawfulBackwardPattern.skipSuffixOfNonempty?_eq,
|
||||
LawfulBackwardPatternModel.skipSuffix?_sliceTo_eq_none_iff] at heq'
|
||||
· split at heq <;> simp at heq
|
||||
· split at heq <;> simp at heq
|
||||
|
||||
end String.Slice.Pattern.Model
|
||||
|
||||
@@ -20,28 +20,42 @@ import Init.Data.String.Lemmas.Order
|
||||
import Init.Data.Order.Lemmas
|
||||
import Init.Data.String.OrderInstances
|
||||
import Init.Omega
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
|
||||
public section
|
||||
|
||||
namespace String.Slice.Pattern.Model.Char
|
||||
|
||||
instance {c : Char} : ForwardPatternModel c where
|
||||
instance {c : Char} : PatternModel c where
|
||||
Matches s := s = String.singleton c
|
||||
not_matches_empty := by simp
|
||||
|
||||
instance {c : Char} : NoPrefixForwardPatternModel c :=
|
||||
.of_length_eq (by simp +contextual [ForwardPatternModel.Matches])
|
||||
instance {c : Char} : NoPrefixPatternModel c :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {c : Char} : NoSuffixPatternModel c :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsMatch c pos ↔
|
||||
∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ s.startPos.get h = c := by
|
||||
simp only [Model.isMatch_iff, ForwardPatternModel.Matches, sliceTo_copy_eq_iff_exists_splits]
|
||||
simp only [Model.isMatch_iff, PatternModel.Matches, copy_sliceTo_eq_iff_exists_splits]
|
||||
refine ⟨?_, ?_⟩
|
||||
· simp only [splits_singleton_iff]
|
||||
exact fun ⟨t₂, h, h₁, h₂, h₃⟩ => ⟨h, h₁, h₂⟩
|
||||
· rintro ⟨h, rfl, rfl⟩
|
||||
exact ⟨_, Slice.splits_next_startPos⟩
|
||||
|
||||
theorem isRevMatch_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch c pos ↔
|
||||
∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by
|
||||
simp only [Model.isRevMatch_iff, PatternModel.Matches, copy_sliceFrom_eq_iff_exists_splits]
|
||||
refine ⟨?_, ?_⟩
|
||||
· simp only [splits_singleton_right_iff]
|
||||
exact fun ⟨t₂, h, h₁, h₂, h₃⟩ => ⟨h, h₁, h₂⟩
|
||||
· rintro ⟨h, rfl, rfl⟩
|
||||
exact ⟨_, Slice.splits_prev_endPos⟩
|
||||
|
||||
theorem isLongestMatch_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestMatch c pos ↔
|
||||
∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ s.startPos.get h = c := by
|
||||
@@ -52,21 +66,46 @@ theorem isLongestMatchAt_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
|
||||
simp +contextual [Model.isLongestMatchAt_iff, isLongestMatch_iff, ← Pos.ofSliceFrom_inj,
|
||||
Pos.get_eq_get_ofSliceFrom, Pos.ofSliceFrom_next]
|
||||
|
||||
theorem isLongestRevMatch_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestRevMatch c pos ↔
|
||||
∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by
|
||||
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff]
|
||||
|
||||
theorem isLongestRevMatchAt_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestRevMatchAt c pos pos' ↔ ∃ h, pos = pos'.prev h ∧ (pos'.prev h).get (by simp) = c := by
|
||||
simp +contextual [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff, ← Pos.ofSliceTo_inj,
|
||||
Pos.get_eq_get_ofSliceTo, Pos.ofSliceTo_prev]
|
||||
|
||||
theorem isLongestMatchAt_of_get_eq {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos}
|
||||
(hc : pos.get h = c) : IsLongestMatchAt c pos (pos.next h) :=
|
||||
isLongestMatchAt_iff.2 ⟨h, by simp [hc]⟩
|
||||
|
||||
theorem isLongestRevMatchAt_of_get_eq {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos}
|
||||
(hc : (pos.prev h).get (by simp) = c) : IsLongestRevMatchAt c (pos.prev h) pos :=
|
||||
isLongestRevMatchAt_iff.2 ⟨h, by simp [hc]⟩
|
||||
|
||||
instance {c : Char} : LawfulForwardPatternModel c where
|
||||
skipPrefix?_eq_some_iff {s} pos := by
|
||||
simp [isLongestMatch_iff, ForwardPattern.skipPrefix?, and_comm, eq_comm (b := pos)]
|
||||
|
||||
instance {c : Char} : LawfulBackwardPatternModel c where
|
||||
skipSuffix?_eq_some_iff {s} pos := by
|
||||
simp [isLongestRevMatch_iff, BackwardPattern.skipSuffix?, and_comm, eq_comm (b := pos)]
|
||||
|
||||
theorem toSearcher_eq {c : Char} {s : Slice} :
|
||||
ToForwardSearcher.toSearcher c s = ToForwardSearcher.toSearcher (· == c) s := (rfl)
|
||||
|
||||
theorem toBackwardSearcher_eq {c : Char} {s : Slice} :
|
||||
ToBackwardSearcher.toSearcher c s = ToBackwardSearcher.toSearcher (· == c) s := (rfl)
|
||||
|
||||
theorem matchesAt_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
MatchesAt c pos ↔ ∃ (h : pos ≠ s.endPos), pos.get h = c := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm]
|
||||
|
||||
theorem revMatchesAt_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
RevMatchesAt c pos ↔ ∃ (h : pos ≠ s.startPos), (pos.prev h).get (by simp) = c := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm]
|
||||
|
||||
theorem matchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
MatchesAt c pos ↔ ∃ t₁ t₂, pos.Splits t₁ (singleton c ++ t₂) := by
|
||||
rw [matchesAt_iff]
|
||||
@@ -77,37 +116,81 @@ theorem matchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
have hne := hs.ne_endPos_of_singleton
|
||||
exact ⟨hne, (singleton_append_inj.mp (hs.eq_right (pos.splits_next_right hne))).1.symm⟩
|
||||
|
||||
theorem revMatchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
RevMatchesAt c pos ↔ ∃ t₁ t₂, pos.Splits (t₁ ++ singleton c) t₂ := by
|
||||
rw [revMatchesAt_iff]
|
||||
refine ⟨?_, ?_⟩
|
||||
· rintro ⟨h, rfl⟩
|
||||
exact ⟨_, _, pos.splits_prev_right h⟩
|
||||
· rintro ⟨t₁, t₂, hs⟩
|
||||
have hne := hs.ne_startPos_of_singleton
|
||||
refine ⟨hne, ?_⟩
|
||||
have := hs.eq_left (pos.splits_prev_right hne)
|
||||
simp only [append_singleton, push_inj] at this
|
||||
exact this.2.symm
|
||||
|
||||
theorem not_matchesAt_of_get_ne {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos}
|
||||
(hc : pos.get h ≠ c) : ¬ MatchesAt c pos := by
|
||||
simp [matchesAt_iff, hc]
|
||||
|
||||
theorem not_revMatchesAt_of_get_ne {c : Char} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos}
|
||||
(hc : (pos.prev h).get (by simp) ≠ c) : ¬ RevMatchesAt c pos := by
|
||||
simp [revMatchesAt_iff, hc]
|
||||
|
||||
theorem matchAt?_eq {s : Slice} {pos : s.Pos} {c : Char} :
|
||||
matchAt? c pos =
|
||||
if h₀ : ∃ (h : pos ≠ s.endPos), pos.get h = c then some (pos.next h₀.1) else none := by
|
||||
split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff]
|
||||
|
||||
theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {c : Char} :
|
||||
revMatchAt? c pos =
|
||||
if h₀ : ∃ (h : pos ≠ s.startPos), (pos.prev h).get (by simp) = c then some (pos.prev h₀.1) else none := by
|
||||
split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff]
|
||||
|
||||
theorem isMatch_iff_isMatch_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsMatch c pos ↔ IsMatch (· == c) pos := by
|
||||
simp [isMatch_iff, CharPred.isMatch_iff, beq_iff_eq]
|
||||
|
||||
theorem isRevMatch_iff_isRevMatch_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch c pos ↔ IsRevMatch (· == c) pos := by
|
||||
simp [isRevMatch_iff, CharPred.isRevMatch_iff, beq_iff_eq]
|
||||
|
||||
theorem isLongestMatch_iff_isLongestMatch_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestMatch c pos ↔ IsLongestMatch (· == c) pos := by
|
||||
simp [isLongestMatch_iff_isMatch, isMatch_iff_isMatch_beq]
|
||||
|
||||
theorem isLongestRevMatch_iff_isLongestRevMatch_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestRevMatch c pos ↔ IsLongestRevMatch (· == c) pos := by
|
||||
simp [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff_isRevMatch_beq]
|
||||
|
||||
theorem isLongestMatchAt_iff_isLongestMatchAt_beq {c : Char} {s : Slice}
|
||||
{pos pos' : s.Pos} :
|
||||
IsLongestMatchAt c pos pos' ↔ IsLongestMatchAt (· == c) pos pos' := by
|
||||
simp [Model.isLongestMatchAt_iff, isLongestMatch_iff_isLongestMatch_beq]
|
||||
|
||||
theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_beq {c : Char} {s : Slice}
|
||||
{pos pos' : s.Pos} :
|
||||
IsLongestRevMatchAt c pos pos' ↔ IsLongestRevMatchAt (· == c) pos pos' := by
|
||||
simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff_isLongestRevMatch_beq]
|
||||
|
||||
theorem matchesAt_iff_matchesAt_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
MatchesAt c pos ↔ MatchesAt (· == c) pos := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_beq]
|
||||
|
||||
theorem revMatchesAt_iff_revMatchesAt_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
RevMatchesAt c pos ↔ RevMatchesAt (· == c) pos := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq]
|
||||
|
||||
theorem matchAt?_eq_matchAt?_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
matchAt? c pos = matchAt? (· == c) pos := by
|
||||
refine Option.ext (fun pos' => ?_)
|
||||
simp [matchAt?_eq_some_iff, isLongestMatchAt_iff_isLongestMatchAt_beq]
|
||||
|
||||
theorem revMatchAt?_eq_revMatchAt?_beq {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
revMatchAt? c pos = revMatchAt? (· == c) pos := by
|
||||
refine Option.ext (fun pos' => ?_)
|
||||
simp [revMatchAt?_eq_some_iff, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq]
|
||||
|
||||
theorem isValidSearchFrom_iff_isValidSearchFrom_beq {c : Char} {s : Slice} {p : s.Pos}
|
||||
{l : List (SearchStep s)} : IsValidSearchFrom c p l ↔ IsValidSearchFrom (· == c) p l := by
|
||||
refine ⟨fun h => ?_, fun h => ?_⟩
|
||||
@@ -120,11 +203,28 @@ theorem isValidSearchFrom_iff_isValidSearchFrom_beq {c : Char} {s : Slice} {p :
|
||||
| matched => simp_all [IsValidSearchFrom.matched, isLongestMatchAt_iff_isLongestMatchAt_beq]
|
||||
| mismatched => simp_all [IsValidSearchFrom.mismatched, matchesAt_iff_matchesAt_beq]
|
||||
|
||||
theorem isValidRevSearchFrom_iff_isValidRevSearchFrom_beq {c : Char} {s : Slice} {p : s.Pos}
|
||||
{l : List (SearchStep s)} : IsValidRevSearchFrom c p l ↔ IsValidRevSearchFrom (· == c) p l := by
|
||||
refine ⟨fun h => ?_, fun h => ?_⟩
|
||||
· induction h with
|
||||
| startPos => simpa using IsValidRevSearchFrom.startPos
|
||||
| matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq]
|
||||
| mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_beq]
|
||||
· induction h with
|
||||
| startPos => simpa using IsValidRevSearchFrom.startPos
|
||||
| matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_beq]
|
||||
| mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_beq]
|
||||
|
||||
instance {c : Char} : LawfulToForwardSearcherModel c where
|
||||
isValidSearchFrom_toList s := by
|
||||
simpa [toSearcher_eq, isValidSearchFrom_iff_isValidSearchFrom_beq] using
|
||||
LawfulToForwardSearcherModel.isValidSearchFrom_toList (pat := (· == c)) (s := s)
|
||||
|
||||
instance {c : Char} : LawfulToBackwardSearcherModel c where
|
||||
isValidRevSearchFrom_toList s := by
|
||||
simpa [toBackwardSearcher_eq, isValidRevSearchFrom_iff_isValidRevSearchFrom_beq] using
|
||||
LawfulToBackwardSearcherModel.isValidRevSearchFrom_toList (pat := (· == c)) (s := s)
|
||||
|
||||
end Pattern.Model.Char
|
||||
|
||||
theorem startsWith_char_eq_startsWith_beq {c : Char} {s : Slice} :
|
||||
|
||||
@@ -23,7 +23,7 @@ open Std String.Slice Pattern Pattern.Model
|
||||
|
||||
namespace String.Slice
|
||||
|
||||
theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type}
|
||||
[∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id]
|
||||
[∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id]
|
||||
[ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} {pos : s.Pos} :
|
||||
@@ -40,7 +40,7 @@ theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [ForwardPatternMo
|
||||
| matched h₁ _ _ => have := h₁.matchesAt; grind
|
||||
| mismatched => grind
|
||||
|
||||
theorem Pattern.Model.find?_eq_none_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
theorem Pattern.Model.find?_eq_none_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type}
|
||||
[∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id]
|
||||
[∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id]
|
||||
[ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} :
|
||||
@@ -65,14 +65,14 @@ theorem find?_eq_none_iff {ρ : Type} (pat : ρ) {σ : Slice → Type}
|
||||
[ToForwardSearcher pat σ] {s : Slice} : s.find? pat = none ↔ s.contains pat = false := by
|
||||
rw [← Option.isNone_iff_eq_none, ← Option.isSome_eq_false_iff, isSome_find?]
|
||||
|
||||
theorem Pattern.Model.contains_eq_false_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
theorem Pattern.Model.contains_eq_false_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type}
|
||||
[∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id]
|
||||
[∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id]
|
||||
[ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} :
|
||||
s.contains pat = false ↔ ∀ (pos : s.Pos), ¬ MatchesAt pat pos := by
|
||||
rw [← find?_eq_none_iff, Slice.find?_eq_none_iff]
|
||||
|
||||
theorem Pattern.Model.contains_eq_true_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
theorem Pattern.Model.contains_eq_true_iff {ρ : Type} (pat : ρ) [PatternModel pat] {σ : Slice → Type}
|
||||
[∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id]
|
||||
[∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id]
|
||||
[ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} :
|
||||
@@ -85,7 +85,7 @@ theorem Pos.find?_eq_find?_sliceFrom {ρ : Type} {pat : ρ} {σ : Slice → Type
|
||||
p.find? pat = ((s.sliceFrom p).find? pat).map Pos.ofSliceFrom :=
|
||||
(rfl)
|
||||
|
||||
theorem Pattern.Model.posFind?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
theorem Pattern.Model.posFind?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] {σ : Slice → Type}
|
||||
[∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id]
|
||||
[∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id]
|
||||
[ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} {pos pos' : s.Pos} :
|
||||
@@ -100,7 +100,7 @@ theorem Pattern.Model.posFind?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatter
|
||||
refine ⟨Pos.sliceFrom _ _ h₁, ⟨by simpa using h₂, fun p hp₁ hp₂ => ?_⟩, by simp⟩
|
||||
exact h₃ (Pos.ofSliceFrom p) Slice.Pos.le_ofSliceFrom (Pos.lt_sliceFrom_iff.1 hp₁) hp₂
|
||||
|
||||
theorem Pattern.Model.posFind?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {σ : Slice → Type}
|
||||
theorem Pattern.Model.posFind?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] {σ : Slice → Type}
|
||||
[∀ s, Iterator (σ s) Id (SearchStep s)] [∀ s, Iterators.Finite (σ s) Id]
|
||||
[∀ s, IteratorLoop (σ s) Id Id] [∀ s, LawfulIteratorLoop (σ s) Id Id]
|
||||
[ToForwardSearcher pat σ] [LawfulToForwardSearcherModel pat] {s : Slice} {pos : s.Pos} :
|
||||
|
||||
@@ -19,124 +19,228 @@ import Init.Data.String.Lemmas.Order
|
||||
import Init.Data.Order.Lemmas
|
||||
import Init.Data.String.OrderInstances
|
||||
import Init.Omega
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
|
||||
public section
|
||||
|
||||
namespace String.Slice.Pattern.Model.CharPred
|
||||
|
||||
instance {p : Char → Bool} : ForwardPatternModel p where
|
||||
instance {p : Char → Bool} : PatternModel p where
|
||||
Matches s := ∃ c, s = singleton c ∧ p c
|
||||
not_matches_empty := by
|
||||
simp
|
||||
|
||||
instance {p : Char → Bool} : NoPrefixForwardPatternModel p :=
|
||||
.of_length_eq (by simp +contextual [ForwardPatternModel.Matches])
|
||||
instance {p : Char → Bool} : NoPrefixPatternModel p :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {p : Char → Bool} : NoSuffixPatternModel p :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
IsMatch p pos ↔
|
||||
∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by
|
||||
simp only [Model.isMatch_iff, ForwardPatternModel.Matches, sliceTo_copy_eq_iff_exists_splits]
|
||||
simp only [Model.isMatch_iff, PatternModel.Matches, copy_sliceTo_eq_iff_exists_splits]
|
||||
refine ⟨?_, ?_⟩
|
||||
· simp only [splits_singleton_iff]
|
||||
refine fun ⟨c, ⟨t₂, h, h₁, h₂, h₃⟩, hc⟩ => ⟨h, h₁, h₂ ▸ hc⟩
|
||||
· rintro ⟨h, rfl, h'⟩
|
||||
exact ⟨s.startPos.get h, ⟨_, Slice.splits_next_startPos⟩, h'⟩
|
||||
|
||||
theorem isRevMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch p pos ↔
|
||||
∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by
|
||||
simp only [Model.isRevMatch_iff, PatternModel.Matches, copy_sliceFrom_eq_iff_exists_splits]
|
||||
refine ⟨?_, ?_⟩
|
||||
· simp only [splits_singleton_right_iff]
|
||||
refine fun ⟨c, ⟨t₂, h, h₁, h₂, h₃⟩, hc⟩ => ⟨h, h₁, h₂ ▸ hc⟩
|
||||
· rintro ⟨h, rfl, h'⟩
|
||||
exact ⟨(s.endPos.prev h).get (by simp), ⟨_, Slice.splits_prev_endPos⟩, h'⟩
|
||||
|
||||
theorem isLongestMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestMatch p pos ↔
|
||||
∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by
|
||||
rw [isLongestMatch_iff_isMatch, isMatch_iff]
|
||||
|
||||
theorem isLongestRevMatch_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestRevMatch p pos ↔
|
||||
∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by
|
||||
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff]
|
||||
|
||||
theorem isLongestMatchAt_iff {p : Char → Bool} {s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestMatchAt p pos pos' ↔ ∃ h, pos' = pos.next h ∧ p (pos.get h) := by
|
||||
simp +contextual [Model.isLongestMatchAt_iff, isLongestMatch_iff, ← Pos.ofSliceFrom_inj,
|
||||
Pos.get_eq_get_ofSliceFrom, Pos.ofSliceFrom_next]
|
||||
|
||||
theorem isLongestRevMatchAt_iff {p : Char → Bool} {s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestRevMatchAt p pos pos' ↔ ∃ h, pos = pos'.prev h ∧ p ((pos'.prev h).get (by simp)) := by
|
||||
simp +contextual [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff, ← Pos.ofSliceTo_inj,
|
||||
Pos.get_eq_get_ofSliceTo, Pos.ofSliceTo_prev]
|
||||
|
||||
theorem isLongestMatchAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos}
|
||||
(hc : p (pos.get h)) : IsLongestMatchAt p pos (pos.next h) :=
|
||||
isLongestMatchAt_iff.2 ⟨h, by simp [hc]⟩
|
||||
|
||||
theorem isLongestRevMatchAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos}
|
||||
(hc : p ((pos.prev h).get (by simp))) : IsLongestRevMatchAt p (pos.prev h) pos :=
|
||||
isLongestRevMatchAt_iff.2 ⟨h, by simp [hc]⟩
|
||||
|
||||
instance {p : Char → Bool} : LawfulForwardPatternModel p where
|
||||
skipPrefix?_eq_some_iff {s} pos := by
|
||||
simp [isLongestMatch_iff, ForwardPattern.skipPrefix?, and_comm, eq_comm (b := pos)]
|
||||
|
||||
instance {p : Char → Bool} : LawfulBackwardPatternModel p where
|
||||
skipSuffix?_eq_some_iff {s} pos := by
|
||||
simp [isLongestRevMatch_iff, BackwardPattern.skipSuffix?, and_comm, eq_comm (b := pos)]
|
||||
|
||||
instance {p : Char → Bool} : LawfulToForwardSearcherModel p :=
|
||||
.defaultImplementation
|
||||
|
||||
instance {p : Char → Bool} : LawfulToBackwardSearcherModel p :=
|
||||
.defaultImplementation
|
||||
|
||||
theorem matchesAt_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
MatchesAt p pos ↔ ∃ (h : pos ≠ s.endPos), p (pos.get h) := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm]
|
||||
|
||||
theorem revMatchesAt_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
RevMatchesAt p pos ↔ ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm]
|
||||
|
||||
theorem not_matchesAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.endPos}
|
||||
(hc : p (pos.get h) = false) : ¬ MatchesAt p pos := by
|
||||
simp [matchesAt_iff, hc]
|
||||
|
||||
theorem not_revMatchesAt_of_get {p : Char → Bool} {s : Slice} {pos : s.Pos} {h : pos ≠ s.startPos}
|
||||
(hc : p ((pos.prev h).get (by simp)) = false) : ¬ RevMatchesAt p pos := by
|
||||
simp [revMatchesAt_iff, hc]
|
||||
|
||||
theorem matchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Bool} :
|
||||
matchAt? p pos =
|
||||
if h₀ : ∃ (h : pos ≠ s.endPos), p (pos.get h) then some (pos.next h₀.1) else none := by
|
||||
split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff]
|
||||
|
||||
theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Bool} :
|
||||
revMatchAt? p pos =
|
||||
if h₀ : ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) then some (pos.prev h₀.1) else none := by
|
||||
split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff]
|
||||
|
||||
namespace Decidable
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : ForwardPatternModel p where
|
||||
Matches := ForwardPatternModel.Matches (decide <| p ·)
|
||||
not_matches_empty := ForwardPatternModel.not_matches_empty (pat := (decide <| p ·))
|
||||
instance {p : Char → Prop} [DecidablePred p] : PatternModel p where
|
||||
Matches := PatternModel.Matches (decide <| p ·)
|
||||
not_matches_empty := PatternModel.not_matches_empty (pat := (decide <| p ·))
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : NoPrefixForwardPatternModel p where
|
||||
eq_empty := NoPrefixForwardPatternModel.eq_empty (pat := (decide <| p ·))
|
||||
instance {p : Char → Prop} [DecidablePred p] : NoPrefixPatternModel p where
|
||||
eq_empty := NoPrefixPatternModel.eq_empty (pat := (decide <| p ·))
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : NoSuffixPatternModel p where
|
||||
eq_empty := NoSuffixPatternModel.eq_empty (pat := (decide <| p ·))
|
||||
|
||||
theorem isMatch_iff_isMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch p pos ↔ IsMatch (decide <| p ·) pos :=
|
||||
⟨fun ⟨h⟩ => ⟨h⟩, fun ⟨h⟩ => ⟨h⟩⟩
|
||||
|
||||
theorem isRevMatch_iff_isRevMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch p pos ↔ IsRevMatch (decide <| p ·) pos :=
|
||||
⟨fun ⟨h⟩ => ⟨h⟩, fun ⟨h⟩ => ⟨h⟩⟩
|
||||
|
||||
theorem isMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
IsMatch p pos ↔
|
||||
∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by
|
||||
simp [isMatch_iff_isMatch_decide, CharPred.isMatch_iff]
|
||||
|
||||
theorem isRevMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch p pos ↔
|
||||
∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [isRevMatch_iff_isRevMatch_decide, CharPred.isRevMatch_iff]
|
||||
|
||||
theorem isLongestMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
IsLongestMatch p pos ↔
|
||||
∃ (h : s.startPos ≠ s.endPos), pos = s.startPos.next h ∧ p (s.startPos.get h) := by
|
||||
rw [isLongestMatch_iff_isMatch, isMatch_iff]
|
||||
|
||||
theorem isLongestRevMatch_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
IsLongestRevMatch p pos ↔
|
||||
∃ (h : s.endPos ≠ s.startPos), pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff]
|
||||
|
||||
theorem isLongestMatch_iff_isLongestMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos : s.Pos} : IsLongestMatch p pos ↔ IsLongestMatch (decide <| p ·) pos := by
|
||||
simp [isLongestMatch_iff_isMatch, isMatch_iff_isMatch_decide]
|
||||
|
||||
theorem isLongestRevMatch_iff_isLongestRevMatch_decide {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos : s.Pos} : IsLongestRevMatch p pos ↔ IsLongestRevMatch (decide <| p ·) pos := by
|
||||
simp [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff_isRevMatch_decide]
|
||||
|
||||
theorem isLongestMatchAt_iff_isLongestMatchAt_decide {p : Char → Prop} [DecidablePred p]
|
||||
{s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestMatchAt p pos pos' ↔ IsLongestMatchAt (decide <| p ·) pos pos' := by
|
||||
simp [Model.isLongestMatchAt_iff, isLongestMatch_iff_isLongestMatch_decide]
|
||||
|
||||
theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_decide {p : Char → Prop} [DecidablePred p]
|
||||
{s : Slice} {pos pos' : s.Pos} :
|
||||
IsLongestRevMatchAt p pos pos' ↔ IsLongestRevMatchAt (decide <| p ·) pos pos' := by
|
||||
simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff_isLongestRevMatch_decide]
|
||||
|
||||
theorem isLongestMatchAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos pos' : s.Pos} :
|
||||
IsLongestMatchAt p pos pos' ↔ ∃ h, pos' = pos.next h ∧ p (pos.get h) := by
|
||||
simp [isLongestMatchAt_iff_isLongestMatchAt_decide, CharPred.isLongestMatchAt_iff]
|
||||
|
||||
theorem isLongestRevMatchAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos pos' : s.Pos} :
|
||||
IsLongestRevMatchAt p pos pos' ↔ ∃ h, pos = pos'.prev h ∧ p ((pos'.prev h).get (by simp)) := by
|
||||
simp [isLongestRevMatchAt_iff_isLongestRevMatchAt_decide, CharPred.isLongestRevMatchAt_iff]
|
||||
|
||||
theorem isLongestMatchAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos}
|
||||
{h : pos ≠ s.endPos} (hc : p (pos.get h)) : IsLongestMatchAt p pos (pos.next h) :=
|
||||
isLongestMatchAt_iff.2 ⟨h, by simp [hc]⟩
|
||||
|
||||
theorem isLongestRevMatchAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos}
|
||||
{h : pos ≠ s.startPos} (hc : p ((pos.prev h).get (by simp))) :
|
||||
IsLongestRevMatchAt p (pos.prev h) pos :=
|
||||
isLongestRevMatchAt_iff.2 ⟨h, by simp [hc]⟩
|
||||
|
||||
theorem matchesAt_iff_matchesAt_decide {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos : s.Pos} : MatchesAt p pos ↔ MatchesAt (decide <| p ·) pos := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_decide]
|
||||
|
||||
theorem revMatchesAt_iff_revMatchesAt_decide {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos : s.Pos} : RevMatchesAt p pos ↔ RevMatchesAt (decide <| p ·) pos := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide]
|
||||
|
||||
theorem matchAt?_eq_matchAt?_decide {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos : s.Pos} : matchAt? p pos = matchAt? (decide <| p ·) pos := by
|
||||
ext endPos
|
||||
simp [isLongestMatchAt_iff_isLongestMatchAt_decide]
|
||||
|
||||
theorem revMatchAt?_eq_revMatchAt?_decide {p : Char → Prop} [DecidablePred p] {s : Slice}
|
||||
{pos : s.Pos} : revMatchAt? p pos = revMatchAt? (decide <| p ·) pos := by
|
||||
ext startPos
|
||||
simp [isLongestRevMatchAt_iff_isLongestRevMatchAt_decide]
|
||||
|
||||
theorem skipPrefix?_eq_skipPrefix?_decide {p : Char → Prop} [DecidablePred p] :
|
||||
ForwardPattern.skipPrefix? p = ForwardPattern.skipPrefix? (decide <| p ·) := rfl
|
||||
|
||||
theorem skipSuffix?_eq_skipSuffix?_decide {p : Char → Prop} [DecidablePred p] :
|
||||
BackwardPattern.skipSuffix? p = BackwardPattern.skipSuffix? (decide <| p ·) := rfl
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : LawfulForwardPatternModel p where
|
||||
skipPrefix?_eq_some_iff {s} pos := by
|
||||
rw [skipPrefix?_eq_skipPrefix?_decide, isLongestMatch_iff_isLongestMatch_decide]
|
||||
exact LawfulForwardPatternModel.skipPrefix?_eq_some_iff ..
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : LawfulBackwardPatternModel p where
|
||||
skipSuffix?_eq_some_iff {s} pos := by
|
||||
rw [skipSuffix?_eq_skipSuffix?_decide, isLongestRevMatch_iff_isLongestRevMatch_decide]
|
||||
exact LawfulBackwardPatternModel.skipSuffix?_eq_some_iff ..
|
||||
|
||||
theorem toSearcher_eq {p : Char → Prop} [DecidablePred p] {s : Slice} :
|
||||
ToForwardSearcher.toSearcher p s = ToForwardSearcher.toSearcher (decide <| p ·) s := (rfl)
|
||||
|
||||
theorem toBackwardSearcher_eq {p : Char → Prop} [DecidablePred p] {s : Slice} :
|
||||
ToBackwardSearcher.toSearcher p s = ToBackwardSearcher.toSearcher (decide <| p ·) s := (rfl)
|
||||
|
||||
theorem isValidSearchFrom_iff_isValidSearchFrom_decide {p : Char → Prop} [DecidablePred p]
|
||||
{s : Slice} {pos : s.Pos} {l : List (SearchStep s)} :
|
||||
IsValidSearchFrom p pos l ↔ IsValidSearchFrom (decide <| p ·) pos l := by
|
||||
@@ -150,24 +254,55 @@ theorem isValidSearchFrom_iff_isValidSearchFrom_decide {p : Char → Prop} [Deci
|
||||
| matched => simp_all [IsValidSearchFrom.matched, isLongestMatchAt_iff_isLongestMatchAt_decide]
|
||||
| mismatched => simp_all [IsValidSearchFrom.mismatched, matchesAt_iff_matchesAt_decide]
|
||||
|
||||
theorem isValidRevSearchFrom_iff_isValidRevSearchFrom_decide {p : Char → Prop} [DecidablePred p]
|
||||
{s : Slice} {pos : s.Pos} {l : List (SearchStep s)} :
|
||||
IsValidRevSearchFrom p pos l ↔ IsValidRevSearchFrom (decide <| p ·) pos l := by
|
||||
refine ⟨fun h => ?_, fun h => ?_⟩
|
||||
· induction h with
|
||||
| startPos => simpa using IsValidRevSearchFrom.startPos
|
||||
| matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide]
|
||||
| mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_decide]
|
||||
· induction h with
|
||||
| startPos => simpa using IsValidRevSearchFrom.startPos
|
||||
| matched => simp_all [IsValidRevSearchFrom.matched, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide]
|
||||
| mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_decide]
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : LawfulToForwardSearcherModel p where
|
||||
isValidSearchFrom_toList s := by
|
||||
simpa [toSearcher_eq, isValidSearchFrom_iff_isValidSearchFrom_decide] using
|
||||
LawfulToForwardSearcherModel.isValidSearchFrom_toList (pat := (decide <| p ·)) (s := s)
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : LawfulToBackwardSearcherModel p where
|
||||
isValidRevSearchFrom_toList s := by
|
||||
simpa [toBackwardSearcher_eq, isValidRevSearchFrom_iff_isValidRevSearchFrom_decide] using
|
||||
LawfulToBackwardSearcherModel.isValidRevSearchFrom_toList (pat := (decide <| p ·)) (s := s)
|
||||
|
||||
theorem matchesAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
MatchesAt p pos ↔ ∃ (h : pos ≠ s.endPos), p (pos.get h) := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm]
|
||||
|
||||
theorem revMatchesAt_iff {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
|
||||
RevMatchesAt p pos ↔ ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm]
|
||||
|
||||
theorem not_matchesAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos}
|
||||
{h : pos ≠ s.endPos} (hc : ¬ p (pos.get h)) : ¬ MatchesAt p pos := by
|
||||
simp [matchesAt_iff, hc]
|
||||
|
||||
theorem not_revMatchesAt_of_get {p : Char → Prop} [DecidablePred p] {s : Slice} {pos : s.Pos}
|
||||
{h : pos ≠ s.startPos} (hc : ¬ p ((pos.prev h).get (by simp))) : ¬ RevMatchesAt p pos := by
|
||||
simp [revMatchesAt_iff, hc]
|
||||
|
||||
theorem matchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Prop} [DecidablePred p] :
|
||||
matchAt? p pos =
|
||||
if h₀ : ∃ (h : pos ≠ s.endPos), p (pos.get h) then some (pos.next h₀.1) else none := by
|
||||
split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff]
|
||||
|
||||
theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {p : Char → Prop} [DecidablePred p] :
|
||||
revMatchAt? p pos =
|
||||
if h₀ : ∃ (h : pos ≠ s.startPos), p ((pos.prev h).get (by simp)) then some (pos.prev h₀.1) else none := by
|
||||
split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff]
|
||||
|
||||
end Decidable
|
||||
|
||||
end Pattern.Model.CharPred
|
||||
|
||||
@@ -28,7 +28,7 @@ set_option doc.verso true
|
||||
# Verification of {name}`String.Slice.splitToSubslice`
|
||||
|
||||
This PR verifies the {name}`String.Slice.splitToSubslice` function by relating it to a model
|
||||
implementation based on the {name}`String.Slice.Pattern.Model.ForwardPatternModel` class.
|
||||
implementation based on the {name}`String.Slice.Pattern.Model.PatternModel` class.
|
||||
|
||||
This gives a low-level correctness proof from which higher-level API lemmas can be derived.
|
||||
-/
|
||||
@@ -36,7 +36,7 @@ This gives a low-level correctness proof from which higher-level API lemmas can
|
||||
namespace String.Slice.Pattern.Model
|
||||
|
||||
@[cbv_opaque]
|
||||
public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {s : Slice}
|
||||
public protected noncomputable def split {ρ : Type} (pat : ρ) [PatternModel pat] {s : Slice}
|
||||
(firstRejected curr : s.Pos) (hle : firstRejected ≤ curr) : List s.Subslice :=
|
||||
if h : curr = s.endPos then
|
||||
[s.subslice _ _ hle]
|
||||
@@ -49,12 +49,12 @@ public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternM
|
||||
termination_by curr
|
||||
|
||||
@[simp]
|
||||
public theorem split_endPos {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {s : Slice}
|
||||
public theorem split_endPos {ρ : Type} {pat : ρ} [PatternModel pat] {s : Slice}
|
||||
{firstRejected : s.Pos} :
|
||||
Model.split (s := s) pat firstRejected s.endPos (by simp) = [s.subslice firstRejected s.endPos (by simp)] := by
|
||||
simp [Model.split]
|
||||
|
||||
public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {firstRejected start stop : s.Pos} {hle} (h : IsLongestMatchAt pat start stop) :
|
||||
Model.split pat firstRejected start hle =
|
||||
s.subslice _ _ hle :: Model.split pat stop stop (by exact Std.le_refl _) := by
|
||||
@@ -63,7 +63,7 @@ public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [ForwardPatte
|
||||
· congr <;> exact (matchAt?_eq_some_iff.1 ‹_›).eq h
|
||||
· simp [matchAt?_eq_some_iff.2 ‹_›] at *
|
||||
|
||||
public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {firstRejected start} (stop : s.Pos) (h₀ : start ≤ stop) {hle}
|
||||
(h : ∀ p, start ≤ p → p < stop → ¬ MatchesAt pat p) :
|
||||
Model.split pat firstRejected start hle =
|
||||
@@ -80,7 +80,7 @@ public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternM
|
||||
· obtain rfl : start = stop := Std.le_antisymm h₀ (Std.not_lt.1 h')
|
||||
simp
|
||||
|
||||
public theorem split_eq_next_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
|
||||
public theorem split_eq_next_of_not_matchesAt {ρ : Type} {pat : ρ} [PatternModel pat]
|
||||
{s : Slice} {firstRejected start} {hle} (hs : start ≠ s.endPos) (h : ¬ MatchesAt pat start) :
|
||||
Model.split pat firstRejected start hle =
|
||||
Model.split pat firstRejected (start.next hs) (by exact Std.le_trans hle (by simp)) := by
|
||||
@@ -103,7 +103,7 @@ def splitFromSteps {s : Slice} (currPos : s.Pos) (l : List (SearchStep s)) : Lis
|
||||
| .matched p q :: l => s.subslice! currPos p :: splitFromSteps q l
|
||||
|
||||
theorem IsValidSearchFrom.splitFromSteps_eq_extend_split {ρ : Type} (pat : ρ)
|
||||
[ForwardPatternModel pat] (l : List (SearchStep s)) (pos pos' : s.Pos) (h₀ : pos ≤ pos')
|
||||
[PatternModel pat] (l : List (SearchStep s)) (pos pos' : s.Pos) (h₀ : pos ≤ pos')
|
||||
(h' : ∀ p, pos ≤ p → p < pos' → ¬ MatchesAt pat p)
|
||||
(h : IsValidSearchFrom pat pos' l) :
|
||||
splitFromSteps pos l = Model.split pat pos pos' h₀ := by
|
||||
@@ -155,7 +155,7 @@ end Model
|
||||
open Model
|
||||
|
||||
@[cbv_eval]
|
||||
public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [ForwardPatternModel pat]
|
||||
public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [PatternModel pat]
|
||||
{σ : Slice → Type} [ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)]
|
||||
[∀ s, Std.Iterators.Finite (σ s) Id] [LawfulToForwardSearcherModel pat] (s : Slice) :
|
||||
(s.splitToSubslice pat).toList = Model.split pat s.startPos s.startPos (by exact Std.le_refl _) := by
|
||||
@@ -168,7 +168,7 @@ end Pattern
|
||||
open Pattern
|
||||
|
||||
public theorem toList_splitToSubslice_of_isEmpty {ρ : Type} (pat : ρ)
|
||||
[Model.ForwardPatternModel pat] {σ : Slice → Type}
|
||||
[Model.PatternModel pat] {σ : Slice → Type}
|
||||
[ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)]
|
||||
[∀ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] {s : Slice}
|
||||
(h : s.isEmpty = true) :
|
||||
@@ -182,7 +182,7 @@ public theorem toList_split_eq_splitToSubslice {ρ : Type} (pat : ρ) {σ : Slic
|
||||
simp [split, Std.Iter.toList_map]
|
||||
|
||||
public theorem toList_split_of_isEmpty {ρ : Type} (pat : ρ)
|
||||
[Model.ForwardPatternModel pat] {σ : Slice → Type}
|
||||
[Model.PatternModel pat] {σ : Slice → Type}
|
||||
[ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)]
|
||||
[∀ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] {s : Slice}
|
||||
(h : s.isEmpty = true) :
|
||||
@@ -200,7 +200,7 @@ public theorem split_eq_split_toSlice {ρ : Type} {pat : ρ} {σ : Slice → Typ
|
||||
|
||||
@[simp]
|
||||
public theorem toList_split_empty {ρ : Type} (pat : ρ)
|
||||
[Model.ForwardPatternModel pat] {σ : Slice → Type}
|
||||
[Model.PatternModel pat] {σ : Slice → Type}
|
||||
[ToForwardSearcher pat σ] [∀ s, Std.Iterator (σ s) Id (SearchStep s)]
|
||||
[∀ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] :
|
||||
("".split pat).toList.map Slice.copy = [""] := by
|
||||
|
||||
@@ -23,6 +23,7 @@ import Init.Data.String.OrderInstances
|
||||
import Init.Data.String.Lemmas.Order
|
||||
import Init.Data.String.Lemmas.Intercalate
|
||||
import Init.Data.List.SplitOn.Lemmas
|
||||
import Init.Data.String.Lemmas.Slice
|
||||
|
||||
public section
|
||||
|
||||
@@ -70,6 +71,11 @@ theorem Slice.toList_split_intercalate {c : Char} {l : List Slice} (hl : ∀ s
|
||||
· simp_all
|
||||
· rw [List.splitOn_intercalate] <;> simp_all
|
||||
|
||||
theorem Slice.toList_split_intercalate_beq {c : Char} {l : List Slice} (hl : ∀ s ∈ l, c ∉ s.copy.toList) :
|
||||
((Slice.intercalate (String.singleton c) l).split c).toList ==
|
||||
if l = [] then ["".toSlice] else l := by
|
||||
split <;> simp_all [toList_split_intercalate hl, beq_list_iff]
|
||||
|
||||
theorem toList_split_intercalate {c : Char} {l : List String} (hl : ∀ s ∈ l, c ∉ s.toList) :
|
||||
((String.intercalate (String.singleton c) l).split c).toList.map (·.copy) =
|
||||
if l = [] then [""] else l := by
|
||||
@@ -78,4 +84,9 @@ theorem toList_split_intercalate {c : Char} {l : List String} (hl : ∀ s ∈ l,
|
||||
· simp_all
|
||||
· rw [List.splitOn_intercalate] <;> simp_all
|
||||
|
||||
theorem toList_split_intercalate_beq {c : Char} {l : List String} (hl : ∀ s ∈ l, c ∉ s.toList) :
|
||||
((String.intercalate (String.singleton c) l).split c).toList ==
|
||||
if l = [] then ["".toSlice] else l.map String.toSlice := by
|
||||
split <;> simp_all [toList_split_intercalate hl, Slice.beq_list_iff]
|
||||
|
||||
end String
|
||||
|
||||
@@ -19,12 +19,12 @@ namespace String.Slice.Pattern.Model
|
||||
|
||||
namespace ForwardSliceSearcher
|
||||
|
||||
instance {pat : Slice} : ForwardPatternModel pat where
|
||||
instance {pat : Slice} : PatternModel pat where
|
||||
/-
|
||||
See the docstring of `ForwardPatternModel` for an explanation about why we disallow matching the
|
||||
See the docstring of `PatternModel` for an explanation about why we disallow matching the
|
||||
empty string.
|
||||
|
||||
Requiring `s ≠ ""` is a trick that allows us to give a `ForwardPatternModel` instance
|
||||
Requiring `s ≠ ""` is a trick that allows us to give a `PatternModel` instance
|
||||
unconditionally, without forcing `pat.copy` to be non-empty (which would make it very awkward
|
||||
to state theorems about the instance). It does not change anything about the fact that all lemmas
|
||||
about this instance require `pat.isEmpty = false`.
|
||||
@@ -32,34 +32,60 @@ instance {pat : Slice} : ForwardPatternModel pat where
|
||||
Matches s := s ≠ "" ∧ s = pat.copy
|
||||
not_matches_empty := by simp
|
||||
|
||||
instance {pat : Slice} : NoPrefixForwardPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [ForwardPatternModel.Matches])
|
||||
instance {pat : Slice} : NoPrefixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {pat : Slice} : NoSuffixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsMatch pat pos ↔ (s.sliceTo pos).copy = pat.copy := by
|
||||
simp only [Model.isMatch_iff, ForwardPatternModel.Matches, ne_eq, copy_eq_empty_iff,
|
||||
simp only [Model.isMatch_iff, PatternModel.Matches, ne_eq, copy_eq_empty_iff,
|
||||
Bool.not_eq_true, and_iff_right_iff_imp]
|
||||
intro h'
|
||||
rw [← isEmpty_copy (s := s.sliceTo pos), h', isEmpty_copy, h]
|
||||
|
||||
theorem isRevMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat.copy := by
|
||||
simp only [Model.isRevMatch_iff, PatternModel.Matches, ne_eq, copy_eq_empty_iff,
|
||||
Bool.not_eq_true, and_iff_right_iff_imp]
|
||||
intro h'
|
||||
rw [← isEmpty_copy (s := s.sliceFrom pos), h', isEmpty_copy, h]
|
||||
|
||||
theorem isLongestMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestMatch pat pos ↔ (s.sliceTo pos).copy = pat.copy := by
|
||||
rw [isLongestMatch_iff_isMatch, isMatch_iff h]
|
||||
|
||||
theorem isLongestRevMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat.copy := by
|
||||
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff h]
|
||||
|
||||
theorem isLongestMatchAt_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat.copy := by
|
||||
simp [Model.isLongestMatchAt_iff, isLongestMatch_iff h]
|
||||
|
||||
theorem isLongestRevMatchAt_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat.copy := by
|
||||
simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff h]
|
||||
|
||||
theorem isLongestMatchAt_iff_splits {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ t₁ t₂, pos₁.Splits t₁ (pat.copy ++ t₂) ∧
|
||||
pos₂.Splits (t₁ ++ pat.copy) t₂ := by
|
||||
simp only [isLongestMatchAt_iff h, copy_slice_eq_iff_splits]
|
||||
|
||||
theorem isLongestRevMatchAt_iff_splits {pat s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat.isEmpty = false) :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔ ∃ t₁ t₂, pos₁.Splits t₁ (pat.copy ++ t₂) ∧
|
||||
pos₂.Splits (t₁ ++ pat.copy) t₂ := by
|
||||
simp only [isLongestRevMatchAt_iff h, copy_slice_eq_iff_splits]
|
||||
|
||||
theorem isLongestMatch_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestMatch pat pos ↔ ∃ t, pos.Splits pat.copy t := by
|
||||
simp only [← isLongestMatchAt_startPos_iff, isLongestMatchAt_iff_splits h, splits_startPos_iff,
|
||||
and_assoc, exists_and_left, exists_eq_left, empty_append]
|
||||
exact ⟨fun ⟨h, _, h'⟩ => ⟨h, h'⟩, fun ⟨h, h'⟩ => ⟨h, h'.eq_append.symm, h'⟩⟩
|
||||
rw [isLongestMatch_iff h, copy_sliceTo_eq_iff_exists_splits]
|
||||
|
||||
theorem isLongestRevMatch_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestRevMatch pat pos ↔ ∃ t, pos.Splits t pat.copy := by
|
||||
rw [isLongestRevMatch_iff h, copy_sliceFrom_eq_iff_exists_splits]
|
||||
|
||||
theorem isLongestMatchAt_iff_extract {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔
|
||||
@@ -71,6 +97,18 @@ theorem isLongestMatchAt_iff_extract {pat s : Slice} {pos₁ pos₂ : s.Pos} (h
|
||||
exact ⟨by simp [Pos.le_iff, Pos.Raw.le_iff]; omega,
|
||||
by simp [← h', ← toByteArray_inj, toByteArray_copy_slice]⟩
|
||||
|
||||
theorem isLongestRevMatchAt_iff_extract {pat s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat.isEmpty = false) :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔
|
||||
s.copy.toByteArray.extract pos₁.offset.byteIdx pos₂.offset.byteIdx =
|
||||
pat.copy.toByteArray := by
|
||||
rw [isLongestRevMatchAt_iff h]
|
||||
refine ⟨fun ⟨h, h'⟩ => ?_, fun h' => ?_⟩
|
||||
· simp [← h', toByteArray_copy_slice]
|
||||
· rw [← Slice.toByteArray_copy_ne_empty_iff, ← h', ne_eq, ByteArray.extract_eq_empty_iff] at h
|
||||
exact ⟨by simp [Pos.le_iff, Pos.Raw.le_iff]; omega,
|
||||
by simp [← h', ← toByteArray_inj, toByteArray_copy_slice]⟩
|
||||
|
||||
theorem offset_of_isLongestMatchAt {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false)
|
||||
(h' : IsLongestMatchAt pat pos₁ pos₂) : pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by
|
||||
simp only [Pos.Raw.ext_iff, Pos.Raw.byteIdx_increaseBy]
|
||||
@@ -81,12 +119,29 @@ theorem offset_of_isLongestMatchAt {pat s : Slice} {pos₁ pos₂ : s.Pos} (h :
|
||||
suffices pos₂.offset.byteIdx ≤ s.utf8ByteSize by omega
|
||||
simpa [Pos.le_iff, Pos.Raw.le_iff] using pos₂.le_endPos
|
||||
|
||||
theorem offset_of_isLongestRevMatchAt {pat s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat.isEmpty = false) (h' : IsLongestRevMatchAt pat pos₁ pos₂) :
|
||||
pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by
|
||||
simp only [Pos.Raw.ext_iff, Pos.Raw.byteIdx_increaseBy]
|
||||
rw [isLongestRevMatchAt_iff_extract h] at h'
|
||||
rw [← Slice.toByteArray_copy_ne_empty_iff, ← h', ne_eq, ByteArray.extract_eq_empty_iff] at h
|
||||
replace h' := congrArg ByteArray.size h'
|
||||
simp only [ByteArray.size_extract, size_toByteArray, utf8ByteSize_copy] at h'
|
||||
suffices pos₂.offset.byteIdx ≤ s.utf8ByteSize by omega
|
||||
simpa [Pos.le_iff, Pos.Raw.le_iff] using pos₂.le_endPos
|
||||
|
||||
theorem matchesAt_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
MatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ (pat.copy ++ t₂) := by
|
||||
simp only [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_splits h]
|
||||
exact ⟨fun ⟨e, t₁, t₂, ht₁, ht₂⟩ => ⟨t₁, t₂, ht₁⟩,
|
||||
fun ⟨t₁, t₂, ht⟩ => ⟨ht.rotateRight, t₁, t₂, ht, ht.splits_rotateRight⟩⟩
|
||||
|
||||
theorem revMatchesAt_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
RevMatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits (t₁ ++ pat.copy) t₂ := by
|
||||
simp only [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_splits h]
|
||||
exact ⟨fun ⟨e, t₁, t₂, ht₁, ht₂⟩ => ⟨t₁, t₂, ht₂⟩,
|
||||
fun ⟨t₁, t₂, ht⟩ => ⟨ht.rotateLeft, t₁, t₂, ht.splits_rotateLeft, ht⟩⟩
|
||||
|
||||
theorem exists_matchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false) :
|
||||
(∃ (pos : s.Pos), MatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat.copy ++ t₂ := by
|
||||
simp only [matchesAt_iff_splits h]
|
||||
@@ -99,6 +154,18 @@ theorem exists_matchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false)
|
||||
⟨t₁, pat.copy ++ t₂, by rw [← append_assoc]; exact heq, rfl⟩
|
||||
exact ⟨s.pos _ hvalid, t₁, t₂, ⟨by rw [← append_assoc]; exact heq, by simp⟩⟩
|
||||
|
||||
theorem exists_revMatchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false) :
|
||||
(∃ (pos : s.Pos), RevMatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat.copy ++ t₂ := by
|
||||
simp only [revMatchesAt_iff_splits h]
|
||||
constructor
|
||||
· rintro ⟨pos, t₁, t₂, hsplit⟩
|
||||
exact ⟨t₁, t₂, by rw [hsplit.eq_append, append_assoc]⟩
|
||||
· rintro ⟨t₁, t₂, heq⟩
|
||||
have hvalid : (t₁ ++ pat.copy).rawEndPos.IsValidForSlice s :=
|
||||
Pos.Raw.isValidForSlice_iff_exists_append.mpr
|
||||
⟨t₁ ++ pat.copy, t₂, heq, rfl⟩
|
||||
exact ⟨s.pos _ hvalid, t₁, t₂, ⟨heq, by simp⟩⟩
|
||||
|
||||
theorem matchesAt_iff_isLongestMatchAt {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
MatchesAt pat pos ↔ ∃ (h : (pos.offset.increaseBy pat.utf8ByteSize).IsValidForSlice s),
|
||||
IsLongestMatchAt pat pos (s.pos _ h) := by
|
||||
@@ -108,6 +175,25 @@ theorem matchesAt_iff_isLongestMatchAt {pat s : Slice} {pos : s.Pos} (h : pat.is
|
||||
obtain rfl : p = s.pos _ this := by simpa [Pos.ext_iff] using offset_of_isLongestMatchAt h h'
|
||||
exact h'
|
||||
|
||||
theorem revMatchesAt_iff_isLongestRevMatchAt {pat s : Slice} {pos : s.Pos}
|
||||
(h : pat.isEmpty = false) :
|
||||
RevMatchesAt pat pos ↔
|
||||
∃ (h : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s),
|
||||
IsLongestRevMatchAt pat (s.pos _ h) pos := by
|
||||
refine ⟨fun ⟨⟨p, h'⟩⟩ => ?_, fun ⟨_, h⟩ => ⟨⟨_, h⟩⟩⟩
|
||||
have hoff := offset_of_isLongestRevMatchAt h h'
|
||||
have hvalid : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s := by
|
||||
rw [show pos.offset.decreaseBy pat.utf8ByteSize = p.offset from by
|
||||
simp [Pos.Raw.ext_iff, Pos.Raw.byteIdx_decreaseBy, Pos.Raw.byteIdx_increaseBy] at hoff ⊢
|
||||
omega]
|
||||
exact p.isValidForSlice
|
||||
refine ⟨hvalid, ?_⟩
|
||||
obtain rfl : p = s.pos _ hvalid := by
|
||||
simp only [Pos.ext_iff, offset_pos]
|
||||
simp [Pos.Raw.ext_iff, Pos.Raw.byteIdx_decreaseBy, Pos.Raw.byteIdx_increaseBy] at hoff ⊢
|
||||
omega
|
||||
exact h'
|
||||
|
||||
theorem matchesAt_iff_getElem {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
|
||||
MatchesAt pat pos ↔
|
||||
∃ (h : pos.offset.byteIdx + pat.copy.toByteArray.size ≤ s.copy.toByteArray.size),
|
||||
@@ -146,31 +232,56 @@ end ForwardSliceSearcher
|
||||
|
||||
namespace ForwardStringSearcher
|
||||
|
||||
instance {pat : String} : ForwardPatternModel pat where
|
||||
instance {pat : String} : PatternModel pat where
|
||||
Matches s := s ≠ "" ∧ s = pat
|
||||
not_matches_empty := by simp
|
||||
|
||||
instance {pat : String} : NoPrefixForwardPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [ForwardPatternModel.Matches])
|
||||
instance {pat : String} : NoPrefixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
instance {pat : String} : NoSuffixPatternModel pat :=
|
||||
.of_length_eq (by simp +contextual [PatternModel.Matches])
|
||||
|
||||
theorem isMatch_iff_slice {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
IsMatch (ρ := String) pat pos ↔ IsMatch (ρ := Slice) pat.toSlice pos := by
|
||||
simp only [Model.isMatch_iff, ForwardPatternModel.Matches, copy_toSlice]
|
||||
simp only [Model.isMatch_iff, PatternModel.Matches, copy_toSlice]
|
||||
|
||||
theorem isRevMatch_iff_slice {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
IsRevMatch (ρ := String) pat pos ↔ IsRevMatch (ρ := Slice) pat.toSlice pos := by
|
||||
simp only [Model.isRevMatch_iff, PatternModel.Matches, copy_toSlice]
|
||||
|
||||
theorem isLongestMatch_iff_isLongestMatch_toSlice {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestMatch (ρ := String) pat pos ↔ IsLongestMatch (ρ := Slice) pat.toSlice pos where
|
||||
mp h := ⟨isMatch_iff_slice.1 h.isMatch, fun p hp hm => h.not_isMatch p hp (isMatch_iff_slice.2 hm)⟩
|
||||
mpr h := ⟨isMatch_iff_slice.2 h.isMatch, fun p hp hm => h.not_isMatch p hp (isMatch_iff_slice.1 hm)⟩
|
||||
|
||||
theorem isLongestRevMatch_iff_isLongestRevMatch_toSlice {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
IsLongestRevMatch (ρ := String) pat pos ↔ IsLongestRevMatch (ρ := Slice) pat.toSlice pos where
|
||||
mp h := ⟨isRevMatch_iff_slice.1 h.isRevMatch,
|
||||
fun p hp hm => h.not_isRevMatch p hp (isRevMatch_iff_slice.2 hm)⟩
|
||||
mpr h := ⟨isRevMatch_iff_slice.2 h.isRevMatch,
|
||||
fun p hp hm => h.not_isRevMatch p hp (isRevMatch_iff_slice.1 hm)⟩
|
||||
|
||||
theorem isLongestMatchAt_iff_isLongestMatchAt_toSlice {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
|
||||
IsLongestMatchAt (ρ := String) pat pos₁ pos₂ ↔
|
||||
IsLongestMatchAt (ρ := Slice) pat.toSlice pos₁ pos₂ := by
|
||||
simp [Model.isLongestMatchAt_iff, isLongestMatch_iff_isLongestMatch_toSlice]
|
||||
|
||||
theorem isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice {pat : String} {s : Slice}
|
||||
{pos₁ pos₂ : s.Pos} :
|
||||
IsLongestRevMatchAt (ρ := String) pat pos₁ pos₂ ↔
|
||||
IsLongestRevMatchAt (ρ := Slice) pat.toSlice pos₁ pos₂ := by
|
||||
simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff_isLongestRevMatch_toSlice]
|
||||
|
||||
theorem matchesAt_iff_toSlice {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
MatchesAt (ρ := String) pat pos ↔ MatchesAt (ρ := Slice) pat.toSlice pos := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_toSlice]
|
||||
|
||||
theorem revMatchesAt_iff_toSlice {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
RevMatchesAt (ρ := String) pat pos ↔ RevMatchesAt (ρ := Slice) pat.toSlice pos := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt,
|
||||
isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice]
|
||||
|
||||
private theorem toSlice_isEmpty (h : pat ≠ "") : pat.toSlice.isEmpty = false := by
|
||||
rwa [isEmpty_toSlice, isEmpty_eq_false_iff]
|
||||
|
||||
@@ -179,16 +290,31 @@ theorem isMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
rw [isMatch_iff_slice, ForwardSliceSearcher.isMatch_iff (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isRevMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
IsRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat := by
|
||||
rw [isRevMatch_iff_slice, ForwardSliceSearcher.isRevMatch_iff (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isLongestMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
IsLongestMatch pat pos ↔ (s.sliceTo pos).copy = pat := by
|
||||
rw [isLongestMatch_iff_isMatch, isMatch_iff h]
|
||||
|
||||
theorem isLongestRevMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
IsLongestRevMatch pat pos ↔ (s.sliceFrom pos).copy = pat := by
|
||||
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff h]
|
||||
|
||||
theorem isLongestMatchAt_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat := by
|
||||
rw [isLongestMatchAt_iff_isLongestMatchAt_toSlice,
|
||||
ForwardSliceSearcher.isLongestMatchAt_iff (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isLongestRevMatchAt_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat ≠ "") :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔ ∃ h, (s.slice pos₁ pos₂ h).copy = pat := by
|
||||
rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice,
|
||||
ForwardSliceSearcher.isLongestRevMatchAt_iff (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isLongestMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat ≠ "") :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔
|
||||
@@ -197,6 +323,14 @@ theorem isLongestMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ :
|
||||
ForwardSliceSearcher.isLongestMatchAt_iff_splits (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isLongestRevMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat ≠ "") :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔
|
||||
∃ t₁ t₂, pos₁.Splits t₁ (pat ++ t₂) ∧ pos₂.Splits (t₁ ++ pat) t₂ := by
|
||||
rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice,
|
||||
ForwardSliceSearcher.isLongestRevMatchAt_iff_splits (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isLongestMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat ≠ "") :
|
||||
IsLongestMatchAt pat pos₁ pos₂ ↔
|
||||
@@ -205,6 +339,14 @@ theorem isLongestMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ :
|
||||
ForwardSliceSearcher.isLongestMatchAt_iff_extract (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem isLongestRevMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat ≠ "") :
|
||||
IsLongestRevMatchAt pat pos₁ pos₂ ↔
|
||||
s.copy.toByteArray.extract pos₁.offset.byteIdx pos₂.offset.byteIdx = pat.toByteArray := by
|
||||
rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice,
|
||||
ForwardSliceSearcher.isLongestRevMatchAt_iff_extract (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem offset_of_isLongestMatchAt {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat ≠ "") (h' : IsLongestMatchAt pat pos₁ pos₂) :
|
||||
pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by
|
||||
@@ -212,12 +354,25 @@ theorem offset_of_isLongestMatchAt {pat : String} {s : Slice} {pos₁ pos₂ : s
|
||||
exact ForwardSliceSearcher.offset_of_isLongestMatchAt (toSlice_isEmpty h)
|
||||
(isLongestMatchAt_iff_isLongestMatchAt_toSlice.1 h')
|
||||
|
||||
theorem offset_of_isLongestRevMatchAt {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
|
||||
(h : pat ≠ "") (h' : IsLongestRevMatchAt pat pos₁ pos₂) :
|
||||
pos₂.offset = pos₁.offset.increaseBy pat.utf8ByteSize := by
|
||||
rw [show pat.utf8ByteSize = pat.toSlice.utf8ByteSize from utf8ByteSize_toSlice.symm]
|
||||
exact ForwardSliceSearcher.offset_of_isLongestRevMatchAt (toSlice_isEmpty h)
|
||||
(isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice.1 h')
|
||||
|
||||
theorem matchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
MatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits t₁ (pat ++ t₂) := by
|
||||
rw [matchesAt_iff_toSlice,
|
||||
ForwardSliceSearcher.matchesAt_iff_splits (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem revMatchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
RevMatchesAt pat pos ↔ ∃ t₁ t₂, pos.Splits (t₁ ++ pat) t₂ := by
|
||||
rw [revMatchesAt_iff_toSlice,
|
||||
ForwardSliceSearcher.revMatchesAt_iff_splits (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem exists_matchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat ≠ "") :
|
||||
(∃ (pos : s.Pos), MatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat ++ t₂ := by
|
||||
simp only [matchesAt_iff_splits h]
|
||||
@@ -230,6 +385,14 @@ theorem exists_matchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat ≠ "
|
||||
⟨t₁, pat ++ t₂, by rw [← append_assoc]; exact heq, rfl⟩
|
||||
exact ⟨s.pos _ hvalid, t₁, t₂, ⟨by rw [← append_assoc]; exact heq, by simp⟩⟩
|
||||
|
||||
theorem exists_revMatchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat ≠ "") :
|
||||
(∃ (pos : s.Pos), RevMatchesAt pat pos) ↔ ∃ t₁ t₂, s.copy = t₁ ++ pat ++ t₂ := by
|
||||
rw [show (∃ (pos : s.Pos), RevMatchesAt (ρ := String) pat pos) ↔
|
||||
(∃ (pos : s.Pos), RevMatchesAt (ρ := Slice) pat.toSlice pos) from by
|
||||
simp [revMatchesAt_iff_toSlice],
|
||||
ForwardSliceSearcher.exists_revMatchesAt_iff_eq_append (toSlice_isEmpty h)]
|
||||
simp
|
||||
|
||||
theorem matchesAt_iff_isLongestMatchAt {pat : String} {s : Slice} {pos : s.Pos}
|
||||
(h : pat ≠ "") :
|
||||
MatchesAt pat pos ↔ ∃ (h : (pos.offset.increaseBy pat.utf8ByteSize).IsValidForSlice s),
|
||||
@@ -239,6 +402,16 @@ theorem matchesAt_iff_isLongestMatchAt {pat : String} {s : Slice} {pos : s.Pos}
|
||||
simp only [utf8ByteSize_toSlice, ← isLongestMatchAt_iff_isLongestMatchAt_toSlice] at key
|
||||
rwa [matchesAt_iff_toSlice]
|
||||
|
||||
theorem revMatchesAt_iff_isLongestRevMatchAt {pat : String} {s : Slice} {pos : s.Pos}
|
||||
(h : pat ≠ "") :
|
||||
RevMatchesAt pat pos ↔
|
||||
∃ (h : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s),
|
||||
IsLongestRevMatchAt pat (s.pos _ h) pos := by
|
||||
have key := ForwardSliceSearcher.revMatchesAt_iff_isLongestRevMatchAt (pat := pat.toSlice)
|
||||
(toSlice_isEmpty h) (pos := pos)
|
||||
simp only [utf8ByteSize_toSlice, ← isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice] at key
|
||||
rwa [revMatchesAt_iff_toSlice]
|
||||
|
||||
theorem matchesAt_iff_getElem {pat : String} {s : Slice} {pos : s.Pos} (h : pat ≠ "") :
|
||||
MatchesAt pat pos ↔
|
||||
∃ (h : pos.offset.byteIdx + pat.toByteArray.size ≤ s.copy.toByteArray.size),
|
||||
@@ -259,6 +432,11 @@ theorem matchesAt_iff_matchesAt_toSlice {pat : String} {s : Slice}
|
||||
{pos : s.Pos} : MatchesAt pat pos ↔ MatchesAt pat.toSlice pos := by
|
||||
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_toSlice]
|
||||
|
||||
theorem revMatchesAt_iff_revMatchesAt_toSlice {pat : String} {s : Slice}
|
||||
{pos : s.Pos} : RevMatchesAt pat pos ↔ RevMatchesAt pat.toSlice pos := by
|
||||
simp [revMatchesAt_iff_exists_isLongestRevMatchAt,
|
||||
isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice]
|
||||
|
||||
theorem toSearcher_eq {pat : String} {s : Slice} :
|
||||
ToForwardSearcher.toSearcher pat s = ToForwardSearcher.toSearcher pat.toSlice s := (rfl)
|
||||
|
||||
@@ -275,6 +453,21 @@ theorem isValidSearchFrom_iff_isValidSearchFrom_toSlice {pat : String}
|
||||
| matched => simp_all [IsValidSearchFrom.matched, isLongestMatchAt_iff_isLongestMatchAt_toSlice]
|
||||
| mismatched => simp_all [IsValidSearchFrom.mismatched, matchesAt_iff_matchesAt_toSlice]
|
||||
|
||||
theorem isValidRevSearchFrom_iff_isValidRevSearchFrom_toSlice {pat : String}
|
||||
{s : Slice} {pos : s.Pos} {l : List (SearchStep s)} :
|
||||
IsValidRevSearchFrom pat pos l ↔ IsValidRevSearchFrom pat.toSlice pos l := by
|
||||
refine ⟨fun h => ?_, fun h => ?_⟩
|
||||
· induction h with
|
||||
| startPos => simpa using IsValidRevSearchFrom.startPos
|
||||
| matched => simp_all [IsValidRevSearchFrom.matched,
|
||||
isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice]
|
||||
| mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_toSlice]
|
||||
· induction h with
|
||||
| startPos => simpa using IsValidRevSearchFrom.startPos
|
||||
| matched => simp_all [IsValidRevSearchFrom.matched,
|
||||
isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice]
|
||||
| mismatched => simp_all [IsValidRevSearchFrom.mismatched, revMatchesAt_iff_revMatchesAt_toSlice]
|
||||
|
||||
end ForwardStringSearcher
|
||||
|
||||
end String.Slice.Pattern.Model
|
||||
|
||||
@@ -76,10 +76,12 @@ namespace Model.ForwardSliceSearcher
|
||||
|
||||
open Pattern.ForwardSliceSearcher
|
||||
|
||||
public instance {pat : Slice} : LawfulForwardPattern pat where
|
||||
skipPrefixOfNonempty?_eq _ := rfl
|
||||
startsWith_eq _ := isSome_skipPrefix?.symm
|
||||
|
||||
public theorem lawfulForwardPatternModel {pat : Slice} (hpat : pat.isEmpty = false) :
|
||||
LawfulForwardPatternModel pat where
|
||||
skipPrefixOfNonempty?_eq h := rfl
|
||||
startsWith_eq s := isSome_skipPrefix?.symm
|
||||
skipPrefix?_eq_some_iff pos := by
|
||||
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat]
|
||||
|
||||
@@ -89,15 +91,116 @@ namespace Model.ForwardStringSearcher
|
||||
|
||||
open Pattern.ForwardSliceSearcher
|
||||
|
||||
public instance {pat : String} : LawfulForwardPattern pat where
|
||||
skipPrefixOfNonempty?_eq _ := rfl
|
||||
startsWith_eq _ := isSome_skipPrefix?.symm
|
||||
|
||||
public theorem lawfulForwardPatternModel {pat : String} (hpat : pat ≠ "") :
|
||||
LawfulForwardPatternModel pat where
|
||||
skipPrefixOfNonempty?_eq h := rfl
|
||||
startsWith_eq s := isSome_skipPrefix?.symm
|
||||
skipPrefix?_eq_some_iff pos := by
|
||||
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat]
|
||||
|
||||
end Model.ForwardStringSearcher
|
||||
|
||||
namespace BackwardSliceSearcher
|
||||
|
||||
theorem endsWith_iff {pat s : Slice} : endsWith pat s ↔ ∃ t, s.copy = t ++ pat.copy := by
|
||||
rw [endsWith]
|
||||
simp [Internal.memcmpSlice_eq_true_iff, utf8ByteSize_eq_size_toByteArray_copy, -size_toByteArray]
|
||||
generalize pat.copy = pat
|
||||
generalize s.copy = s
|
||||
refine ⟨fun ⟨h₁, h₂⟩ => ?_, ?_⟩
|
||||
· rw [Nat.sub_add_cancel h₁] at h₂
|
||||
suffices (s.rawEndPos.unoffsetBy pat.rawEndPos).IsValid s by
|
||||
have h₃ : (s.sliceFrom (s.pos _ this)).copy = pat := by
|
||||
rw [← toByteArray_inj, (s.pos _ this).splits.toByteArray_right_eq]
|
||||
simpa [offset_pos, Pos.Raw.byteIdx_unoffsetBy, byteIdx_rawEndPos]
|
||||
have := (s.pos _ this).splits
|
||||
rw [h₃] at this
|
||||
exact ⟨_, this.eq_append⟩
|
||||
rw [Pos.Raw.isValid_iff_isValidUTF8_extract_utf8ByteSize]
|
||||
refine ⟨by simp [Pos.Raw.le_iff, Pos.Raw.byteIdx_unoffsetBy], ?_⟩
|
||||
simp only [size_toByteArray] at h₂
|
||||
simpa [Pos.Raw.byteIdx_unoffsetBy, byteIdx_rawEndPos, h₂] using pat.isValidUTF8
|
||||
· rintro ⟨t, rfl⟩
|
||||
exact ⟨by simp, by rw [Nat.sub_add_cancel (by simp)]; exact
|
||||
ByteArray.extract_append_eq_right (by simp) (by simp)⟩
|
||||
|
||||
theorem skipSuffix?_eq_some_iff {pat s : Slice} {pos : s.Pos} :
|
||||
skipSuffix? pat s = some pos ↔ (s.sliceFrom pos).copy = pat.copy := by
|
||||
fun_cases skipSuffix? with
|
||||
| case1 h =>
|
||||
simp only [Option.some.injEq]
|
||||
obtain ⟨t, ht⟩ := endsWith_iff.1 h
|
||||
have hpc : pat.copy.utf8ByteSize = pat.utf8ByteSize := Slice.utf8ByteSize_copy
|
||||
have hsz : s.utf8ByteSize = t.utf8ByteSize + pat.utf8ByteSize := by
|
||||
have := congrArg String.utf8ByteSize ht
|
||||
simp only [utf8ByteSize_append, Slice.utf8ByteSize_copy] at this
|
||||
exact this
|
||||
have hoff : (s.endPos.offset.unoffsetBy pat.rawEndPos) = t.rawEndPos := by
|
||||
ext
|
||||
simp only [offset_endPos, Pos.Raw.byteIdx_unoffsetBy, byteIdx_rawEndPos,
|
||||
String.byteIdx_rawEndPos]
|
||||
omega
|
||||
have hval : (s.endPos.offset.unoffsetBy pat.rawEndPos).IsValidForSlice s :=
|
||||
Pos.Raw.isValidForSlice_iff_exists_append.mpr ⟨t, pat.copy, ht, hoff⟩
|
||||
have hsp : (s.pos _ hval).Splits t pat.copy := ⟨ht, hoff⟩
|
||||
rw [Slice.pos!_eq_pos hval]
|
||||
exact ⟨(· ▸ hsp.copy_sliceFrom_eq),
|
||||
fun h => hsp.pos_eq_of_eq_right (h ▸ pos.splits)⟩
|
||||
| case2 h =>
|
||||
simp only [endsWith_iff, not_exists] at h
|
||||
simp only [reduceCtorEq, false_iff]
|
||||
intro heq
|
||||
have := h (s.sliceTo pos).copy
|
||||
simp [← heq, pos.splits.eq_append] at this
|
||||
|
||||
theorem isSome_skipSuffix? {pat s : Slice} : (skipSuffix? pat s).isSome = endsWith pat s := by
|
||||
fun_cases skipSuffix? <;> simp_all
|
||||
|
||||
public theorem endsWith_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
|
||||
BackwardPattern.endsWith pat s = true := by
|
||||
suffices pat.copy = "" by simp [BackwardPattern.endsWith, endsWith_iff, this]
|
||||
simpa
|
||||
|
||||
public theorem skipSuffix?_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
|
||||
BackwardPattern.skipSuffix? pat s = some s.endPos := by
|
||||
simpa [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff]
|
||||
|
||||
end BackwardSliceSearcher
|
||||
|
||||
namespace Model.BackwardSliceSearcher
|
||||
|
||||
open Pattern.BackwardSliceSearcher
|
||||
|
||||
public instance {pat : Slice} : LawfulBackwardPattern pat where
|
||||
skipSuffixOfNonempty?_eq _ := rfl
|
||||
endsWith_eq _ := isSome_skipSuffix?.symm
|
||||
|
||||
public theorem lawfulBackwardPatternModel {pat : Slice} (hpat : pat.isEmpty = false) :
|
||||
LawfulBackwardPatternModel pat where
|
||||
skipSuffix?_eq_some_iff pos := by
|
||||
simp [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff,
|
||||
ForwardSliceSearcher.isLongestRevMatch_iff hpat]
|
||||
|
||||
end Model.BackwardSliceSearcher
|
||||
|
||||
namespace Model.BackwardStringSearcher
|
||||
|
||||
open Pattern.BackwardSliceSearcher
|
||||
|
||||
public instance {pat : String} : LawfulBackwardPattern pat where
|
||||
skipSuffixOfNonempty?_eq _ := rfl
|
||||
endsWith_eq _ := isSome_skipSuffix?.symm
|
||||
|
||||
public theorem lawfulBackwardPatternModel {pat : String} (hpat : pat ≠ "") :
|
||||
LawfulBackwardPatternModel pat where
|
||||
skipSuffix?_eq_some_iff pos := by
|
||||
simp [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff,
|
||||
ForwardStringSearcher.isLongestRevMatch_iff hpat]
|
||||
|
||||
end Model.BackwardStringSearcher
|
||||
|
||||
end Pattern
|
||||
|
||||
public theorem startsWith_string_eq_startsWith_toSlice {pat : String} {s : Slice} :
|
||||
|
||||
@@ -29,12 +29,12 @@ theorem startsWith_eq_forwardPatternStartsWith {ρ : Type} {pat : ρ} [ForwardPa
|
||||
theorem dropPrefix?_eq_map_skipPrefix? {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : Slice} :
|
||||
s.dropPrefix? pat = (s.skipPrefix? pat).map s.sliceFrom := (rfl)
|
||||
|
||||
theorem Pattern.Model.skipPrefix?_eq_some_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat]
|
||||
theorem Pattern.Model.skipPrefix?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat]
|
||||
[LawfulForwardPatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
s.skipPrefix? pat = some pos ↔ IsLongestMatch pat pos := by
|
||||
rw [skipPrefix?_eq_forwardPatternSkipPrefix?, LawfulForwardPatternModel.skipPrefix?_eq_some_iff]
|
||||
|
||||
theorem Pattern.Model.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat]
|
||||
theorem Pattern.Model.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat]
|
||||
[LawfulForwardPatternModel pat] {s : Slice} :
|
||||
s.skipPrefix? pat = none ↔ ¬ MatchesAt pat s.startPos := by
|
||||
rw [skipPrefix?_eq_forwardPatternSkipPrefix?, LawfulForwardPatternModel.skipPrefix?_eq_none_iff]
|
||||
@@ -44,13 +44,13 @@ theorem isSome_skipPrefix? {ρ : Type} {pat : ρ} [ForwardPattern pat] [LawfulFo
|
||||
(s.skipPrefix? pat).isSome = s.startsWith pat := by
|
||||
rw [startsWith_eq_forwardPatternStartsWith, skipPrefix?, LawfulForwardPattern.startsWith_eq]
|
||||
|
||||
theorem Pattern.Model.startsWith_eq_false_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat]
|
||||
theorem Pattern.Model.startsWith_eq_false_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat]
|
||||
[LawfulForwardPatternModel pat] {s : Slice} :
|
||||
s.startsWith pat = false ↔ ¬ MatchesAt pat s.startPos := by
|
||||
rw [← Pattern.Model.skipPrefix?_eq_none_iff, ← Option.isNone_iff_eq_none,
|
||||
← isSome_skipPrefix?, Option.isSome_eq_false_iff]
|
||||
|
||||
theorem Pattern.Model.startsWith_iff {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat]
|
||||
theorem Pattern.Model.startsWith_iff {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat]
|
||||
[LawfulForwardPatternModel pat] {s : Slice} :
|
||||
s.startsWith pat = true ↔ MatchesAt pat s.startPos := by
|
||||
rw [← Bool.not_eq_false, startsWith_eq_false_iff, Classical.not_not]
|
||||
@@ -65,13 +65,65 @@ theorem dropPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [Law
|
||||
{s : Slice} : s.dropPrefix? pat = none ↔ s.startsWith pat = false := by
|
||||
simp [dropPrefix?_eq_map_skipPrefix?]
|
||||
|
||||
theorem Pattern.Model.eq_append_of_dropPrefix?_eq_some {ρ : Type} {pat : ρ} [ForwardPatternModel pat] [ForwardPattern pat]
|
||||
theorem Pattern.Model.eq_append_of_dropPrefix?_eq_some {ρ : Type} {pat : ρ} [PatternModel pat] [ForwardPattern pat]
|
||||
[LawfulForwardPatternModel pat] {s res : Slice} (h : s.dropPrefix? pat = some res) :
|
||||
∃ t, ForwardPatternModel.Matches pat t ∧ s.copy = t ++ res.copy := by
|
||||
∃ t, PatternModel.Matches pat t ∧ s.copy = t ++ res.copy := by
|
||||
simp only [dropPrefix?_eq_map_skipPrefix?, Option.map_eq_some_iff, skipPrefix?_eq_some_iff] at h
|
||||
obtain ⟨pos, h₁, h₂⟩ := h
|
||||
exact ⟨(s.sliceTo pos).copy, h₁.isMatch.matches_copy, by simp [← h₂, ← copy_eq_copy_sliceTo]⟩
|
||||
|
||||
theorem skipSuffix?_eq_backwardPatternSkipSuffix? {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : Slice} :
|
||||
s.skipSuffix? pat = BackwardPattern.skipSuffix? pat s := (rfl)
|
||||
|
||||
theorem endsWith_eq_backwardPatternEndsWith {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : Slice} :
|
||||
s.endsWith pat = BackwardPattern.endsWith pat s := (rfl)
|
||||
|
||||
theorem dropSuffix?_eq_map_skipSuffix? {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : Slice} :
|
||||
s.dropSuffix? pat = (s.skipSuffix? pat).map s.sliceTo := (rfl)
|
||||
|
||||
theorem Pattern.Model.skipSuffix?_eq_some_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat]
|
||||
[LawfulBackwardPatternModel pat] {s : Slice} {pos : s.Pos} :
|
||||
s.skipSuffix? pat = some pos ↔ IsLongestRevMatch pat pos := by
|
||||
rw [skipSuffix?_eq_backwardPatternSkipSuffix?, LawfulBackwardPatternModel.skipSuffix?_eq_some_iff]
|
||||
|
||||
theorem Pattern.Model.skipSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat]
|
||||
[LawfulBackwardPatternModel pat] {s : Slice} :
|
||||
s.skipSuffix? pat = none ↔ ¬ RevMatchesAt pat s.endPos := by
|
||||
rw [skipSuffix?_eq_backwardPatternSkipSuffix?, LawfulBackwardPatternModel.skipSuffix?_eq_none_iff]
|
||||
|
||||
@[simp]
|
||||
theorem isSome_skipSuffix? {ρ : Type} {pat : ρ} [BackwardPattern pat] [LawfulBackwardPattern pat] {s : Slice} :
|
||||
(s.skipSuffix? pat).isSome = s.endsWith pat := by
|
||||
rw [endsWith_eq_backwardPatternEndsWith, skipSuffix?, LawfulBackwardPattern.endsWith_eq]
|
||||
|
||||
theorem Pattern.Model.endsWith_eq_false_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat]
|
||||
[LawfulBackwardPatternModel pat] {s : Slice} :
|
||||
s.endsWith pat = false ↔ ¬ RevMatchesAt pat s.endPos := by
|
||||
rw [← Pattern.Model.skipSuffix?_eq_none_iff, ← Option.isNone_iff_eq_none,
|
||||
← isSome_skipSuffix?, Option.isSome_eq_false_iff]
|
||||
|
||||
theorem Pattern.Model.endsWith_iff {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat]
|
||||
[LawfulBackwardPatternModel pat] {s : Slice} :
|
||||
s.endsWith pat = true ↔ RevMatchesAt pat s.endPos := by
|
||||
rw [← Bool.not_eq_false, endsWith_eq_false_iff, Classical.not_not]
|
||||
|
||||
@[simp]
|
||||
theorem skipSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [LawfulBackwardPattern pat]
|
||||
{s : Slice} : s.skipSuffix? pat = none ↔ s.endsWith pat = false := by
|
||||
rw [← Option.isNone_iff_eq_none, ← Option.isSome_eq_false_iff, isSome_skipSuffix?]
|
||||
|
||||
@[simp]
|
||||
theorem dropSuffix?_eq_none_iff {ρ : Type} {pat : ρ} [BackwardPattern pat] [LawfulBackwardPattern pat]
|
||||
{s : Slice} : s.dropSuffix? pat = none ↔ s.endsWith pat = false := by
|
||||
simp [dropSuffix?_eq_map_skipSuffix?]
|
||||
|
||||
theorem Pattern.Model.eq_append_of_dropSuffix?_eq_some {ρ : Type} {pat : ρ} [PatternModel pat] [BackwardPattern pat]
|
||||
[LawfulBackwardPatternModel pat] {s res : Slice} (h : s.dropSuffix? pat = some res) :
|
||||
∃ t, PatternModel.Matches pat t ∧ s.copy = res.copy ++ t := by
|
||||
simp only [dropSuffix?_eq_map_skipSuffix?, Option.map_eq_some_iff, skipSuffix?_eq_some_iff] at h
|
||||
obtain ⟨pos, h₁, h₂⟩ := h
|
||||
exact ⟨(s.sliceFrom pos).copy, h₁.isRevMatch.matches_copy, by simp [← h₂, ← copy_eq_copy_sliceTo]⟩
|
||||
|
||||
end Slice
|
||||
|
||||
theorem skipPrefix?_eq_skipPrefix?_toSlice {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : String} :
|
||||
@@ -83,4 +135,13 @@ theorem startsWith_eq_startsWith_toSlice {ρ : Type} {pat : ρ} [ForwardPattern
|
||||
theorem dropPrefix?_eq_dropPrefix?_toSlice {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : String} :
|
||||
s.dropPrefix? pat = s.toSlice.dropPrefix? pat := (rfl)
|
||||
|
||||
theorem skipSuffix?_eq_skipSuffix?_toSlice {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : String} :
|
||||
s.skipSuffix? pat = (s.toSlice.skipSuffix? pat).map Pos.ofToSlice := (rfl)
|
||||
|
||||
theorem endsWith_eq_endsWith_toSlice {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : String} :
|
||||
s.endsWith pat = s.toSlice.endsWith pat := (rfl)
|
||||
|
||||
theorem dropSuffix?_eq_dropSuffix?_toSlice {ρ : Type} {pat : ρ} [BackwardPattern pat] {s : String} :
|
||||
s.dropSuffix? pat = s.toSlice.dropSuffix? pat := (rfl)
|
||||
|
||||
end String
|
||||
|
||||
@@ -11,6 +11,8 @@ public import Init.Data.String.TakeDrop
|
||||
import Init.Data.String.Lemmas.Pattern.TakeDrop.Basic
|
||||
import Init.Data.String.Lemmas.Pattern.Char
|
||||
import Init.Data.Option.Lemmas
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
import Init.Data.List.Sublist
|
||||
|
||||
public section
|
||||
|
||||
@@ -52,7 +54,42 @@ theorem startsWith_char_eq_false_iff_forall_append {c : Char} {s : Slice} :
|
||||
|
||||
theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s res : Slice} (h : s.dropPrefix? c = some res) :
|
||||
s.copy = singleton c ++ res.copy := by
|
||||
simpa [ForwardPatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
|
||||
simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
|
||||
|
||||
theorem skipSuffix?_char_eq_some_iff {c : Char} {s : Slice} {pos : s.Pos} :
|
||||
s.skipSuffix? c = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by
|
||||
rw [Pattern.Model.skipSuffix?_eq_some_iff, Char.isLongestRevMatch_iff]
|
||||
|
||||
theorem endsWith_char_iff_get {c : Char} {s : Slice} :
|
||||
s.endsWith c ↔ ∃ h, (s.endPos.prev h).get (by simp) = c := by
|
||||
simp [Pattern.Model.endsWith_iff, Char.revMatchesAt_iff]
|
||||
|
||||
theorem endsWith_char_eq_false_iff_get {c : Char} {s : Slice} :
|
||||
s.endsWith c = false ↔ ∀ h, (s.endPos.prev h).get (by simp) ≠ c := by
|
||||
simp [Pattern.Model.endsWith_eq_false_iff, Char.revMatchesAt_iff]
|
||||
|
||||
theorem endsWith_char_iff_exists_append {c : Char} {s : Slice} :
|
||||
s.endsWith c ↔ ∃ t, s.copy = t ++ singleton c := by
|
||||
rw [Pattern.Model.endsWith_iff, Char.revMatchesAt_iff_splits]
|
||||
simp only [splits_endPos_iff, exists_eq_right, eq_comm (a := s.copy)]
|
||||
|
||||
theorem endsWith_char_eq_getLast? {c : Char} {s : Slice} :
|
||||
s.endsWith c = (s.copy.toList.getLast? == some c) := by
|
||||
rw [Bool.eq_iff_iff, endsWith_char_iff_exists_append, beq_iff_eq,
|
||||
← List.singleton_suffix_iff_getLast?_eq_some, List.suffix_iff_exists_eq_append]
|
||||
constructor
|
||||
· rintro ⟨t, ht⟩
|
||||
exact ⟨t.toList, by rw [ht, toList_append, toList_singleton]⟩
|
||||
· rintro ⟨l, hl⟩
|
||||
exact ⟨ofList l, by rw [← toList_inj, toList_append, toList_singleton, toList_ofList]; exact hl⟩
|
||||
|
||||
theorem endsWith_char_eq_false_iff_forall_append {c : Char} {s : Slice} :
|
||||
s.endsWith c = false ↔ ∀ t, s.copy ≠ t ++ singleton c := by
|
||||
simp [← Bool.not_eq_true, endsWith_char_iff_exists_append]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_char_eq_some {c : Char} {s res : Slice} (h : s.dropSuffix? c = some res) :
|
||||
s.copy = res.copy ++ singleton c := by
|
||||
simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropSuffix?_eq_some h
|
||||
|
||||
end Slice
|
||||
|
||||
@@ -86,4 +123,34 @@ theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s : String} {res : Sli
|
||||
rw [dropPrefix?_eq_dropPrefix?_toSlice] at h
|
||||
simpa using Slice.eq_append_of_dropPrefix?_char_eq_some h
|
||||
|
||||
theorem skipSuffix?_char_eq_some_iff {c : Char} {s : String} {pos : s.Pos} :
|
||||
s.skipSuffix? c = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c := by
|
||||
simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_char_eq_some_iff, ← Pos.toSlice_inj,
|
||||
Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_char_iff_get {c : Char} {s : String} :
|
||||
s.endsWith c ↔ ∃ h, (s.endPos.prev h).get (by simp) = c := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_iff_get, Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_char_eq_false_iff_get {c : Char} {s : String} :
|
||||
s.endsWith c = false ↔ ∀ h, (s.endPos.prev h).get (by simp) ≠ c := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_eq_false_iff_get, Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_char_eq_getLast? {c : Char} {s : String} :
|
||||
s.endsWith c = (s.toList.getLast? == some c) := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_eq_getLast?]
|
||||
|
||||
theorem endsWith_char_iff_exists_append {c : Char} {s : String} :
|
||||
s.endsWith c ↔ ∃ t, s = t ++ singleton c := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_char_iff_exists_append]
|
||||
|
||||
theorem endsWith_char_eq_false_iff_forall_append {c : Char} {s : String} :
|
||||
s.endsWith c = false ↔ ∀ t, s ≠ t ++ singleton c := by
|
||||
simp [← Bool.not_eq_true, endsWith_char_iff_exists_append]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_char_eq_some {c : Char} {s : String} {res : Slice} (h : s.dropSuffix? c = some res) :
|
||||
s = res.copy ++ singleton c := by
|
||||
rw [dropSuffix?_eq_dropSuffix?_toSlice] at h
|
||||
simpa using Slice.eq_append_of_dropSuffix?_char_eq_some h
|
||||
|
||||
end String
|
||||
|
||||
@@ -11,6 +11,7 @@ public import Init.Data.String.TakeDrop
|
||||
import Init.Data.String.Lemmas.Pattern.TakeDrop.Basic
|
||||
import Init.Data.String.Lemmas.Pattern.Pred
|
||||
import Init.Data.Option.Lemmas
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
import Init.ByCases
|
||||
|
||||
public section
|
||||
@@ -45,7 +46,7 @@ theorem startsWith_bool_eq_head? {p : Char → Bool} {s : Slice} :
|
||||
|
||||
theorem eq_append_of_dropPrefix?_bool_eq_some {p : Char → Bool} {s res : Slice} (h : s.dropPrefix? p = some res) :
|
||||
∃ c, s.copy = singleton c ++ res.copy ∧ p c = true := by
|
||||
obtain ⟨_, ⟨c, ⟨rfl, h₁⟩⟩, h₂⟩ := by simpa [ForwardPatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
|
||||
obtain ⟨_, ⟨c, ⟨rfl, h₁⟩⟩, h₂⟩ := by simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
|
||||
exact ⟨_, h₂, h₁⟩
|
||||
|
||||
theorem skipPrefix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} :
|
||||
@@ -69,6 +70,54 @@ theorem eq_append_of_dropPrefix_prop_eq_some {P : Char → Prop} [DecidablePred
|
||||
rw [dropPrefix?_prop_eq_dropPrefix?_decide] at h
|
||||
simpa using eq_append_of_dropPrefix?_bool_eq_some h
|
||||
|
||||
theorem skipSuffix?_bool_eq_some_iff {p : Char → Bool} {s : Slice} {pos : s.Pos} :
|
||||
s.skipSuffix? p = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) = true := by
|
||||
rw [Pattern.Model.skipSuffix?_eq_some_iff, CharPred.isLongestRevMatch_iff]
|
||||
|
||||
theorem endsWith_bool_iff_get {p : Char → Bool} {s : Slice} :
|
||||
s.endsWith p ↔ ∃ h, p ((s.endPos.prev h).get (by simp)) = true := by
|
||||
simp [Pattern.Model.endsWith_iff, CharPred.revMatchesAt_iff]
|
||||
|
||||
theorem endsWith_bool_eq_false_iff_get {p : Char → Bool} {s : Slice} :
|
||||
s.endsWith p = false ↔ ∀ h, p ((s.endPos.prev h).get (by simp)) = false := by
|
||||
simp [Pattern.Model.endsWith_eq_false_iff, CharPred.revMatchesAt_iff]
|
||||
|
||||
theorem endsWith_bool_eq_getLast? {p : Char → Bool} {s : Slice} :
|
||||
s.endsWith p = s.copy.toList.getLast?.any p := by
|
||||
rw [Bool.eq_iff_iff, Pattern.Model.endsWith_iff, CharPred.revMatchesAt_iff]
|
||||
by_cases h : s.endPos = s.startPos
|
||||
· refine ⟨fun ⟨h', _⟩ => by simp_all, ?_⟩
|
||||
have : s.copy = "" := by simp_all [Slice.startPos_eq_endPos_iff.mp h.symm]
|
||||
simp [this]
|
||||
· obtain ⟨t, ht⟩ := s.splits_endPos.exists_eq_append_singleton_of_ne_startPos h
|
||||
simp [h, ht]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_bool_eq_some {p : Char → Bool} {s res : Slice} (h : s.dropSuffix? p = some res) :
|
||||
∃ c, s.copy = res.copy ++ singleton c ∧ p c = true := by
|
||||
obtain ⟨_, ⟨c, ⟨rfl, h₁⟩⟩, h₂⟩ := by simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropSuffix?_eq_some h
|
||||
exact ⟨_, h₂, h₁⟩
|
||||
|
||||
theorem skipSuffix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} :
|
||||
s.skipSuffix? P = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ P ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [skipSuffix?_prop_eq_skipSuffix?_decide, skipSuffix?_bool_eq_some_iff]
|
||||
|
||||
theorem endsWith_prop_iff_get {P : Char → Prop} [DecidablePred P] {s : Slice} :
|
||||
s.endsWith P ↔ ∃ h, P ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [endsWith_prop_eq_endsWith_decide, endsWith_bool_iff_get]
|
||||
|
||||
theorem endsWith_prop_eq_false_iff_get {P : Char → Prop} [DecidablePred P] {s : Slice} :
|
||||
s.endsWith P = false ↔ ∀ h, ¬ P ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [endsWith_prop_eq_endsWith_decide, endsWith_bool_eq_false_iff_get]
|
||||
|
||||
theorem endsWith_prop_eq_getLast? {P : Char → Prop} [DecidablePred P] {s : Slice} :
|
||||
s.endsWith P = s.copy.toList.getLast?.any (decide <| P ·) := by
|
||||
simp [endsWith_prop_eq_endsWith_decide, endsWith_bool_eq_getLast?]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_prop_eq_some {P : Char → Prop} [DecidablePred P] {s res : Slice} (h : s.dropSuffix? P = some res) :
|
||||
∃ c, s.copy = res.copy ++ singleton c ∧ P c := by
|
||||
rw [dropSuffix?_prop_eq_dropSuffix?_decide] at h
|
||||
simpa using eq_append_of_dropSuffix?_bool_eq_some h
|
||||
|
||||
end Slice
|
||||
|
||||
theorem skipPrefix?_bool_eq_some_iff {p : Char → Bool} {s : String} {pos : s.Pos} :
|
||||
@@ -115,4 +164,48 @@ theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char → Prop} [DecidablePred
|
||||
rw [dropPrefix?_eq_dropPrefix?_toSlice] at h
|
||||
simpa using Slice.eq_append_of_dropPrefix_prop_eq_some h
|
||||
|
||||
theorem skipSuffix?_bool_eq_some_iff {p : Char → Bool} {s : String} {pos : s.Pos} :
|
||||
s.skipSuffix? p = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) = true := by
|
||||
simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_bool_eq_some_iff, ← Pos.toSlice_inj,
|
||||
Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_bool_iff_get {p : Char → Bool} {s : String} :
|
||||
s.endsWith p ↔ ∃ h, p ((s.endPos.prev h).get (by simp)) = true := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_bool_iff_get, Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_bool_eq_false_iff_get {p : Char → Bool} {s : String} :
|
||||
s.endsWith p = false ↔ ∀ h, p ((s.endPos.prev h).get (by simp)) = false := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_bool_eq_false_iff_get, Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_bool_eq_getLast? {p : Char → Bool} {s : String} :
|
||||
s.endsWith p = s.toList.getLast?.any p := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_bool_eq_getLast?]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_bool_eq_some {p : Char → Bool} {s : String} {res : Slice} (h : s.dropSuffix? p = some res) :
|
||||
∃ c, s = res.copy ++ singleton c ∧ p c = true := by
|
||||
rw [dropSuffix?_eq_dropSuffix?_toSlice] at h
|
||||
simpa using Slice.eq_append_of_dropSuffix?_bool_eq_some h
|
||||
|
||||
theorem skipSuffix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s : String} {pos : s.Pos} :
|
||||
s.skipSuffix? P = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ P ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_prop_eq_some_iff, ← Pos.toSlice_inj,
|
||||
Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_prop_iff_get {P : Char → Prop} [DecidablePred P] {s : String} :
|
||||
s.endsWith P ↔ ∃ h, P ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_prop_iff_get, Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_prop_eq_false_iff_get {P : Char → Prop} [DecidablePred P] {s : String} :
|
||||
s.endsWith P = false ↔ ∀ h, ¬ P ((s.endPos.prev h).get (by simp)) := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_prop_eq_false_iff_get, Pos.prev_toSlice]
|
||||
|
||||
theorem endsWith_prop_eq_getLast? {P : Char → Prop} [DecidablePred P] {s : String} :
|
||||
s.endsWith P = s.toList.getLast?.any (decide <| P ·) := by
|
||||
simp [endsWith_eq_endsWith_toSlice, Slice.endsWith_prop_eq_getLast?]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_prop_eq_some {P : Char → Prop} [DecidablePred P] {s : String} {res : Slice}
|
||||
(h : s.dropSuffix? P = some res) : ∃ c, s = res.copy ++ singleton c ∧ P c := by
|
||||
rw [dropSuffix?_eq_dropSuffix?_toSlice] at h
|
||||
simpa using Slice.eq_append_of_dropSuffix?_prop_eq_some h
|
||||
|
||||
end String
|
||||
|
||||
@@ -67,7 +67,7 @@ theorem eq_append_of_dropPrefix?_slice_eq_some {pat s res : Slice} (h : s.dropPr
|
||||
| false =>
|
||||
have := ForwardSliceSearcher.lawfulForwardPatternModel hpat
|
||||
have := Pattern.Model.eq_append_of_dropPrefix?_eq_some h
|
||||
simp only [ForwardPatternModel.Matches] at this
|
||||
simp only [PatternModel.Matches] at this
|
||||
obtain ⟨_, ⟨-, rfl⟩, h⟩ := this
|
||||
exact h
|
||||
| true => simp [Option.some.inj (h ▸ dropPrefix?_slice_of_isEmpty hpat), (show pat.copy = "" by simpa)]
|
||||
@@ -104,6 +104,87 @@ theorem eq_append_of_dropPrefix?_string_eq_some {pat : String} {s res : Slice} (
|
||||
rw [dropPrefix?_string_eq_dropPrefix?_toSlice] at h
|
||||
simpa using eq_append_of_dropPrefix?_slice_eq_some h
|
||||
|
||||
theorem skipSuffix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
|
||||
s.skipSuffix? pat = some s.endPos := by
|
||||
rw [skipSuffix?_eq_backwardPatternSkipSuffix?, BackwardSliceSearcher.skipSuffix?_of_isEmpty hpat]
|
||||
|
||||
@[simp]
|
||||
theorem skipSuffix?_slice_eq_some_iff {pat s : Slice} {pos : s.Pos} :
|
||||
s.skipSuffix? pat = some pos ↔ ∃ t, pos.Splits t pat.copy := by
|
||||
match h : pat.isEmpty with
|
||||
| false =>
|
||||
have := BackwardSliceSearcher.lawfulBackwardPatternModel h
|
||||
rw [Pattern.Model.skipSuffix?_eq_some_iff, ForwardSliceSearcher.isLongestRevMatch_iff_splits h]
|
||||
| true => simp [skipSuffix?_slice_of_isEmpty h, (show pat.copy = "" by simpa), eq_comm]
|
||||
|
||||
theorem endsWith_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
|
||||
s.endsWith pat = true := by
|
||||
rw [endsWith_eq_backwardPatternEndsWith, BackwardSliceSearcher.endsWith_of_isEmpty hpat]
|
||||
|
||||
@[simp]
|
||||
theorem endsWith_slice_iff {pat s : Slice} :
|
||||
s.endsWith pat ↔ pat.copy.toList <:+ s.copy.toList := by
|
||||
match h : pat.isEmpty with
|
||||
| false =>
|
||||
have := BackwardSliceSearcher.lawfulBackwardPatternModel h
|
||||
simp only [Model.endsWith_iff, ForwardSliceSearcher.revMatchesAt_iff_splits h,
|
||||
splits_endPos_iff, exists_eq_right]
|
||||
simp only [← toList_inj, toList_append, List.suffix_iff_exists_append_eq]
|
||||
exact ⟨fun ⟨t, ht⟩ => ⟨t.toList, by simp [ht]⟩, fun ⟨t, ht⟩ => ⟨String.ofList t, by simp [← ht]⟩⟩
|
||||
| true => simp [endsWith_slice_of_isEmpty h, (show pat.copy = "" by simpa)]
|
||||
|
||||
@[simp]
|
||||
theorem endsWith_slice_eq_false_iff {pat s : Slice} :
|
||||
s.endsWith pat = false ↔ ¬ (pat.copy.toList <:+ s.copy.toList) := by
|
||||
simp [← Bool.not_eq_true, endsWith_slice_iff]
|
||||
|
||||
theorem dropSuffix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
|
||||
s.dropSuffix? pat = some s := by
|
||||
simp [dropSuffix?_eq_map_skipSuffix?, skipSuffix?_slice_of_isEmpty hpat]
|
||||
|
||||
theorem eq_append_of_dropSuffix?_slice_eq_some {pat s res : Slice} (h : s.dropSuffix? pat = some res) :
|
||||
s.copy = res.copy ++ pat.copy := by
|
||||
match hpat : pat.isEmpty with
|
||||
| false =>
|
||||
have := BackwardSliceSearcher.lawfulBackwardPatternModel hpat
|
||||
have := Pattern.Model.eq_append_of_dropSuffix?_eq_some h
|
||||
simp only [PatternModel.Matches] at this
|
||||
obtain ⟨_, ⟨-, rfl⟩, h⟩ := this
|
||||
exact h
|
||||
| true => simp [Option.some.inj (h ▸ dropSuffix?_slice_of_isEmpty hpat), (show pat.copy = "" by simpa)]
|
||||
|
||||
@[simp]
|
||||
theorem skipSuffix?_string_eq_some_iff' {pat : String} {s : Slice} {pos : s.Pos} :
|
||||
s.skipSuffix? pat = some pos ↔ ∃ t, pos.Splits t pat := by
|
||||
simp [skipSuffix?_string_eq_skipSuffix?_toSlice]
|
||||
|
||||
@[simp]
|
||||
theorem skipSuffix?_string_empty {s : Slice} : s.skipSuffix? "" = some s.endPos := by
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem endsWith_string_iff {pat : String} {s : Slice} :
|
||||
s.endsWith pat ↔ pat.toList <:+ s.copy.toList := by
|
||||
simp [endsWith_string_eq_endsWith_toSlice]
|
||||
|
||||
@[simp]
|
||||
theorem endsWith_string_empty {s : Slice} : s.endsWith "" = true := by
|
||||
simp
|
||||
|
||||
@[simp]
|
||||
theorem endsWith_string_eq_false_iff {pat : String} {s : Slice} :
|
||||
s.endsWith pat = false ↔ ¬ (pat.toList <:+ s.copy.toList) := by
|
||||
simp [endsWith_string_eq_endsWith_toSlice]
|
||||
|
||||
@[simp]
|
||||
theorem dropSuffix?_string_empty {s : Slice} : s.dropSuffix? "" = some s := by
|
||||
simpa [dropSuffix?_string_eq_dropSuffix?_toSlice] using dropSuffix?_slice_of_isEmpty (by simp)
|
||||
|
||||
theorem eq_append_of_dropSuffix?_string_eq_some {pat : String} {s res : Slice} (h : s.dropSuffix? pat = some res) :
|
||||
s.copy = res.copy ++ pat := by
|
||||
rw [dropSuffix?_string_eq_dropSuffix?_toSlice] at h
|
||||
simpa using eq_append_of_dropSuffix?_slice_eq_some h
|
||||
|
||||
end Slice
|
||||
|
||||
theorem skipPrefix?_slice_of_isEmpty {pat : Slice} {s : String} (hpat : pat.isEmpty = true) :
|
||||
|
||||
@@ -8,6 +8,8 @@ module
|
||||
prelude
|
||||
public import Init.Data.String.Search
|
||||
import all Init.Data.String.Search
|
||||
import Init.Data.String.Lemmas.Slice
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
|
||||
public section
|
||||
|
||||
@@ -28,4 +30,42 @@ theorem Pos.le_find {s : String} (pos : s.Pos) (pattern : ρ) [ToForwardSearcher
|
||||
pos ≤ pos.find pattern := by
|
||||
simp [Pos.find, ← toSlice_le]
|
||||
|
||||
@[simp]
|
||||
theorem front?_toSlice {s : String} : s.toSlice.front? = s.front? :=
|
||||
(rfl)
|
||||
|
||||
theorem front?_eq_get? {s : String} : s.front? = s.startPos.get? := by
|
||||
simp [← front?_toSlice, ← Pos.get?_toSlice, Slice.front?_eq_get?]
|
||||
|
||||
theorem front?_eq {s : String} : s.front? = s.toList.head? := by
|
||||
simp [← front?_toSlice, Slice.front?_eq]
|
||||
|
||||
@[simp]
|
||||
theorem front_toSlice {s : String} : s.toSlice.front = s.front :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem front_eq {s : String} : s.front = s.front?.getD default := by
|
||||
simp [← front_toSlice, Slice.front_eq]
|
||||
|
||||
@[simp]
|
||||
theorem back?_toSlice {s : String} : s.toSlice.back? = s.back? :=
|
||||
(rfl)
|
||||
|
||||
theorem back?_eq_get? {s : String} : s.back? = s.endPos.prev?.bind Pos.get? := by
|
||||
simp only [← back?_toSlice, Slice.back?_eq_get?, endPos_toSlice, Slice.Pos.prev?_eq_dif,
|
||||
startPos_toSlice, Pos.toSlice_inj, Pos.prev?_eq_dif]
|
||||
split <;> simp [← Pos.get?_toSlice, Pos.toSlice_prev]
|
||||
|
||||
theorem back?_eq {s : String} : s.back? = s.toList.getLast? := by
|
||||
simp [← back?_toSlice, Slice.back?_eq]
|
||||
|
||||
@[simp]
|
||||
theorem back_toSlice {s : String} : s.toSlice.back = s.back :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem back_eq {s : String} : s.back = s.back?.getD default := by
|
||||
simp [← back_toSlice, Slice.back_eq]
|
||||
|
||||
end String
|
||||
|
||||
@@ -11,6 +11,8 @@ import all Init.Data.String.Slice
|
||||
import Init.Data.String.Lemmas.Pattern.Memcmp
|
||||
import Init.Data.String.Lemmas.Basic
|
||||
import Init.Data.ByteArray.Lemmas
|
||||
import Init.Data.String.Lemmas.IsEmpty
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
|
||||
public section
|
||||
|
||||
@@ -33,9 +35,104 @@ theorem beq_eq_true_iff {s t : Slice} : s == t ↔ s.copy = t.copy := by
|
||||
theorem beq_eq_false_iff {s t : Slice} : (s == t) = false ↔ s.copy ≠ t.copy := by
|
||||
simp [← Bool.not_eq_true]
|
||||
|
||||
theorem beq_eq_decide {s t : Slice} : (s == t) = decide (s.copy = t.copy) := by
|
||||
cases h : s == t <;> simp_all
|
||||
theorem beq_eq_decide {s t : Slice} : (s == t) = decide (s.copy = t.copy) :=
|
||||
Bool.eq_iff_iff.2 (by simp)
|
||||
|
||||
instance : EquivBEq String.Slice :=
|
||||
equivBEq_of_iff_apply_eq copy (by simp)
|
||||
|
||||
theorem beq_list_iff {l l' : List String.Slice} : l == l' ↔ l.map copy = l'.map copy := by
|
||||
induction l generalizing l' <;> cases l' <;> simp_all
|
||||
|
||||
theorem beq_list_eq_false_iff {l l' : List String.Slice} :
|
||||
(l == l') = false ↔ l.map copy ≠ l'.map copy := by
|
||||
simp [← Bool.not_eq_true, beq_list_iff]
|
||||
|
||||
theorem beq_list_eq_decide {l l' : List String.Slice} :
|
||||
(l == l') = decide (l.map copy = l'.map copy) :=
|
||||
Bool.eq_iff_iff.2 (by simp [beq_list_iff])
|
||||
|
||||
end BEq
|
||||
|
||||
end String.Slice
|
||||
namespace Pos
|
||||
|
||||
theorem get?_eq_dif {s : Slice} {p : s.Pos} : p.get? = if h : p = s.endPos then none else some (p.get h) :=
|
||||
(rfl)
|
||||
|
||||
theorem get?_eq_some_get {s : Slice} {p : s.Pos} (h : p ≠ s.endPos) : p.get? = some (p.get h) := by
|
||||
simp [Pos.get?, h]
|
||||
|
||||
@[simp]
|
||||
theorem get?_eq_none_iff {s : Slice} {p : s.Pos} : p.get? = none ↔ p = s.endPos := by
|
||||
simp [Pos.get?]
|
||||
|
||||
theorem get?_eq_none {s : Slice} {p : s.Pos} (h : p = s.endPos) : p.get? = none :=
|
||||
get?_eq_none_iff.2 h
|
||||
|
||||
@[simp]
|
||||
theorem get?_endPos {s : Slice} : s.endPos.get? = none := by
|
||||
simp
|
||||
|
||||
end Pos
|
||||
|
||||
end Slice
|
||||
|
||||
namespace Pos
|
||||
|
||||
theorem get?_toSlice {s : String} {p : s.Pos} : p.toSlice.get? = p.get? :=
|
||||
(rfl)
|
||||
|
||||
theorem get?_eq_dif {s : String} {p : s.Pos} : p.get? = if h : p = s.endPos then none else some (p.get h) := by
|
||||
simp [← get?_toSlice, Slice.Pos.get?_eq_dif]
|
||||
|
||||
theorem get?_eq_some_get {s : String} {p : s.Pos} (h : p ≠ s.endPos) : p.get? = some (p.get h) := by
|
||||
simpa [← get?_toSlice] using Slice.Pos.get?_eq_some_get (by simpa)
|
||||
|
||||
@[simp]
|
||||
theorem get?_eq_none_iff {s : String} {p : s.Pos} : p.get? = none ↔ p = s.endPos := by
|
||||
simp [← get?_toSlice]
|
||||
|
||||
theorem get?_eq_none {s : String} {p : s.Pos} (h : p = s.endPos) : p.get? = none :=
|
||||
get?_eq_none_iff.2 h
|
||||
|
||||
@[simp]
|
||||
theorem get?_endPos {s : String} : s.endPos.get? = none := by
|
||||
simp
|
||||
|
||||
end Pos
|
||||
|
||||
namespace Slice
|
||||
|
||||
theorem front?_eq_get? {s : Slice} : s.front? = s.startPos.get? :=
|
||||
(rfl)
|
||||
|
||||
theorem front?_eq {s : Slice} : s.front? = s.copy.toList.head? := by
|
||||
simp only [front?_eq_get?, Pos.get?_eq_dif]
|
||||
split
|
||||
· simp_all [startPos_eq_endPos_iff, eq_comm (a := none)]
|
||||
· rename_i h
|
||||
obtain ⟨t, ht⟩ := s.splits_startPos.exists_eq_singleton_append h
|
||||
simp [ht]
|
||||
|
||||
@[simp]
|
||||
theorem front_eq {s : Slice} : s.front = s.front?.getD default := by
|
||||
simp [front]
|
||||
|
||||
theorem back?_eq_get? {s : Slice} : s.back? = s.endPos.prev?.bind Pos.get? :=
|
||||
(rfl)
|
||||
|
||||
theorem back?_eq {s : Slice} : s.back? = s.copy.toList.getLast? := by
|
||||
simp [back?_eq_get?, Pos.prev?_eq_dif]
|
||||
split
|
||||
· simp_all [startPos_eq_endPos_iff, eq_comm (a := s.endPos), eq_comm (a := none)]
|
||||
· rename_i h
|
||||
obtain ⟨t, ht⟩ := s.splits_endPos.exists_eq_append_singleton_of_ne_startPos h
|
||||
simp [ht, Pos.get?_eq_some_get]
|
||||
|
||||
@[simp]
|
||||
theorem back_eq {s : Slice} : s.back = s.back?.getD default := by
|
||||
simp [back]
|
||||
|
||||
end Slice
|
||||
|
||||
end String
|
||||
|
||||
@@ -17,6 +17,8 @@ import Init.Data.String.OrderInstances
|
||||
import Init.Data.Nat.Order
|
||||
import Init.Omega
|
||||
import Init.Data.String.Lemmas.FindPos
|
||||
import Init.Data.List.TakeDrop
|
||||
import Init.Data.List.Nat.TakeDrop
|
||||
|
||||
/-!
|
||||
# `Splits` predicates on `String.Pos` and `String.Slice.Pos`.
|
||||
@@ -365,7 +367,7 @@ theorem Slice.Pos.Splits.of_prev {s : Slice} {p : s.Pos} {hp}
|
||||
obtain ⟨rfl, rfl, rfl⟩ := by simpa using h.eq (splits_prev p hp)
|
||||
exact splits_prev_right p hp
|
||||
|
||||
theorem Slice.sliceTo_copy_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : String} :
|
||||
theorem Slice.copy_sliceTo_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : String} :
|
||||
(s.sliceTo p).copy = t₁ ↔ ∃ t₂, p.Splits t₁ t₂ := by
|
||||
refine ⟨?_, ?_⟩
|
||||
· rintro rfl
|
||||
@@ -373,13 +375,21 @@ theorem Slice.sliceTo_copy_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ :
|
||||
· rintro ⟨t₂, h⟩
|
||||
exact p.splits.eq_left h
|
||||
|
||||
theorem sliceTo_copy_eq_iff_exists_splits {s : String} {p : s.Pos} {t₁ : String} :
|
||||
(s.sliceTo p).copy = t₁ ↔ ∃ t₂, p.Splits t₁ t₂ := by
|
||||
theorem Slice.copy_sliceFrom_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₂ : String} :
|
||||
(s.sliceFrom p).copy = t₂ ↔ ∃ t₁, p.Splits t₁ t₂ := by
|
||||
refine ⟨?_, ?_⟩
|
||||
· rintro rfl
|
||||
exact ⟨_, p.splits⟩
|
||||
· rintro ⟨t₂, h⟩
|
||||
exact p.splits.eq_left h
|
||||
exact p.splits.eq_right h
|
||||
|
||||
theorem copy_sliceTo_eq_iff_exists_splits {s : String} {p : s.Pos} {t₁ : String} :
|
||||
(s.sliceTo p).copy = t₁ ↔ ∃ t₂, p.Splits t₁ t₂ := by
|
||||
simp [← Pos.splits_toSlice_iff, ← Slice.copy_sliceTo_eq_iff_exists_splits]
|
||||
|
||||
theorem copy_sliceFrom_eq_iff_exists_splits {s : String} {p : s.Pos} {t₂ : String} :
|
||||
(s.sliceFrom p).copy = t₂ ↔ ∃ t₁, p.Splits t₁ t₂ := by
|
||||
simp [← Pos.splits_toSlice_iff, ← Slice.copy_sliceFrom_eq_iff_exists_splits]
|
||||
|
||||
theorem Pos.Splits.offset_eq_decreaseBy {s : String} {p : s.Pos} (h : p.Splits t₁ t₂) :
|
||||
p.offset = s.rawEndPos.decreaseBy t₂.utf8ByteSize := by
|
||||
@@ -425,8 +435,7 @@ theorem Slice.splits_singleton_iff {s : Slice} {p : s.Pos} {c : Char} {t : Strin
|
||||
simp [startPos_ne_endPos_iff, ← copy_ne_empty_iff, h.eq_append]
|
||||
have spl : (s.startPos.next this).Splits (singleton c) t := by
|
||||
rw [← empty_append (s := singleton c)]
|
||||
apply Pos.Splits.next
|
||||
simp [h.eq_append]
|
||||
exact Pos.Splits.next (by simp [h.eq_append])
|
||||
refine ⟨this, ⟨h.pos_eq spl, ?_, h.eq_append⟩⟩
|
||||
rw [← empty_append (s := singleton c)] at spl
|
||||
exact spl.get_eq_of_singleton
|
||||
@@ -440,6 +449,27 @@ theorem splits_singleton_iff {s : String} {p : s.Pos} {c : Char} {t : String} :
|
||||
rw [← Pos.splits_toSlice_iff, Slice.splits_singleton_iff]
|
||||
simp [← Pos.ofToSlice_inj]
|
||||
|
||||
theorem Slice.splits_singleton_right_iff {s : Slice} {p : s.Pos} {c : Char} {t : String} :
|
||||
p.Splits t (singleton c) ↔
|
||||
∃ h, p = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c ∧ s.copy = t ++ singleton c := by
|
||||
refine ⟨fun h => ?_, ?_⟩
|
||||
· have : s.endPos ≠ s.startPos := by
|
||||
simp [ne_comm (a := s.endPos), startPos_ne_endPos_iff, ← copy_ne_empty_iff, h.eq_append]
|
||||
have spl : (s.endPos.prev this).Splits t (singleton c) := by
|
||||
rw [← append_empty (s := singleton c)]
|
||||
exact Pos.Splits.prev (by simp [h.eq_append])
|
||||
refine ⟨this, ⟨h.pos_eq spl, ?_, h.eq_append⟩⟩
|
||||
exact (h.eq_append ▸ Pos.next_prev (h := this) ▸ s.splits_endPos).get_eq_of_singleton
|
||||
· rintro ⟨h, ⟨rfl, rfl, h'⟩⟩
|
||||
rw [← String.append_empty (s := singleton _)]
|
||||
exact Pos.Splits.prev (by simp [h'])
|
||||
|
||||
theorem splits_singleton_right_iff {s : String} {p : s.Pos} {c : Char} {t : String} :
|
||||
p.Splits t (singleton c) ↔
|
||||
∃ h, p = s.endPos.prev h ∧ (s.endPos.prev h).get (by simp) = c ∧ s = t ++ singleton c := by
|
||||
rw [← Pos.splits_toSlice_iff, Slice.splits_singleton_right_iff]
|
||||
simp [← Pos.ofToSlice_inj, Pos.prev_toSlice]
|
||||
|
||||
theorem Slice.splits_next_startPos {s : Slice} {h : s.startPos ≠ s.endPos} :
|
||||
(s.startPos.next h).Splits
|
||||
(singleton (s.startPos.get h)) (s.sliceFrom (s.startPos.next h)).copy := by
|
||||
@@ -454,6 +484,20 @@ theorem splits_next_startPos {s : String} {h : s.startPos ≠ s.endPos} :
|
||||
rw [← Pos.splits_toSlice_iff]
|
||||
apply (Slice.splits_next_startPos).of_eq <;> simp [String.Pos.next_toSlice]
|
||||
|
||||
theorem Slice.splits_prev_endPos {s : Slice} {h : s.endPos ≠ s.startPos} :
|
||||
(s.endPos.prev h).Splits
|
||||
(s.sliceTo (s.endPos.prev h)).copy (singleton ((s.endPos.prev h).get (by simp))) := by
|
||||
rw [← String.append_empty (s := singleton _)]
|
||||
apply Slice.Pos.Splits.prev
|
||||
have := Slice.Pos.splits_prev_right s.endPos h
|
||||
rwa [copy_sliceFrom_endPos] at this
|
||||
|
||||
theorem splits_prev_endPos {s : String} {h : s.endPos ≠ s.startPos} :
|
||||
(s.endPos.prev h).Splits
|
||||
(s.sliceTo (s.endPos.prev h)).copy (singleton ((s.endPos.prev h).get (by simp))) := by
|
||||
rw [← Pos.splits_toSlice_iff]
|
||||
apply (Slice.splits_prev_endPos).of_eq <;> simp [String.Pos.prev_toSlice, h]
|
||||
|
||||
theorem Slice.Pos.Splits.toByteArray_eq_left {s : Slice} {p : s.Pos} {t₁ t₂ : String} (h : p.Splits t₁ t₂) :
|
||||
t₁.toByteArray = s.copy.toByteArray.extract 0 p.offset.byteIdx := by
|
||||
rw [h.eq_left p.splits]
|
||||
@@ -649,4 +693,51 @@ theorem Slice.splits_slice {s : Slice} {p₀ p₁ : s.Pos} (h) (p : (s.slice p
|
||||
p.Splits (s.slice p₀ (Pos.ofSlice p) Pos.le_ofSlice).copy (s.slice (Pos.ofSlice p) p₁ Pos.ofSlice_le).copy := by
|
||||
simpa using p.splits
|
||||
|
||||
theorem Slice.Pos.Splits.nextn {s : Slice} {t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
|
||||
(p.nextn n).Splits (t₁ ++ String.ofList (t₂.toList.take n)) (String.ofList (t₂.toList.drop n)) := by
|
||||
induction n generalizing p t₁ t₂ with
|
||||
| zero => simpa
|
||||
| succ n ih =>
|
||||
rw [Pos.nextn_add_one]
|
||||
split
|
||||
· simp_all
|
||||
· obtain ⟨t₂, rfl⟩ := h.exists_eq_singleton_append ‹_›
|
||||
simpa [← append_assoc] using ih h.next
|
||||
|
||||
theorem Slice.splits_nextn_startPos (s : Slice) (n : Nat) :
|
||||
(s.startPos.nextn n).Splits (String.ofList (s.copy.toList.take n)) (String.ofList (s.copy.toList.drop n)) := by
|
||||
simpa using s.splits_startPos.nextn n
|
||||
|
||||
theorem Pos.Splits.nextn {s t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (i : Nat) :
|
||||
(p.nextn i).Splits (t₁ ++ String.ofList (t₂.toList.take i)) (String.ofList (t₂.toList.drop i)) := by
|
||||
simpa [← splits_toSlice_iff, toSlice_nextn] using h.toSlice.nextn i
|
||||
|
||||
theorem splits_nextn_startPos (s : String) (n : Nat) :
|
||||
(s.startPos.nextn n).Splits (String.ofList (s.toList.take n)) (String.ofList (s.toList.drop n)) := by
|
||||
simpa using s.splits_startPos.nextn n
|
||||
|
||||
theorem Slice.Pos.Splits.prevn {s : Slice} {t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
|
||||
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.length - n))) (String.ofList (t₁.toList.drop (t₁.length - n)) ++ t₂) := by
|
||||
induction n generalizing p t₁ t₂ with
|
||||
| zero => simpa [← String.length_toList]
|
||||
| succ n ih =>
|
||||
rw [Pos.prevn_add_one]
|
||||
split
|
||||
· simp_all
|
||||
· obtain ⟨t₂, rfl⟩ := h.exists_eq_append_singleton_of_ne_startPos ‹_›
|
||||
simpa [Nat.add_sub_add_right, List.take_append, List.drop_append, ← append_assoc] using ih h.prev
|
||||
|
||||
theorem Slice.splits_prevn_endPos (s : Slice) (n : Nat) :
|
||||
(s.endPos.prevn n).Splits (String.ofList (s.copy.toList.take (s.copy.length - n)))
|
||||
(String.ofList (s.copy.toList.drop (s.copy.length - n))) := by
|
||||
simpa using s.splits_endPos.prevn n
|
||||
|
||||
theorem Pos.Splits.prevn {s t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
|
||||
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.length - n))) (String.ofList (t₁.toList.drop (t₁.length - n)) ++ t₂) := by
|
||||
simpa [← splits_toSlice_iff, toSlice_prevn] using h.toSlice.prevn n
|
||||
|
||||
theorem splits_prevn_endPos (s : String) (n : Nat) :
|
||||
(s.endPos.prevn n).Splits (String.ofList (s.toList.take (s.length - n))) (String.ofList (s.toList.drop (s.length - n))) := by
|
||||
simpa using s.splits_endPos.prevn n
|
||||
|
||||
end String
|
||||
|
||||
86
src/Init/Data/String/Lemmas/TakeDrop.lean
Normal file
86
src/Init/Data/String/Lemmas/TakeDrop.lean
Normal file
@@ -0,0 +1,86 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Author: Julia Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.TakeDrop
|
||||
import all Init.Data.String.Slice
|
||||
import all Init.Data.String.TakeDrop
|
||||
import Init.Data.String.Lemmas.Splits
|
||||
|
||||
public section
|
||||
|
||||
namespace String
|
||||
|
||||
namespace Slice
|
||||
|
||||
theorem drop_eq_sliceFrom {s : Slice} {n : Nat} : s.drop n = s.sliceFrom (s.startPos.nextn n) :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_drop {s : Slice} {n : Nat} : (s.drop n).copy.toList = s.copy.toList.drop n := by
|
||||
simp [drop_eq_sliceFrom, (s.splits_nextn_startPos n).copy_sliceFrom_eq]
|
||||
|
||||
theorem dropEnd_eq_sliceTo {s : Slice} {n : Nat} : s.dropEnd n = s.sliceTo (s.endPos.prevn n) :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_dropEnd {s : Slice} {n : Nat} :
|
||||
(s.dropEnd n).copy.toList = s.copy.toList.take (s.copy.length - n) := by
|
||||
simp [dropEnd_eq_sliceTo, (s.splits_prevn_endPos n).copy_sliceTo_eq]
|
||||
|
||||
theorem take_eq_sliceTo {s : Slice} {n : Nat} : s.take n = s.sliceTo (s.startPos.nextn n) :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_take {s : Slice} {n : Nat} : (s.take n).copy.toList = s.copy.toList.take n := by
|
||||
simp [take_eq_sliceTo, (s.splits_nextn_startPos n).copy_sliceTo_eq]
|
||||
|
||||
theorem takeEnd_eq_sliceFrom {s : Slice} {n : Nat} : s.takeEnd n = s.sliceFrom (s.endPos.prevn n) :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_takeEnd {s : Slice} {n : Nat} :
|
||||
(s.takeEnd n).copy.toList = s.copy.toList.drop (s.copy.length - n) := by
|
||||
simp [takeEnd_eq_sliceFrom, (s.splits_prevn_endPos n).copy_sliceFrom_eq]
|
||||
|
||||
end Slice
|
||||
|
||||
@[simp]
|
||||
theorem drop_toSlice {s : String} {n : Nat} : s.toSlice.drop n = s.drop n :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_drop {s : String} {n : Nat} : (s.drop n).copy.toList = s.toList.drop n := by
|
||||
simp [← drop_toSlice]
|
||||
|
||||
@[simp]
|
||||
theorem dropEnd_toSlice {s : String} {n : Nat} : s.toSlice.dropEnd n = s.dropEnd n :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_dropEnd {s : String} {n : Nat} :
|
||||
(s.dropEnd n).copy.toList = s.toList.take (s.length - n) := by
|
||||
simp [← dropEnd_toSlice]
|
||||
|
||||
@[simp]
|
||||
theorem take_toSlice {s : String} {n : Nat} : s.toSlice.take n = s.take n :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_take {s : String} {n : Nat} : (s.take n).copy.toList = s.toList.take n := by
|
||||
simp [← take_toSlice]
|
||||
|
||||
@[simp]
|
||||
theorem takeEnd_toSlice {s : String} {n : Nat} : s.toSlice.takeEnd n = s.takeEnd n :=
|
||||
(rfl)
|
||||
|
||||
@[simp]
|
||||
theorem toList_copy_takeEnd {s : String} {n : Nat} :
|
||||
(s.takeEnd n).copy.toList = s.toList.drop (s.length - n) := by
|
||||
simp [← takeEnd_toSlice]
|
||||
|
||||
end String
|
||||
@@ -117,7 +117,7 @@ class ForwardPattern {ρ : Type} (pat : ρ) where
|
||||
-/
|
||||
startsWith : (s : Slice) → Bool := fun s => (skipPrefix? s).isSome
|
||||
|
||||
@[deprecated ForwardPattern.dropPrefix? (since := "2026-03-19")]
|
||||
@[deprecated ForwardPattern.skipPrefix? (since := "2026-03-19")]
|
||||
def ForwardPattern.dropPrefix? {ρ : Type} (pat : ρ) [ForwardPattern pat] (s : Slice) : Option s.Pos :=
|
||||
ForwardPattern.skipPrefix? pat s
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ instance {c : Char} : LawfulBackwardPattern c where
|
||||
skipSuffixOfNonempty?_eq h := LawfulBackwardPattern.skipSuffixOfNonempty?_eq (pat := (· == c)) h
|
||||
endsWith_eq s := LawfulBackwardPattern.endsWith_eq (pat := (· == c)) s
|
||||
|
||||
instance {c : Char} : ToBackwardSearcher c (ToBackwardSearcher.DefaultBackwardSearcher c) :=
|
||||
.defaultImplementation
|
||||
instance {c : Char} : ToBackwardSearcher c (ToBackwardSearcher.DefaultBackwardSearcher (· == c)) where
|
||||
toSearcher s := ToBackwardSearcher.toSearcher (· == c) s
|
||||
|
||||
end Char
|
||||
|
||||
|
||||
@@ -139,8 +139,9 @@ instance {p : Char → Prop} [DecidablePred p] : LawfulBackwardPattern p where
|
||||
skipSuffixOfNonempty?_eq h := LawfulBackwardPattern.skipSuffixOfNonempty?_eq (pat := (decide <| p ·)) h
|
||||
endsWith_eq s := LawfulBackwardPattern.endsWith_eq (pat := (decide <| p ·)) s
|
||||
|
||||
instance {p : Char → Prop} [DecidablePred p] : ToBackwardSearcher p (ToBackwardSearcher.DefaultBackwardSearcher p) :=
|
||||
.defaultImplementation
|
||||
instance {p : Char → Prop} [DecidablePred p] :
|
||||
ToBackwardSearcher p (ToBackwardSearcher.DefaultBackwardSearcher (decide <| p ·)) where
|
||||
toSearcher s := ToBackwardSearcher.toSearcher (decide <| p ·) s
|
||||
|
||||
end Decidable
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ public import Init.Data.Ord.Basic
|
||||
public import Init.Data.Iterators.Combinators.FilterMap
|
||||
public import Init.Data.String.ToSlice
|
||||
public import Init.Data.String.Subslice
|
||||
public import Init.Data.String.Iter
|
||||
public import Init.Data.String.Iter.Basic
|
||||
public import Init.Data.String.Iterate
|
||||
import Init.Data.Iterators.Consumers.Collect
|
||||
import Init.Data.Iterators.Consumers.Loop
|
||||
@@ -84,10 +84,11 @@ instance : ToString String.Slice where
|
||||
theorem toStringToString_eq : ToString.toString = String.Slice.copy := (rfl)
|
||||
|
||||
@[extern "lean_slice_hash"]
|
||||
opaque hash (s : @& Slice) : UInt64
|
||||
protected def hash (s : @& Slice) : UInt64 :=
|
||||
String.hash s.copy
|
||||
|
||||
instance : Hashable Slice where
|
||||
hash := hash
|
||||
hash := Slice.hash
|
||||
|
||||
instance : LT Slice where
|
||||
lt x y := x.copy < y.copy
|
||||
@@ -1151,6 +1152,19 @@ where go (acc : String) (s : Slice) : List Slice → String
|
||||
| a :: as => go (acc ++ s ++ a) s as
|
||||
| [] => acc
|
||||
|
||||
/--
|
||||
Appends all the slices in a list of slices, in order.
|
||||
|
||||
Use {name}`String.Slice.intercalate` to place a separator string between the strings in a list.
|
||||
|
||||
Examples:
|
||||
* {lean}`String.Slice.join ["gr", "ee", "n"] = "green"`
|
||||
* {lean}`String.Slice.join ["b", "", "l", "", "ue"] = "blue"`
|
||||
* {lean}`String.Slice.join [] = ""`
|
||||
-/
|
||||
def join (l : List String.Slice) : String :=
|
||||
l.foldl (fun (r : String) (s : String.Slice) => r ++ s) ""
|
||||
|
||||
/--
|
||||
Converts a string to the Lean compiler's representation of names. The resulting name is
|
||||
hierarchical, and the string is split at the dots ({lean}`'.'`).
|
||||
|
||||
@@ -107,6 +107,9 @@ syntax (name := showLocalThms) "show_local_thms" : grind
|
||||
-/
|
||||
syntax (name := showTerm) "show_term " grindSeq : grind
|
||||
|
||||
/-- Shows the pending goals. -/
|
||||
syntax (name := showGoals) "show_goals" : grind
|
||||
|
||||
declare_syntax_cat grind_ref (behavior := both)
|
||||
|
||||
syntax:max anchor : grind_ref
|
||||
@@ -315,5 +318,8 @@ Only available in `sym =>` mode.
|
||||
-/
|
||||
syntax (name := symSimp) "simp" (ppSpace colGt ident)? (" [" ident,* "]")? : grind
|
||||
|
||||
/-- `exact e` closes the main goal if its target type matches that of `e`. -/
|
||||
macro "exact " e:term : grind => `(grind| tactic => exact $e:term)
|
||||
|
||||
end Grind
|
||||
end Lean.Parser.Tactic
|
||||
|
||||
@@ -185,15 +185,21 @@ example : foo.default = (default, default) :=
|
||||
abbrev inferInstance {α : Sort u} [i : α] : α := i
|
||||
|
||||
set_option checkBinderAnnotations false in
|
||||
/-- `inferInstanceAs α` synthesizes an instance of type `α` and normalizes it to
|
||||
"instance normal form": the result is a constructor application whose sub-instance fields
|
||||
are canonical instances and whose types match `α` exactly. This is useful when `α` is
|
||||
definitionally equal to some `α'` for which instances are registered, as it prevents
|
||||
leaking the definition's RHS at lower transparencies. See `Lean.Meta.InstanceNormalForm`
|
||||
for details. Example:
|
||||
/-- `inferInstanceAs α` synthesizes an instance of type `α`, transporting it from a
|
||||
definitionally equal type if necessary. This is useful when `α` is definitionally equal to
|
||||
some `α'` for which instances are registered, as it prevents leaking the definition's RHS
|
||||
at lower transparencies.
|
||||
|
||||
`inferInstanceAs` requires an expected type from context. If you just need to synthesize an
|
||||
instance without transporting between types, use `inferInstance` instead.
|
||||
|
||||
Example:
|
||||
```
|
||||
#check inferInstanceAs (Inhabited Nat) -- Inhabited Nat
|
||||
def D := Nat
|
||||
instance : Inhabited D := inferInstanceAs (Inhabited Nat)
|
||||
```
|
||||
|
||||
See `Lean.Meta.WrapInstance` for details.
|
||||
-/
|
||||
abbrev «inferInstanceAs» (α : Sort u) [i : α] : α := i
|
||||
|
||||
@@ -3261,7 +3267,7 @@ Version of `Array.get!Internal` that does not increment the reference count of i
|
||||
This is only intended for direct use by the compiler.
|
||||
-/
|
||||
@[extern "lean_array_get_borrowed"]
|
||||
unsafe opaque Array.get!InternalBorrowed {α : Type u} [Inhabited α] (a : @& Array α) (i : @& Nat) : α
|
||||
unsafe opaque Array.get!InternalBorrowed {α : Type u} [@&Inhabited α] (a : @& Array α) (i : @& Nat) : α
|
||||
|
||||
/--
|
||||
Use the indexing notation `a[i]!` instead.
|
||||
@@ -3269,7 +3275,7 @@ Use the indexing notation `a[i]!` instead.
|
||||
Access an element from an array, or panic if the index is out of bounds.
|
||||
-/
|
||||
@[extern "lean_array_get"]
|
||||
def Array.get!Internal {α : Type u} [Inhabited α] (a : @& Array α) (i : @& Nat) : α :=
|
||||
def Array.get!Internal {α : Type u} [@&Inhabited α] (a : @& Array α) (i : @& Nat) : α :=
|
||||
Array.getD a i default
|
||||
|
||||
/--
|
||||
@@ -3648,8 +3654,8 @@ will prevent the actual monad from being "copied" to the code being specialized.
|
||||
When we reimplement the specializer, we may consider copying `inst` if it also
|
||||
occurs outside binders or if it is an instance.
|
||||
-/
|
||||
@[never_extract, extern "lean_panic_fn"]
|
||||
def panicCore {α : Sort u} [Inhabited α] (msg : String) : α := default
|
||||
@[never_extract, extern "lean_panic_fn_borrowed"]
|
||||
def panicCore {α : Sort u} [@&Inhabited α] (msg : String) : α := default
|
||||
|
||||
/--
|
||||
`(panic "msg" : α)` has a built-in implementation which prints `msg` to
|
||||
@@ -4082,7 +4088,7 @@ Actions in the resulting monad are functions that take the local value as a para
|
||||
ordinary actions in `m`.
|
||||
-/
|
||||
def ReaderT (ρ : Type u) (m : Type u → Type v) (α : Type u) : Type (max u v) :=
|
||||
ρ → m α
|
||||
(a : @&ρ) → m α
|
||||
|
||||
/--
|
||||
Interpret `ρ → m α` as an element of `ReaderT ρ m α`.
|
||||
|
||||
@@ -49,6 +49,14 @@ syntax (name := ground) "ground" : sym_simproc
|
||||
/-- Simplify telescope binders but not the final body. -/
|
||||
syntax (name := telescope) "telescope" : sym_simproc
|
||||
|
||||
/-- Simplify control-flow expressions (`if-then-else`, `match`, `cond`, `dite`).
|
||||
Visits only conditions and discriminants. Intended as a `pre` simproc. -/
|
||||
syntax (name := control) "control" : sym_simproc
|
||||
|
||||
/-- Simplify arrow telescopes (`p₁ → p₂ → ... → q`) without entering binders.
|
||||
Simplifies each `pᵢ` and `q` individually. Intended as a `pre` simproc. -/
|
||||
syntax (name := arrowTelescope) "arrow_telescope" : sym_simproc
|
||||
|
||||
/-- Rewrite using a named theorem set. Optionally specify a discharger for conditional rewrites. -/
|
||||
syntax (name := rewriteSet) "rewrite" ident (" with " sym_discharger)? : sym_simproc
|
||||
|
||||
|
||||
@@ -2259,42 +2259,6 @@ with grind
|
||||
```
|
||||
This is more convenient than the equivalent `· by rename_i _ acc _; exact I1 acc`.
|
||||
|
||||
### Witnesses
|
||||
|
||||
When a specification has a parameter whose type is tagged with `@[mvcgen_witness_type]`, `mvcgen`
|
||||
classifies the corresponding goal as a *witness* rather than a verification condition.
|
||||
Witnesses are concrete values that the user must provide (inspired by zero-knowledge proofs),
|
||||
as opposed to invariants (predicates maintained across loop iterations) or verification conditions
|
||||
(propositions to prove).
|
||||
|
||||
Witness goals are labelled `witness1`, `witness2`, etc. and can be provided in a `witnesses` section
|
||||
that appears before the `invariants` section:
|
||||
```
|
||||
mvcgen [...] witnesses
|
||||
· W1
|
||||
· W2
|
||||
invariants
|
||||
· I1
|
||||
with grind
|
||||
```
|
||||
Like invariants, witnesses support case label syntax:
|
||||
```
|
||||
mvcgen [...] witnesses
|
||||
| witness1 => W1
|
||||
```
|
||||
|
||||
See the `@[mvcgen_witness_type]` attribute for how to register custom witness types.
|
||||
|
||||
### Invariant and witness type attributes
|
||||
|
||||
The `@[mvcgen_invariant_type]` and `@[mvcgen_witness_type]` tag attributes control how `mvcgen`
|
||||
classifies subgoals:
|
||||
* A goal whose type is an application of a type tagged with `@[mvcgen_invariant_type]` is classified
|
||||
as an invariant (`inv<n>`).
|
||||
* A goal whose type is an application of a type tagged with `@[mvcgen_witness_type]` is classified
|
||||
as a witness (`witness<n>`).
|
||||
* All other goals are classified as verification conditions (`vc<n>`).
|
||||
|
||||
### Invariant suggestions
|
||||
|
||||
`mvcgen` will suggest invariants for you if you use the `invariants?` keyword.
|
||||
|
||||
@@ -21,6 +21,7 @@ public section
|
||||
|
||||
namespace Lean.IR
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_add_extern]
|
||||
def addExtern (declName : Name) (externAttrData : ExternAttrData) : CoreM Unit := do
|
||||
if !isPrivateName declName then
|
||||
|
||||
@@ -10,6 +10,7 @@ public import Lean.Compiler.IR.Format
|
||||
public import Lean.Compiler.ExportAttr
|
||||
public import Lean.Compiler.LCNF.PublicDeclsExt
|
||||
import Lean.Compiler.InitAttr
|
||||
import all Lean.Compiler.ModPkgExt
|
||||
import Init.Data.Format.Macro
|
||||
import Lean.Compiler.LCNF.Basic
|
||||
|
||||
@@ -129,8 +130,14 @@ private def exportIREntries (env : Environment) : Array (Name × Array EnvExtens
|
||||
-- safety: cast to erased type
|
||||
let initDecls : Array EnvExtensionEntry := unsafe unsafeCast initDecls
|
||||
|
||||
-- needed during initialization via interpreter
|
||||
let modPkg : Array (Option PkgId) := modPkgExt.exportEntriesFn env (modPkgExt.getState env) .private
|
||||
-- safety: cast to erased type
|
||||
let modPkg : Array EnvExtensionEntry := unsafe unsafeCast modPkg
|
||||
|
||||
#[(declMapExt.name, irEntries),
|
||||
(Lean.regularInitAttr.ext.name, initDecls)]
|
||||
(Lean.regularInitAttr.ext.name, initDecls),
|
||||
(modPkgExt.name, modPkg)]
|
||||
|
||||
def findEnvDecl (env : Environment) (declName : Name) : Option Decl :=
|
||||
Compiler.LCNF.findExtEntry? env declMapExt declName findAtSorted? (·.2.find?)
|
||||
|
||||
@@ -342,6 +342,11 @@ def LetValue.toExpr (e : LetValue pu) : Expr :=
|
||||
| .unbox var _ => mkApp (.const `unbox []) (.fvar var)
|
||||
| .isShared fvarId _ => mkApp (.const `isShared []) (.fvar fvarId)
|
||||
|
||||
def LetValue.isPersistent (val : LetValue .impure) : Bool :=
|
||||
match val with
|
||||
| .fap _ xs => xs.isEmpty -- all global constants are persistent
|
||||
| _ => false
|
||||
|
||||
structure LetDecl (pu : Purity) where
|
||||
fvarId : FVarId
|
||||
binderName : Name
|
||||
|
||||
@@ -97,6 +97,7 @@ partial def collectCode (code : Code .impure) : M Unit := do
|
||||
match decl.value with
|
||||
| .oproj _ parent =>
|
||||
addDerivedValue parent decl.fvarId
|
||||
-- Keep in sync with PropagateBorrow, InferBorrow
|
||||
| .fap ``Array.getInternal args =>
|
||||
if let .fvar parent := args[1]! then
|
||||
addDerivedValue parent decl.fvarId
|
||||
@@ -234,11 +235,6 @@ def withParams (ps : Array (Param .impure)) (x : RcM α) : RcM α := do
|
||||
{ ctx with idx := ctx.idx + 1, varMap }
|
||||
withReader update x
|
||||
|
||||
def LetValue.isPersistent (val : LetValue .impure) : Bool :=
|
||||
match val with
|
||||
| .fap _ xs => xs.isEmpty -- all global constants are persistent
|
||||
| _ => false
|
||||
|
||||
@[inline]
|
||||
def withLetDecl (decl : LetDecl .impure) (x : RcM α) : RcM α := do
|
||||
let update := fun ctx =>
|
||||
|
||||
@@ -213,6 +213,8 @@ inductive OwnReason where
|
||||
| jpArgPropagation (jpFVar : FVarId)
|
||||
/-- Tail call preservation at a join point jump. -/
|
||||
| jpTailCallPreservation (jpFVar : FVarId)
|
||||
/-- Annotated as an owned parameter (currently only triggerable through `@[export]`)-/
|
||||
| ownedAnnotation
|
||||
|
||||
def OwnReason.toString (reason : OwnReason) : CompilerM String := do
|
||||
PP.run do
|
||||
@@ -229,6 +231,7 @@ def OwnReason.toString (reason : OwnReason) : CompilerM String := do
|
||||
| .tailCallPreservation funcName => return s!"tail call preservation of {funcName}"
|
||||
| .jpArgPropagation jpFVar => return s!"backward propagation from JP {← PP.ppFVar jpFVar}"
|
||||
| .jpTailCallPreservation jpFVar => return s!"JP tail call preservation {← PP.ppFVar jpFVar}"
|
||||
| .ownedAnnotation => return s!"Annotated as owned"
|
||||
|
||||
/--
|
||||
Determine whether an `OwnReason` is necessary for correctness (forced) or just an optimization
|
||||
@@ -240,13 +243,19 @@ def OwnReason.isForced (reason : OwnReason) : Bool :=
|
||||
-- All of these reasons propagate through ABI decisions and can thus safely be ignored as they
|
||||
-- will be accounted for by the reference counting pass.
|
||||
| .constructorArg .. | .functionCallArg .. | .fvarCall .. | .partialApplication ..
|
||||
| .jpArgPropagation .. => false
|
||||
| .jpArgPropagation ..
|
||||
-- forward propagation can never affect a user-annotated parameter
|
||||
| .forwardProjectionProp ..
|
||||
-- backward propagation on a user-annotated parameter is only necessary if the projected value
|
||||
-- directly flows into a reset-reuse. However, the borrow annotation propagator ensures this
|
||||
-- situation never arises
|
||||
| .backwardProjectionProp .. => false
|
||||
-- Results of functions and constructors are naturally owned.
|
||||
| .constructorResult .. | .functionCallResult ..
|
||||
-- We cannot pass borrowed values to reset or have borrow annotations destroy tail calls for
|
||||
-- correctness reasons.
|
||||
| .resetReuse .. | .tailCallPreservation .. | .jpTailCallPreservation ..
|
||||
| .forwardProjectionProp .. | .backwardProjectionProp .. => true
|
||||
| .ownedAnnotation => true
|
||||
|
||||
/--
|
||||
Infer the borrowing annotations in a SCC through dataflow analysis.
|
||||
@@ -256,10 +265,19 @@ partial def infer (decls : Array (Decl .impure)) : CompilerM ParamMap := do
|
||||
return map.paramMap
|
||||
where
|
||||
go : InferM Unit := do
|
||||
for (_, params) in (← get).paramMap.map do
|
||||
for param in params do
|
||||
if !param.borrow && param.type.isPossibleRef then
|
||||
-- if the param already disqualifies as borrow now this is because of an annotation
|
||||
ownFVar param.fvarId .ownedAnnotation
|
||||
modify fun s => { s with modified := false }
|
||||
loop
|
||||
|
||||
loop : InferM Unit := do
|
||||
step
|
||||
if (← get).modified then
|
||||
modify fun s => { s with modified := false }
|
||||
go
|
||||
loop
|
||||
else
|
||||
return ()
|
||||
|
||||
@@ -361,10 +379,23 @@ where
|
||||
| .oproj _ x _ =>
|
||||
if ← isOwned x then ownFVar z (.forwardProjectionProp z)
|
||||
if ← isOwned z then ownFVar x (.backwardProjectionProp z)
|
||||
-- Keep in sync with ExplicitRC, PropagateBorrow
|
||||
| .fap ``Array.getInternal args =>
|
||||
if let .fvar parent := args[1]! then
|
||||
if ← isOwned parent then ownFVar z (.forwardProjectionProp z)
|
||||
| .fap ``Array.get!Internal args =>
|
||||
if let .fvar parent := args[2]! then
|
||||
if ← isOwned parent then ownFVar z (.forwardProjectionProp z)
|
||||
| .fap ``Array.uget args =>
|
||||
if let .fvar parent := args[1]! then
|
||||
if ← isOwned parent then ownFVar z (.forwardProjectionProp z)
|
||||
| .fap f args =>
|
||||
let ps ← getParamInfo (.decl f)
|
||||
ownFVar z (.functionCallResult z)
|
||||
ownArgsUsingParams args ps (.functionCallArg z)
|
||||
-- Constants remain alive at least until the end of execution and can thus effectively be seen
|
||||
-- as a "borrowed" read.
|
||||
if args.size > 0 then
|
||||
let ps ← getParamInfo (.decl f)
|
||||
ownFVar z (.functionCallResult z)
|
||||
ownArgsUsingParams args ps (.functionCallArg z)
|
||||
| .fvar x args =>
|
||||
ownFVar z (.functionCallResult z); ownFVar x (.fvarCall z); ownArgs (.fvarCall z) args
|
||||
| .pap _ args => ownFVar z (.functionCallResult z); ownArgs (.partialApplication z) args
|
||||
|
||||
@@ -21,6 +21,6 @@ def getOtherDeclType (declName : Name) (us : List Level := []) : CompilerM Expr
|
||||
match (← getPhase) with
|
||||
| .base => getOtherDeclBaseType declName us
|
||||
| .mono => getOtherDeclMonoType declName
|
||||
| .impure => getOtherDeclImpureType declName
|
||||
| .impure => throwError "getOtherDeclType unsupported for impure"
|
||||
|
||||
end Lean.Compiler.LCNF
|
||||
|
||||
@@ -154,16 +154,18 @@ mutual
|
||||
return f!"oset {← ppFVar fvarId} [{i}] := {← ppArg y};" ++ .line ++ (← ppCode k)
|
||||
| .setTag fvarId cidx k _ =>
|
||||
return f!"setTag {← ppFVar fvarId} := {cidx};" ++ .line ++ (← ppCode k)
|
||||
| .inc fvarId n _ _ k _ =>
|
||||
| .inc fvarId n check persistent k _ =>
|
||||
let ann := (if persistent then "[persistent]" else "") ++ (if !check then "[ref]" else "")
|
||||
if n != 1 then
|
||||
return f!"inc[{n}] {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
return f!"inc[{n}]{ann} {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
else
|
||||
return f!"inc {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
| .dec fvarId n _ _ k _ =>
|
||||
return f!"inc{ann} {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
| .dec fvarId n check persistent k _ =>
|
||||
let ann := (if persistent then "[persistent]" else "") ++ (if !check then "[ref]" else "")
|
||||
if n != 1 then
|
||||
return f!"dec[{n}] {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
return f!"dec[{n}]{ann} {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
else
|
||||
return f!"dec {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
return f!"dec{ann} {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
| .del fvarId k _ =>
|
||||
return f!"del {← ppFVar fvarId};" ++ .line ++ (← ppCode k)
|
||||
|
||||
|
||||
@@ -105,10 +105,26 @@ where
|
||||
|
||||
collectLetValue (z : FVarId) (v : LetValue .impure) : InferM Unit := do
|
||||
match v with
|
||||
| .oproj _ x _ =>
|
||||
let xVal ← getOwnedness x
|
||||
join z xVal
|
||||
| .ctor .. | .fap .. | .fvar .. | .pap .. | .sproj .. | .uproj .. | .erased .. | .lit .. =>
|
||||
| .oproj _ parent _ =>
|
||||
let parentVal ← getOwnedness parent
|
||||
join z parentVal
|
||||
-- Keep in sync with ExplicitRC, InferBorrow
|
||||
| .fap ``Array.getInternal args =>
|
||||
if let .fvar parent := args[1]! then
|
||||
let parentVal ← getOwnedness parent
|
||||
join z parentVal
|
||||
| .fap ``Array.get!Internal args =>
|
||||
if let .fvar parent := args[2]! then
|
||||
let parentVal ← getOwnedness parent
|
||||
join z parentVal
|
||||
| .fap ``Array.uget args =>
|
||||
if let .fvar parent := args[1]! then
|
||||
let parentVal ← getOwnedness parent
|
||||
join z parentVal
|
||||
| .fap _ args =>
|
||||
let value := if args.isEmpty then .borrow else .own
|
||||
join z value
|
||||
| .ctor .. | .fvar .. | .pap .. | .sproj .. | .uproj .. | .erased .. | .lit .. =>
|
||||
join z .own
|
||||
| _ => unreachable!
|
||||
|
||||
|
||||
@@ -20,8 +20,10 @@ inductive SpecParamInfo where
|
||||
/--
|
||||
A parameter that is an type class instance (or an arrow that produces a type class instance),
|
||||
and is fixed in recursive declarations. By default, Lean always specializes this kind of argument.
|
||||
If the `weak` parameter is set we only specialize for this parameter iff another parameter causes
|
||||
specialization as well.
|
||||
-/
|
||||
| fixedInst
|
||||
| fixedInst (weak : Bool)
|
||||
/--
|
||||
A parameter that is a function and is fixed in recursive declarations. If the user tags a declaration
|
||||
with `@[specialize]` without specifying which arguments should be specialized, Lean will specialize
|
||||
@@ -49,14 +51,15 @@ namespace SpecParamInfo
|
||||
|
||||
@[inline]
|
||||
def causesSpecialization : SpecParamInfo → Bool
|
||||
| .fixedInst | .fixedHO | .user => true
|
||||
| .fixedNeutral | .other => false
|
||||
| .fixedInst false | .fixedHO | .user => true
|
||||
| .fixedInst true | .fixedNeutral | .other => false
|
||||
|
||||
end SpecParamInfo
|
||||
|
||||
instance : ToMessageData SpecParamInfo where
|
||||
toMessageData
|
||||
| .fixedInst => "I"
|
||||
| .fixedInst false => "I"
|
||||
| .fixedInst true => "W"
|
||||
| .fixedHO => "H"
|
||||
| .fixedNeutral => "N"
|
||||
| .user => "U"
|
||||
@@ -130,6 +133,18 @@ private def isNoSpecType (env : Environment) (type : Expr) : Bool :=
|
||||
else
|
||||
false
|
||||
|
||||
/--
|
||||
Return `true` if `type` is a type tagged with `@[weak_specialize]` or an arrow that produces this kind of type.
|
||||
-/
|
||||
private def isWeakSpecType (env : Environment) (type : Expr) : Bool :=
|
||||
match type with
|
||||
| .forallE _ _ b _ => isWeakSpecType env b
|
||||
| _ =>
|
||||
if let .const declName _ := type.getAppFn then
|
||||
hasWeakSpecializeAttribute env declName
|
||||
else
|
||||
false
|
||||
|
||||
/-!
|
||||
*Note*: `fixedNeutral` must have forward dependencies.
|
||||
|
||||
@@ -160,7 +175,7 @@ See comment at `.fixedNeutral`.
|
||||
private def hasFwdDeps (decl : Decl .pure) (paramsInfo : Array SpecParamInfo) (j : Nat) : Bool := Id.run do
|
||||
let param := decl.params[j]!
|
||||
for h : k in (j+1)...decl.params.size do
|
||||
if paramsInfo[k]!.causesSpecialization then
|
||||
if paramsInfo[k]!.causesSpecialization || paramsInfo[k]! matches .fixedInst .. then
|
||||
let param' := decl.params[k]
|
||||
if param'.type.containsFVar param.fvarId then
|
||||
return true
|
||||
@@ -199,7 +214,7 @@ def computeSpecEntries (decls : Array (Decl .pure)) (autoSpecialize : Name → O
|
||||
else if isTypeFormerType param.type then
|
||||
pure .fixedNeutral
|
||||
else if (← isArrowClass? param.type).isSome then
|
||||
pure .fixedInst
|
||||
pure (.fixedInst (weak := isWeakSpecType (← getEnv) param.type))
|
||||
/-
|
||||
Recall that if `specArgs? == some #[]`, then user annotated function with `@[specialize]`, but did not
|
||||
specify which arguments must be specialized besides instances. In this case, we try to specialize
|
||||
|
||||
@@ -209,7 +209,7 @@ def collect (paramsInfo : Array SpecParamInfo) (args : Array (Arg .pure)) :
|
||||
match paramInfo with
|
||||
| .other =>
|
||||
argMask := argMask.push none
|
||||
| .fixedNeutral | .user | .fixedInst | .fixedHO =>
|
||||
| .fixedNeutral | .user | .fixedInst .. | .fixedHO =>
|
||||
argMask := argMask.push (some arg)
|
||||
Closure.collectArg arg
|
||||
return argMask
|
||||
@@ -257,7 +257,8 @@ def shouldSpecialize (specEntry : SpecEntry) (args : Array (Arg .pure)) : Specia
|
||||
match paramInfo with
|
||||
| .other => pure ()
|
||||
| .fixedNeutral => pure () -- If we want to monomorphize types such as `Array`, we need to change here
|
||||
| .fixedInst | .user => if ← isGround arg then return true
|
||||
| .fixedInst true => pure () -- weak: don't trigger specialization on its own
|
||||
| .fixedInst false | .user => if ← isGround arg then return true
|
||||
| .fixedHO => if ← hoCheck arg then return true
|
||||
|
||||
return false
|
||||
@@ -509,7 +510,7 @@ def updateLocalSpecParamInfo : SpecializeM Unit := do
|
||||
for entry in infos do
|
||||
if let some mask := (← get).parentMasks[entry.declName]? then
|
||||
let maskInfo info :=
|
||||
mask.zipWith info (f := fun b i => if !b && i.causesSpecialization then .other else i)
|
||||
mask.zipWith info (f := fun b i => if !b && (i.causesSpecialization || i matches .fixedInst ..) then .other else i)
|
||||
let entry := { entry with paramsInfo := maskInfo entry.paramsInfo }
|
||||
modify fun s => {
|
||||
s with
|
||||
|
||||
@@ -240,12 +240,4 @@ where fillCache := do
|
||||
fieldInfo := fields
|
||||
}
|
||||
|
||||
public def getOtherDeclImpureType (declName : Name) : CoreM Expr := do
|
||||
match (← impureTypeExt.find? declName) with
|
||||
| some type => return type
|
||||
| none =>
|
||||
let type ← toImpureType (← getOtherDeclMonoType declName)
|
||||
monoTypeExt.insert declName type
|
||||
return type
|
||||
|
||||
end Lean.Compiler.LCNF
|
||||
|
||||
@@ -24,6 +24,17 @@ Marks a definition to never be specialized during code generation.
|
||||
builtin_initialize nospecializeAttr : TagAttribute ←
|
||||
registerTagAttribute `nospecialize "mark definition to never be specialized"
|
||||
|
||||
/--
|
||||
Marks a type for weak specialization: Parameters of this type are only specialized when
|
||||
another argument already triggers specialization. Unlike `@[nospecialize]`, if specialization
|
||||
happens for other reasons, parameters of this type will participate in the specialization
|
||||
rather than being ignored.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
builtin_initialize weakSpecializeAttr : TagAttribute ←
|
||||
registerTagAttribute `weak_specialize
|
||||
"mark type for weak specialization: instances are only specialized when another argument already triggers specialization"
|
||||
|
||||
private def elabSpecArgs (declName : Name) (args : Array Syntax) : MetaM (Array Nat) := do
|
||||
if args.isEmpty then return #[]
|
||||
let info ← getConstInfo declName
|
||||
@@ -82,4 +93,7 @@ def hasSpecializeAttribute (env : Environment) (declName : Name) : Bool :=
|
||||
def hasNospecializeAttribute (env : Environment) (declName : Name) : Bool :=
|
||||
nospecializeAttr.hasTag env declName
|
||||
|
||||
def hasWeakSpecializeAttribute (env : Environment) (declName : Name) : Bool :=
|
||||
weakSpecializeAttr.hasTag env declName
|
||||
|
||||
end Lean.Compiler
|
||||
|
||||
@@ -343,13 +343,13 @@ def instantiateTypeLevelParams (c : ConstantVal) (us : List Level) : CoreM Expr
|
||||
modifyInstLevelTypeCache fun s => s.insert c.name (us, r)
|
||||
return r
|
||||
|
||||
def instantiateValueLevelParams (c : ConstantInfo) (us : List Level) : CoreM Expr := do
|
||||
def instantiateValueLevelParams (c : ConstantInfo) (us : List Level) (allowOpaque := false) : CoreM Expr := do
|
||||
if let some (us', r) := (← get).cache.instLevelValue.find? c.name then
|
||||
if us == us' then
|
||||
return r
|
||||
unless c.hasValue do
|
||||
unless c.hasValue (allowOpaque := allowOpaque) do
|
||||
throwError "Not a definition or theorem: {.ofConstName c.name}"
|
||||
let r := c.instantiateValueLevelParams! us
|
||||
let r := c.instantiateValueLevelParams! us (allowOpaque := allowOpaque)
|
||||
modifyInstLevelValueCache fun s => s.insert c.name (us, r)
|
||||
return r
|
||||
|
||||
|
||||
@@ -14,29 +14,35 @@ public section
|
||||
|
||||
namespace Lean
|
||||
/--
|
||||
Reducibility hints are used in the convertibility checker.
|
||||
When trying to solve a constraint such a
|
||||
Reducibility hints guide the kernel's *lazy delta reduction* strategy. When the kernel encounters a
|
||||
definitional equality constraint
|
||||
|
||||
(f ...) =?= (g ...)
|
||||
|
||||
where f and g are definitions, the checker has to decide which one will be unfolded.
|
||||
If f (g) is opaque, then g (f) is unfolded if it is also not marked as opaque,
|
||||
Else if f (g) is abbrev, then f (g) is unfolded if g (f) is also not marked as abbrev,
|
||||
Else if f and g are regular, then we unfold the one with the biggest definitional height.
|
||||
Otherwise both are unfolded.
|
||||
where `f` and `g` are definitions, it must decide which side to unfold. The rules (implemented in
|
||||
`lazy_delta_reduction_step` in `src/kernel/type_checker.cpp`) are:
|
||||
|
||||
The arguments of the `regular` Constructor are: the definitional height and the flag `selfOpt`.
|
||||
* If `f` and `g` have the **same hint kind**:
|
||||
- Both `.opaque` or both `.abbrev`: unfold both.
|
||||
- Both `.regular`: unfold the one with the **greater** height first. If their heights are equal
|
||||
(in particular, if `f` and `g` are the same definition), first try to compare their arguments
|
||||
for definitional equality (short-circuiting the unfolding if they match), then unfold both.
|
||||
* If `f` and `g` have **different hint kinds**: unfold the one that is *not* `.opaque`, preferring to
|
||||
unfold `.abbrev` over `.regular`.
|
||||
|
||||
The definitional height is by default computed by the kernel. It only takes into account
|
||||
other regular definitions used in a definition. When creating declarations using meta-programming,
|
||||
we can specify the definitional depth manually.
|
||||
The `.regular` constructor carries a `UInt32` *definitional height*, which is computed by the
|
||||
elaborator as one plus the maximum height of all `.regular` constants appearing in the definition's
|
||||
body (see `getMaxHeight`). This means `.abbrev` and `.opaque` constants do not contribute to the
|
||||
height. When creating declarations via meta-programming, the height can be specified manually.
|
||||
|
||||
Remark: the hint only affects performance. None of the hints prevent the kernel from unfolding a
|
||||
declaration during Type checking.
|
||||
The hints only affect performance — they control the order in which definitions are unfolded, but
|
||||
never prevent the kernel from unfolding a definition during type checking.
|
||||
|
||||
Remark: the ReducibilityHints are not related to the attributes: reducible/irrelevance/semireducible.
|
||||
These attributes are used by the Elaborator. The ReducibilityHints are used by the kernel (and Elaborator).
|
||||
Moreover, the ReducibilityHints cannot be changed after a declaration is added to the kernel. -/
|
||||
The `ReducibilityHints` are not related to the `@[reducible]`/`@[irreducible]`/`@[semireducible]`
|
||||
attributes. Those attributes are used by the elaborator to control which definitions tactics like
|
||||
`simp`, `rfl`, and `dsimp` will unfold; they do not affect the kernel. Conversely,
|
||||
`ReducibilityHints` are set when a declaration is added to the kernel and cannot be changed
|
||||
afterwards. -/
|
||||
inductive ReducibilityHints where
|
||||
| opaque : ReducibilityHints
|
||||
| abbrev : ReducibilityHints
|
||||
@@ -469,24 +475,37 @@ def numLevelParams (d : ConstantInfo) : Nat :=
|
||||
def type (d : ConstantInfo) : Expr :=
|
||||
d.toConstantVal.type
|
||||
|
||||
/--
|
||||
Returns the value of a definition. With `allowOpaque := true`, values
|
||||
of theorems and opaque declarations are also returned.
|
||||
-/
|
||||
def value? (info : ConstantInfo) (allowOpaque := false) : Option Expr :=
|
||||
match info with
|
||||
| .defnInfo {value, ..} => some value
|
||||
| .thmInfo {value, ..} => some value
|
||||
| .thmInfo {value, ..} => if allowOpaque then some value else none
|
||||
| .opaqueInfo {value, ..} => if allowOpaque then some value else none
|
||||
| _ => none
|
||||
|
||||
/--
|
||||
Returns `true` if this declaration as a value for the purpose of reduction
|
||||
and type-checking, i.e. is a definition.
|
||||
With `allowOpaque := true`, theorems and opaque declarations are also considered to have values.
|
||||
-/
|
||||
def hasValue (info : ConstantInfo) (allowOpaque := false) : Bool :=
|
||||
match info with
|
||||
| .defnInfo _ => true
|
||||
| .thmInfo _ => true
|
||||
| .thmInfo _ => allowOpaque
|
||||
| .opaqueInfo _ => allowOpaque
|
||||
| _ => false
|
||||
|
||||
/--
|
||||
Returns the value of a definition. With `allowOpaque := true`, values
|
||||
of theorems and opaque declarations are also returned.
|
||||
-/
|
||||
def value! (info : ConstantInfo) (allowOpaque := false) : Expr :=
|
||||
match info with
|
||||
| .defnInfo {value, ..} => value
|
||||
| .thmInfo {value, ..} => value
|
||||
| .thmInfo {value, ..} => if allowOpaque then value else panic! "declaration with value expected"
|
||||
| .opaqueInfo {value, ..} => if allowOpaque then value else panic! "declaration with value expected"
|
||||
| _ => panic! s!"declaration with value expected, but {info.name} has none"
|
||||
|
||||
@@ -510,6 +529,10 @@ def isDefinition : ConstantInfo → Bool
|
||||
| .defnInfo _ => true
|
||||
| _ => false
|
||||
|
||||
def isTheorem : ConstantInfo → Bool
|
||||
| .thmInfo _ => true
|
||||
| _ => false
|
||||
|
||||
def inductiveVal! : ConstantInfo → InductiveVal
|
||||
| .inductInfo val => val
|
||||
| _ => panic! "Expected a `ConstantInfo.inductInfo`."
|
||||
|
||||
@@ -101,7 +101,7 @@ def inferDefEqAttr (declName : Name) : MetaM Unit := do
|
||||
withoutExporting do
|
||||
let info ← getConstInfo declName
|
||||
let isRfl ←
|
||||
if let some value := info.value? then
|
||||
if let some value := info.value? (allowOpaque := true) then
|
||||
isRflProofCore info.type value
|
||||
else
|
||||
pure false
|
||||
|
||||
@@ -7,7 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Lean.Meta.Diagnostics
|
||||
public import Lean.Meta.InstanceNormalForm
|
||||
public import Lean.Meta.WrapInstance
|
||||
public import Lean.Elab.Open
|
||||
public import Lean.Elab.SetOption
|
||||
public import Lean.Elab.Eval
|
||||
@@ -315,9 +315,16 @@ private def mkSilentAnnotationIfHole (e : Expr) : TermElabM Expr := do
|
||||
| _ => panic! "resolveId? returned an unexpected expression"
|
||||
|
||||
@[builtin_term_elab Lean.Parser.Term.inferInstanceAs] def elabInferInstanceAs : TermElab := fun stx expectedType? => do
|
||||
let expectedType ← tryPostponeIfHasMVars expectedType? "`inferInstanceAs` failed"
|
||||
-- The type argument is the last child (works for both `inferInstanceAs T` and `inferInstanceAs <| T`)
|
||||
let typeStx := stx[stx.getNumArgs - 1]!
|
||||
if !backward.inferInstanceAs.wrap.get (← getOptions) then
|
||||
return (← elabTerm (← `(_root_.inferInstanceAs $(⟨typeStx⟩))) expectedType?)
|
||||
|
||||
let some expectedType ← tryPostponeIfHasMVars? expectedType? |
|
||||
throwError (m!"`inferInstanceAs` failed, expected type contains metavariables{indentD expectedType?}" ++
|
||||
.note "`inferInstanceAs` requires full knowledge of the expected (\"target\") type to do its \
|
||||
instance translation. If you do not intend to transport instances between two types, \
|
||||
consider using `inferInstance` or `(inferInstance : expectedType)` instead.")
|
||||
let type ← withSynthesize (postpone := .yes) <| elabType typeStx
|
||||
-- Unify with expected type to resolve metavariables (e.g., `_` placeholders)
|
||||
discard <| isDefEq type expectedType
|
||||
@@ -327,10 +334,10 @@ private def mkSilentAnnotationIfHole (e : Expr) : TermElabM Expr := do
|
||||
let type ← abstractInstImplicitArgs type
|
||||
let inst ← synthInstance type
|
||||
let inst ← if backward.inferInstanceAs.wrap.get (← getOptions) then
|
||||
-- Normalize to instance normal form.
|
||||
-- Wrap instance so its type matches the expected type exactly.
|
||||
let logCompileErrors := !(← read).isNoncomputableSection && !(← read).declName?.any (Lean.isNoncomputable (← getEnv))
|
||||
let isMeta := (← read).isMetaSection
|
||||
withNewMCtxDepth <| normalizeInstance inst expectedType (logCompileErrors := logCompileErrors) (isMeta := isMeta)
|
||||
let isMeta := (← read).declName?.any (isMarkedMeta (← getEnv))
|
||||
withNewMCtxDepth <| wrapInstance inst expectedType (logCompileErrors := logCompileErrors) (isMeta := isMeta)
|
||||
else
|
||||
pure inst
|
||||
ensureHasType expectedType? inst
|
||||
|
||||
@@ -9,7 +9,7 @@ prelude
|
||||
public import Lean.Elab.App
|
||||
public import Lean.Elab.DeclNameGen
|
||||
import Lean.Compiler.NoncomputableAttr
|
||||
import Lean.Meta.InstanceNormalForm
|
||||
import Lean.Meta.WrapInstance
|
||||
|
||||
public section
|
||||
|
||||
@@ -211,19 +211,19 @@ def processDefDeriving (view : DerivingClassView) (decl : Expr) (isNoncomputable
|
||||
-- We don't reduce because of abbreviations such as `DecidableEq`
|
||||
forallTelescope classExpr fun _ classExpr => do
|
||||
let result ← mkInst classExpr declName decl value
|
||||
-- Save the pre-normalization value for the noncomputable check below,
|
||||
-- since `normalizeInstance` may inline noncomputable constants.
|
||||
-- Save the pre-wrapping value for the noncomputable check below,
|
||||
-- since `wrapInstance` may inline noncomputable constants.
|
||||
let preNormClosure ← Closure.mkValueTypeClosure result.instType result.instVal (zetaDelta := true)
|
||||
-- Compute instance name early so `normalizeInstance` can use it for aux def naming.
|
||||
-- Compute instance name early so `wrapInstance` can use it for aux def naming.
|
||||
let env ← getEnv
|
||||
let mut instName := (← getCurrNamespace) ++ (← NameGen.mkBaseNameWithSuffix "inst" preNormClosure.type)
|
||||
instName ← liftMacroM <| mkUnusedBaseName instName
|
||||
if isPrivateName declName then
|
||||
instName := mkPrivateName env instName
|
||||
let isMeta := (← read).isMetaSection
|
||||
let isMeta := (← read).declName?.any (isMarkedMeta (← getEnv))
|
||||
let inst ← if backward.inferInstanceAs.wrap.get (← getOptions) then
|
||||
withDeclNameForAuxNaming instName <| withNewMCtxDepth <|
|
||||
normalizeInstance result.instVal result.instType
|
||||
wrapInstance result.instVal result.instType
|
||||
(logCompileErrors := false) -- covered by noncomputable check below
|
||||
(isMeta := isMeta)
|
||||
else
|
||||
|
||||
@@ -10,7 +10,7 @@ public import Lean.Compiler.NoncomputableAttr
|
||||
public import Lean.Util.NumApps
|
||||
public import Lean.Meta.Eqns
|
||||
public import Lean.Elab.RecAppSyntax
|
||||
public import Lean.Meta.InstanceNormalForm
|
||||
public import Lean.Meta.WrapInstance
|
||||
public import Lean.Elab.DefView
|
||||
public section
|
||||
|
||||
|
||||
@@ -63,10 +63,11 @@ def addPreDefAttributes (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
a wrong setting and creates bad `defEq` equations.
|
||||
-/
|
||||
for preDef in preDefs do
|
||||
unless preDef.modifiers.attrs.any fun a =>
|
||||
a.name = `reducible || a.name = `semireducible ||
|
||||
a.name = `instance_reducible || a.name = `implicit_reducible do
|
||||
setIrreducibleAttribute preDef.declName
|
||||
unless preDef.kind.isTheorem do
|
||||
unless preDef.modifiers.attrs.any fun a =>
|
||||
a.name = `reducible || a.name = `semireducible ||
|
||||
a.name = `instance_reducible || a.name = `implicit_reducible do
|
||||
setIrreducibleAttribute preDef.declName
|
||||
|
||||
/-
|
||||
`enableRealizationsForConst` must happen before `generateEagerEqns`
|
||||
|
||||
@@ -184,6 +184,7 @@ def getUnfoldFor? (declName : Name) : MetaM (Option Name) := do
|
||||
else
|
||||
return none
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_get_structural_rec_arg_pos]
|
||||
def getStructuralRecArgPosImp? (declName : Name) : CoreM (Option Nat) := do
|
||||
let some info := eqnInfoExt.find? (← getEnv) declName | return none
|
||||
|
||||
@@ -80,6 +80,32 @@ private def elimMutualRecursion (preDefs : Array PreDefinition) (fixedParamPerms
|
||||
withRecFunsAsAxioms preDefs do
|
||||
mkBRecOnF recArgInfos positions r values[idx]! FTypes[idx]!
|
||||
trace[Elab.definition.structural] "FArgs: {FArgs}"
|
||||
|
||||
-- Extract the functionals into named `_f` helper definitions (e.g. `foo._f`) so they show up
|
||||
-- with a helpful name in kernel diagnostics. The `_f` definitions are `.abbrev` so the kernel
|
||||
-- unfolds them eagerly; their body heights are registered via `setDefHeightOverride` so that
|
||||
-- `getMaxHeight` computes the correct height for parent definitions.
|
||||
-- For inductive predicates, the previous inline behavior is kept.
|
||||
let FArgs ←
|
||||
if isIndPred then
|
||||
pure FArgs
|
||||
else
|
||||
let us := preDefs[0]!.levelParams.map mkLevelParam
|
||||
FArgs.mapIdxM fun idx fArg => do
|
||||
let fName := preDefs[idx]!.declName ++ `_f
|
||||
let fValue ← eraseRecAppSyntaxExpr (← mkLambdaFVars xs fArg)
|
||||
let fType ← Meta.letToHave (← inferType fValue)
|
||||
let fHeight := getMaxHeight (← getEnv) fValue
|
||||
addDecl (.defnDecl {
|
||||
name := fName, levelParams := preDefs[idx]!.levelParams,
|
||||
type := fType, value := fValue,
|
||||
hints := .abbrev,
|
||||
safety := if preDefs[idx]!.modifiers.isUnsafe then .unsafe else .safe,
|
||||
all := [fName] })
|
||||
modifyEnv (setDefHeightOverride · fName fHeight)
|
||||
setReducibleAttribute fName
|
||||
return mkAppN (mkConst fName us) xs
|
||||
|
||||
let brecOn := brecOnConst 0
|
||||
-- the indices and the major premise are not mentioned in the minor premises
|
||||
-- so using `default` is fine here
|
||||
|
||||
@@ -268,24 +268,3 @@ def isMVCGenInvariantType (env : Environment) (ty : Expr) : Bool :=
|
||||
mvcgenInvariantAttr.hasTag env name
|
||||
else
|
||||
false
|
||||
|
||||
/--
|
||||
Marks a type as a witness type for the `mvcgen` tactic.
|
||||
Goals whose type is an application of a tagged type will be classified
|
||||
as witnesses rather than verification conditions.
|
||||
In the spirit of zero-knowledge proofs, witnesses are concrete values that the user
|
||||
must provide, as opposed to invariants (predicates maintained across iterations)
|
||||
or verification conditions (propositions to prove).
|
||||
-/
|
||||
builtin_initialize mvcgenWitnessTypeAttr : TagAttribute ←
|
||||
registerTagAttribute `mvcgen_witness_type
|
||||
"marks a type as a witness type for the `mvcgen` tactic"
|
||||
|
||||
/--
|
||||
Returns `true` if `ty` is an application of a type tagged with `@[mvcgen_witness_type]`.
|
||||
-/
|
||||
def isMVCGenWitnessType (env : Environment) (ty : Expr) : Bool :=
|
||||
if let .const name .. := ty.getAppFn then
|
||||
mvcgenWitnessTypeAttr.hasTag env name
|
||||
else
|
||||
false
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace VCGen
|
||||
|
||||
structure Result where
|
||||
invariants : Array MVarId
|
||||
witnesses : Array MVarId
|
||||
vcs : Array MVarId
|
||||
|
||||
partial def genVCs (goal : MVarId) (ctx : Context) (fuel : Fuel) : MetaM Result := do
|
||||
@@ -46,13 +45,10 @@ partial def genVCs (goal : MVarId) (ctx : Context) (fuel : Fuel) : MetaM Result
|
||||
for h : idx in *...state.invariants.size do
|
||||
let mv := state.invariants[idx]
|
||||
mv.setTag (Name.mkSimple ("inv" ++ toString (idx + 1)))
|
||||
for h : idx in *...state.witnesses.size do
|
||||
let mv := state.witnesses[idx]
|
||||
mv.setTag (Name.mkSimple ("witness" ++ toString (idx + 1)))
|
||||
for h : idx in *...state.vcs.size do
|
||||
let mv := state.vcs[idx]
|
||||
mv.setTag (Name.mkSimple ("vc" ++ toString (idx + 1)) ++ (← mv.getTag).eraseMacroScopes)
|
||||
return { invariants := state.invariants, witnesses := state.witnesses, vcs := state.vcs }
|
||||
return { invariants := state.invariants, vcs := state.vcs }
|
||||
where
|
||||
onFail (goal : MGoal) (name : Name) : VCGenM Expr := do
|
||||
-- trace[Elab.Tactic.Do.vcgen] "fail {goal.toExpr}"
|
||||
@@ -356,70 +352,53 @@ where
|
||||
|
||||
end VCGen
|
||||
|
||||
/-- Shared implementation for elaborating goal sections (invariants, witnesses).
|
||||
`tagPrefix` is `"inv"` or `"witness"`, used to parse labels like `inv1` or `witness2`.
|
||||
`label` is `"invariant"` or `"witness"`, used in error messages.
|
||||
When `requireAll` is true, an error is thrown if fewer alts are provided than goals. -/
|
||||
private def elabGoalSection (goals : Array MVarId) (alts : Array Syntax)
|
||||
(tagPrefix : String) (label : String) (requireAll := true) : TacticM Unit := do
|
||||
let goals ← goals.filterM (not <$> ·.isAssigned)
|
||||
let mut dotOrCase := LBool.undef -- .true => dot
|
||||
for h : n in 0...alts.size do
|
||||
let alt := alts[n]
|
||||
match alt with
|
||||
| `(goalDotAlt| · $rhs) =>
|
||||
if dotOrCase matches .false then
|
||||
logErrorAt alt m!"Alternation between labelled and bulleted {label}s is not supported."
|
||||
break
|
||||
dotOrCase := .true
|
||||
let some mv := goals[n]? | do
|
||||
logErrorAt alt m!"More {label}s have been defined ({alts.size}) than there were unassigned {label} goals `{tagPrefix}<n>` ({goals.size})."
|
||||
continue
|
||||
withRef rhs do
|
||||
discard <| evalTacticAt (← `(tactic| exact $rhs)) mv
|
||||
| `(goalCaseAlt| | $tag $args* => $rhs) =>
|
||||
if dotOrCase matches .true then
|
||||
logErrorAt alt m!"Alternation between labelled and bulleted {label}s is not supported."
|
||||
break
|
||||
dotOrCase := .false
|
||||
let n? : Option Nat := do
|
||||
let `(binderIdent| $tag:ident) := tag | some n -- fall back to ordinal
|
||||
let .str .anonymous s := tag.getId | none
|
||||
s.dropPrefix? tagPrefix >>= String.Slice.toNat?
|
||||
let some mv := do goals[(← n?) - 1]? | do
|
||||
logErrorAt alt m!"No {label} with label {tag} {repr tag}."
|
||||
continue
|
||||
if ← mv.isAssigned then
|
||||
logErrorAt alt m!"{label} {n?.get!} is already assigned."
|
||||
continue
|
||||
withRef rhs do
|
||||
discard <| evalTacticAt (← `(tactic| rename_i $args*; exact $rhs)) mv
|
||||
| _ => logErrorAt alt m!"Expected `goalDotAlt`, got {alt}"
|
||||
if requireAll && alts.size < goals.size then
|
||||
let missingTypes ← goals[alts.size:].toArray.mapM (·.getType)
|
||||
throwError "Lacking definitions for the following {label}s.\n{toMessageList missingTypes}"
|
||||
|
||||
def elabWitnesses (stx : Syntax) (witnesses : Array MVarId) : TacticM Unit := do
|
||||
let some stx := stx.getOptional? | return ()
|
||||
let stx : TSyntax ``witnessAlts := ⟨stx⟩
|
||||
withRef stx do
|
||||
match stx with
|
||||
| `(witnessAlts| witnesses $alts*) =>
|
||||
elabGoalSection witnesses alts "witness" "witness"
|
||||
| _ => logErrorAt stx m!"Expected witnessAlts, got {stx}"
|
||||
|
||||
def elabInvariants (stx : Syntax) (invariants : Array MVarId) (suggestInvariant : MVarId → TacticM Term) : TacticM Unit := do
|
||||
let some stx := stx.getOptional? | return ()
|
||||
let stx : TSyntax ``invariantAlts := ⟨stx⟩
|
||||
withRef stx do
|
||||
match stx with
|
||||
| `(invariantAlts| $invariantsKW $alts*) =>
|
||||
let invariants ← invariants.filterM (not <$> ·.isAssigned)
|
||||
|
||||
let mut dotOrCase := LBool.undef -- .true => dot
|
||||
for h : n in 0...alts.size do
|
||||
let alt := alts[n]
|
||||
match alt with
|
||||
| `(goalDotAlt| · $rhs) =>
|
||||
if dotOrCase matches .false then
|
||||
logErrorAt alt m!"Alternation between labelled and bulleted invariants is not supported."
|
||||
break
|
||||
dotOrCase := .true
|
||||
let some mv := invariants[n]? | do
|
||||
logErrorAt alt m!"More invariants have been defined ({alts.size}) than there were unassigned invariants goals `inv<n>` ({invariants.size})."
|
||||
continue
|
||||
withRef rhs do
|
||||
discard <| evalTacticAt (← `(tactic| exact $rhs)) mv
|
||||
| `(goalCaseAlt| | $tag $args* => $rhs) =>
|
||||
if dotOrCase matches .true then
|
||||
logErrorAt alt m!"Alternation between labelled and bulleted invariants is not supported."
|
||||
break
|
||||
dotOrCase := .false
|
||||
let n? : Option Nat := do
|
||||
let `(binderIdent| $tag:ident) := tag | some n -- fall back to ordinal
|
||||
let .str .anonymous s := tag.getId | none
|
||||
s.dropPrefix? "inv" >>= String.Slice.toNat?
|
||||
let some mv := do invariants[(← n?) - 1]? | do
|
||||
logErrorAt alt m!"No invariant with label {tag} {repr tag}."
|
||||
continue
|
||||
if ← mv.isAssigned then
|
||||
logErrorAt alt m!"Invariant {n?.get!} is already assigned."
|
||||
continue
|
||||
withRef rhs do
|
||||
discard <| evalTacticAt (← `(tactic| rename_i $args*; exact $rhs)) mv
|
||||
| _ => logErrorAt alt m!"Expected `goalDotAlt`, got {alt}"
|
||||
|
||||
if let `(invariantsKW| invariants) := invariantsKW then
|
||||
elabGoalSection invariants alts "inv" "invariant"
|
||||
if alts.size < invariants.size then
|
||||
let missingTypes ← invariants[alts.size:].toArray.mapM (·.getType)
|
||||
throwErrorAt stx m!"Lacking definitions for the following invariants.\n{toMessageList missingTypes}"
|
||||
else
|
||||
-- We have `invariants?`. First elaborate any user-provided alts, then suggest the rest.
|
||||
elabGoalSection invariants alts "inv" "invariant" (requireAll := false)
|
||||
let invariants ← invariants.filterM (not <$> ·.isAssigned)
|
||||
-- Otherwise, we have `invariants?`. Suggest missing invariants.
|
||||
let mut suggestions := #[]
|
||||
for i in 0...invariants.size do
|
||||
let mv := invariants[i]!
|
||||
@@ -478,8 +457,8 @@ def elabMVCGen : Tactic := fun stx => withMainContext do
|
||||
| none => .unlimited
|
||||
let goal ← getMainGoal
|
||||
let goal ← if ctx.config.elimLets then elimLets goal else pure goal
|
||||
let { invariants, witnesses, vcs } ← VCGen.genVCs goal ctx fuel
|
||||
trace[Elab.Tactic.Do.vcgen] "after genVCs {← (invariants ++ witnesses ++ vcs).mapM fun m => m.getTag}"
|
||||
let { invariants, vcs } ← VCGen.genVCs goal ctx fuel
|
||||
trace[Elab.Tactic.Do.vcgen] "after genVCs {← (invariants ++ vcs).mapM fun m => m.getTag}"
|
||||
let runOnVCs (tac : TSyntax `tactic) (extraMsg : MessageData) (vcs : Array MVarId) : TermElabM (Array MVarId) :=
|
||||
vcs.flatMapM fun vc =>
|
||||
tryCatchRuntimeEx
|
||||
@@ -488,13 +467,10 @@ def elabMVCGen : Tactic := fun stx => withMainContext do
|
||||
(fun ex => throwError "Error while running {tac} on {vc}Message: {indentD ex.toMessageData}\n{extraMsg}")
|
||||
let invariants ←
|
||||
if ctx.config.leave then runOnVCs (← `(tactic| try mleave)) "Try again with -leave." invariants else pure invariants
|
||||
trace[Elab.Tactic.Do.vcgen] "before elabWitnesses {← (invariants ++ witnesses ++ vcs).mapM fun m => m.getTag}"
|
||||
elabWitnesses stx[3] witnesses
|
||||
let witnesses ← witnesses.filterM (not <$> ·.isAssigned)
|
||||
trace[Elab.Tactic.Do.vcgen] "before elabInvariants {← (invariants ++ witnesses ++ vcs).mapM fun m => m.getTag}"
|
||||
elabInvariants stx[4] invariants (suggestInvariant vcs)
|
||||
trace[Elab.Tactic.Do.vcgen] "before elabInvariants {← (invariants ++ vcs).mapM fun m => m.getTag}"
|
||||
elabInvariants stx[3] invariants (suggestInvariant vcs)
|
||||
let invariants ← invariants.filterM (not <$> ·.isAssigned)
|
||||
trace[Elab.Tactic.Do.vcgen] "before trying trivial VCs {← (invariants ++ witnesses ++ vcs).mapM fun m => m.getTag}"
|
||||
trace[Elab.Tactic.Do.vcgen] "before trying trivial VCs {← (invariants ++ vcs).mapM fun m => m.getTag}"
|
||||
let vcs ← do
|
||||
let vcs ← if ctx.config.trivial then runOnVCs (← `(tactic| try mvcgen_trivial)) "Try again with -trivial." vcs else pure vcs
|
||||
let vcs ← if ctx.config.leave then runOnVCs (← `(tactic| try mleave)) "Try again with -leave." vcs else pure vcs
|
||||
@@ -502,17 +478,17 @@ def elabMVCGen : Tactic := fun stx => withMainContext do
|
||||
-- Eliminating lets here causes some metavariables in `mkFreshPair_triple` to become nonassignable
|
||||
-- so we don't do it. Presumably some weird delayed assignment thing is going on.
|
||||
-- let vcs ← if ctx.config.elimLets then liftMetaM <| vcs.mapM elimLets else pure vcs
|
||||
trace[Elab.Tactic.Do.vcgen] "before elabVCs {← (invariants ++ witnesses ++ vcs).mapM fun m => m.getTag}"
|
||||
let vcs ← elabVCs stx[5] vcs
|
||||
trace[Elab.Tactic.Do.vcgen] "before replacing main goal {← (invariants ++ witnesses ++ vcs).mapM fun m => m.getTag}"
|
||||
replaceMainGoal (invariants ++ witnesses ++ vcs).toList
|
||||
trace[Elab.Tactic.Do.vcgen] "before elabVCs {← (invariants ++ vcs).mapM fun m => m.getTag}"
|
||||
let vcs ← elabVCs stx[4] vcs
|
||||
trace[Elab.Tactic.Do.vcgen] "before replacing main goal {← (invariants ++ vcs).mapM fun m => m.getTag}"
|
||||
replaceMainGoal (invariants ++ vcs).toList
|
||||
-- trace[Elab.Tactic.Do.vcgen] "replaced main goal, new: {← getGoals}"
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.mvcgenHint]
|
||||
def elabMVCGenHint : Tactic := fun stx => withMainContext do
|
||||
let stx' : TSyntax ``mvcgen := TSyntax.mk <| stx
|
||||
|>.setKind ``Lean.Parser.Tactic.mvcgen
|
||||
|>.modifyArgs (·.set! 0 (mkAtom "mvcgen") |>.push mkNullNode |>.push (mkNullNode #[← `(invariantAlts| invariants?)]) |>.push mkNullNode)
|
||||
|>.modifyArgs (·.set! 0 (mkAtom "mvcgen") |>.push (mkNullNode #[← `(invariantAlts| invariants?)]) |>.push mkNullNode)
|
||||
-- logInfo m!"{stx}\n{toString stx}\n{repr stx}"
|
||||
-- logInfo m!"{stx'}\n{toString stx'}\n{repr stx'}"
|
||||
Lean.Meta.Tactic.TryThis.addSuggestion stx stx'
|
||||
|
||||
@@ -73,10 +73,6 @@ structure State where
|
||||
-/
|
||||
invariants : Array MVarId := #[]
|
||||
/--
|
||||
Holes of witness type that have been generated so far.
|
||||
-/
|
||||
witnesses : Array MVarId := #[]
|
||||
/--
|
||||
The verification conditions that have been generated so far.
|
||||
-/
|
||||
vcs : Array MVarId := #[]
|
||||
@@ -108,11 +104,8 @@ def addSubGoalAsVC (goal : MVarId) : VCGenM PUnit := do
|
||||
-- VC to the user as-is, without abstracting any variables in the local context.
|
||||
-- This only makes sense for synthetic opaque metavariables.
|
||||
goal.setKind .syntheticOpaque
|
||||
let env ← getEnv
|
||||
if isMVCGenInvariantType env ty then
|
||||
if isMVCGenInvariantType (← getEnv) ty then
|
||||
modify fun s => { s with invariants := s.invariants.push goal }
|
||||
else if isMVCGenWitnessType env ty then
|
||||
modify fun s => { s with witnesses := s.witnesses.push goal }
|
||||
else
|
||||
modify fun s => { s with vcs := s.vcs.push goal }
|
||||
|
||||
|
||||
@@ -76,6 +76,10 @@ def evalGrindSeq : GrindTactic := fun stx =>
|
||||
@[builtin_grind_tactic skip] def evalSkip : GrindTactic := fun _ =>
|
||||
return ()
|
||||
|
||||
@[builtin_grind_tactic showGoals] def evalShowGoals : GrindTactic := fun _ => do
|
||||
let goals ← getUnsolvedGoalMVarIds
|
||||
addRawTrace (goalsToMessageData goals)
|
||||
|
||||
@[builtin_grind_tactic paren] def evalParen : GrindTactic := fun stx =>
|
||||
evalGrindTactic stx[1]
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import Lean.Elab.Tactic.Grind.SimprocDSL
|
||||
import Init.Sym.Simp.SimprocDSL
|
||||
import Lean.Meta.Sym.Simp.EvalGround
|
||||
import Lean.Meta.Sym.Simp.Telescope
|
||||
import Lean.Meta.Sym.Simp.ControlFlow
|
||||
import Lean.Meta.Sym.Simp.Forall
|
||||
import Lean.Meta.Sym.Simp.Rewrite
|
||||
namespace Lean.Elab.Tactic.Grind
|
||||
open Meta Sym.Simp
|
||||
@@ -23,6 +25,14 @@ def elabSimprocGround : SymSimprocElab := fun _ =>
|
||||
def elabSimprocTelescope : SymSimprocElab := fun _ =>
|
||||
return simpTelescope
|
||||
|
||||
@[builtin_sym_simproc Lean.Parser.Sym.Simp.control]
|
||||
def elabSimprocControl : SymSimprocElab := fun _ =>
|
||||
return simpControl
|
||||
|
||||
@[builtin_sym_simproc Lean.Parser.Sym.Simp.arrowTelescope]
|
||||
def elabSimprocArrowTelescope : SymSimprocElab := fun _ =>
|
||||
return simpArrowTelescope
|
||||
|
||||
@[builtin_sym_simproc self]
|
||||
def elabSimprocSelf : SymSimprocElab := fun _ =>
|
||||
return simp
|
||||
|
||||
@@ -787,6 +787,7 @@ where
|
||||
throw ex
|
||||
|
||||
-- `evalSuggest` implementation
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_eval_suggest_tactic]
|
||||
private partial def evalSuggestImpl : TryTactic := fun tac => do
|
||||
trace[try.debug] "{tac}"
|
||||
|
||||
@@ -1193,8 +1193,8 @@ namespace ConstantInfo
|
||||
def instantiateTypeLevelParams (c : ConstantInfo) (ls : List Level) : Expr :=
|
||||
c.toConstantVal.instantiateTypeLevelParams ls
|
||||
|
||||
def instantiateValueLevelParams! (c : ConstantInfo) (ls : List Level) : Expr :=
|
||||
c.value!.instantiateLevelParams c.levelParams ls
|
||||
def instantiateValueLevelParams! (c : ConstantInfo) (ls : List Level) (allowOpaque := false) : Expr :=
|
||||
(c.value! (allowOpaque := allowOpaque)).instantiateLevelParams c.levelParams ls
|
||||
|
||||
end ConstantInfo
|
||||
|
||||
@@ -2755,13 +2755,28 @@ def mkThmOrUnsafeDef [Monad m] [MonadEnv m] (thm : TheoremVal) : m Declaration :
|
||||
else
|
||||
return .thmDecl thm
|
||||
|
||||
/-- Environment extension for overriding the height that `getMaxHeight` assigns to a definition.
|
||||
This is consulted for all definitions regardless of their reducibility hints. Currently used by
|
||||
structural recursion to ensure that parent definitions get the correct height even though the
|
||||
`_f` helper definitions are marked as `.abbrev` (which `getMaxHeight` would otherwise ignore). -/
|
||||
builtin_initialize defHeightOverrideExt : EnvExtension (NameMap UInt32) ←
|
||||
registerEnvExtension (pure {}) (asyncMode := .local)
|
||||
|
||||
/-- Register a height override for a definition so that `getMaxHeight` uses it. -/
|
||||
def setDefHeightOverride (env : Environment) (declName : Name) (height : UInt32) : Environment :=
|
||||
defHeightOverrideExt.modifyState env fun m => m.insert declName height
|
||||
|
||||
def getMaxHeight (env : Environment) (e : Expr) : UInt32 :=
|
||||
let overrides := defHeightOverrideExt.getState env
|
||||
e.foldConsts 0 fun constName max =>
|
||||
match env.findAsync? constName with
|
||||
| some { kind := .defn, constInfo := info, .. } =>
|
||||
match info.get.hints with
|
||||
| ReducibilityHints.regular h => if h > max then h else max
|
||||
| _ => max
|
||||
| _ => max
|
||||
match overrides.find? constName with
|
||||
| some h => if h > max then h else max
|
||||
| none =>
|
||||
match env.findAsync? constName with
|
||||
| some { kind := .defn, constInfo := info, .. } =>
|
||||
match info.get.hints with
|
||||
| ReducibilityHints.regular h => if h > max then h else max
|
||||
| _ => max
|
||||
| _ => max
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -73,7 +73,7 @@ inductive BinderInfo where
|
||||
| default
|
||||
/-- Implicit binder annotation, e.g., `{x : α}` -/
|
||||
| implicit
|
||||
/-- Strict implicit binder annotation, e.g., `{{ x : α }}` -/
|
||||
/-- Strict implicit binder annotation, e.g., `⦃x : α⦄` -/
|
||||
| strictImplicit
|
||||
/-- Local instance binder annotation, e.g., `[Decidable α]` -/
|
||||
| instImplicit
|
||||
@@ -107,7 +107,7 @@ def BinderInfo.isImplicit : BinderInfo → Bool
|
||||
| BinderInfo.implicit => true
|
||||
| _ => false
|
||||
|
||||
/-- Return `true` if the given `BinderInfo` is a strict implicit annotation (e.g., `{{α : Type u}}`) -/
|
||||
/-- Return `true` if the given `BinderInfo` is a strict implicit annotation (e.g., `⦃α : Type u⦄`) -/
|
||||
def BinderInfo.isStrictImplicit : BinderInfo → Bool
|
||||
| BinderInfo.strictImplicit => true
|
||||
| _ => false
|
||||
|
||||
@@ -27,7 +27,7 @@ public import Lean.Meta.Match
|
||||
public import Lean.Meta.ReduceEval
|
||||
public import Lean.Meta.Closure
|
||||
public import Lean.Meta.AbstractNestedProofs
|
||||
public import Lean.Meta.InstanceNormalForm
|
||||
public import Lean.Meta.WrapInstance
|
||||
public import Lean.Meta.LetToHave
|
||||
public import Lean.Meta.ForEachExpr
|
||||
public import Lean.Meta.Transform
|
||||
|
||||
@@ -1321,7 +1321,7 @@ private def getDefInfoTemp (info : ConstantInfo) : MetaM (Option ConstantInfo) :
|
||||
`constName` is an instance. This difference should be irrelevant for `isClassQuickConst?`. -/
|
||||
private def getConstTemp? (constName : Name) : MetaM (Option ConstantInfo) := do
|
||||
match (← getEnv).find? constName with
|
||||
| some (info@(ConstantInfo.thmInfo _)) => getTheoremInfo info
|
||||
| some (ConstantInfo.thmInfo _) => return none
|
||||
| some (info@(ConstantInfo.defnInfo _)) => getDefInfoTemp info
|
||||
| some info => pure (some info)
|
||||
| none => throwUnknownConstantAt (← getRef) constName
|
||||
|
||||
@@ -1126,6 +1126,7 @@ def checkAssignment (mvarId : MVarId) (fvars : Array Expr) (v : Expr) : MetaM (O
|
||||
return none
|
||||
return some v
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
-- Implementation for `_root_.Lean.MVarId.checkedAssign`
|
||||
@[export lean_checked_assign]
|
||||
def checkedAssignImpl (mvarId : MVarId) (val : Expr) : MetaM Bool := do
|
||||
@@ -2233,6 +2234,7 @@ private def whnfCoreAtDefEq (e : Expr) : MetaM Expr := do
|
||||
else
|
||||
whnfCore e
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_is_expr_def_eq]
|
||||
partial def isExprDefEqAuxImpl (t : Expr) (s : Expr) : MetaM Bool := withIncRecDepth do
|
||||
withTraceNodeBefore `Meta.isDefEq (fun _ => return m!"{t} =?= {s}") do
|
||||
|
||||
@@ -46,11 +46,7 @@ External users wanting to look up names should be using `Lean.getConstInfo`.
|
||||
def getUnfoldableConst? (constName : Name) : MetaM (Option ConstantInfo) := do
|
||||
let some ainfo := (← getEnv).findAsync? constName | throwUnknownConstantAt (← getRef) constName
|
||||
match ainfo.kind with
|
||||
| .thm =>
|
||||
if (← shouldReduceAll) then
|
||||
return some ainfo.toConstantInfo
|
||||
else
|
||||
return none
|
||||
| .thm => return none
|
||||
| .defn => if (← canUnfold ainfo.toConstantInfo) then return ainfo.toConstantInfo else return none
|
||||
| _ => return none
|
||||
|
||||
@@ -59,7 +55,7 @@ As with `getUnfoldableConst?` but return `none` instead of failing if the consta
|
||||
-/
|
||||
def getUnfoldableConstNoEx? (constName : Name) : MetaM (Option ConstantInfo) := do
|
||||
match (← getEnv).find? constName with
|
||||
| some (info@(.thmInfo _)) => getTheoremInfo info
|
||||
| some (.thmInfo _) => return none
|
||||
| some (info@(.defnInfo _)) => if (← canUnfold info) then return info else return none
|
||||
| some (.axiomInfo _) => recordUnfoldAxiom constName; return none
|
||||
| _ => return none
|
||||
|
||||
@@ -206,6 +206,7 @@ because it overrides unrelated configurations.
|
||||
else
|
||||
withConfig (fun cfg => { cfg with beta := true, iota := true, zeta := true, zetaHave := true, zetaDelta := true, proj := .yesWithDelta, etaStruct := .all }) x
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_infer_type]
|
||||
def inferTypeImp (e : Expr) : MetaM Expr :=
|
||||
let rec infer (e : Expr) : MetaM Expr := do
|
||||
|
||||
@@ -85,6 +85,7 @@ private def isMVarWithGreaterDepth (v : Level) (mvarId : LMVarId) : MetaM Bool :
|
||||
| Level.mvar mvarId' => return (← mvarId'.getLevel) > (← mvarId.getLevel)
|
||||
| _ => return false
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
mutual
|
||||
|
||||
private partial def solve (u v : Level) : MetaM LBool := do
|
||||
|
||||
@@ -138,6 +138,7 @@ Creates conditional equations and splitter for the given match auxiliary declara
|
||||
|
||||
See also `getEquationsFor`.
|
||||
-/
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_get_match_equations_for]
|
||||
def getEquationsForImpl (matchDeclName : Name) : MetaM MatchEqns := do
|
||||
/-
|
||||
@@ -246,6 +247,7 @@ where go baseName splitterName := withConfig (fun c => { c with etaStruct := .no
|
||||
let result := { eqnNames, splitterName, splitterMatchInfo }
|
||||
registerMatchEqns matchDeclName result
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
/--
|
||||
Generate the congruence equations for the given match auxiliary declaration.
|
||||
The congruence equations have a completely unrestricted left-hand side (arbitrary discriminants),
|
||||
|
||||
@@ -785,6 +785,7 @@ def isDefEqApp (tFn : Expr) (t : Expr) (s : Expr) (_ : tFn = t.getAppFn) : DefEq
|
||||
let numArgs := t.getAppNumArgs
|
||||
isDefEqAppWithInfo t s (numArgs - 1) info
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
/--
|
||||
`isDefEqMain` implementation.
|
||||
-/
|
||||
|
||||
@@ -40,6 +40,7 @@ abbrev cacheResult (e : Expr) (r : Result) : SimpM Result := do
|
||||
modify fun s => { s with persistentCache := s.persistentCache.insert { expr := e } r }
|
||||
return r
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_sym_simp]
|
||||
def simpImpl (e₁ : Expr) : SimpM Result := withIncRecDepth do
|
||||
let numSteps := (← get).numSteps
|
||||
|
||||
@@ -9,6 +9,7 @@ public import Lean.Meta.Sym.Simp.Simproc
|
||||
public import Lean.Meta.Sym.Simp.Theorems
|
||||
public import Lean.Meta.Sym.Simp.App
|
||||
public import Lean.Meta.Sym.Simp.Discharger
|
||||
import Lean.Meta.ACLt
|
||||
import Lean.Meta.Sym.InstantiateS
|
||||
import Lean.Meta.Sym.InstantiateMVarsS
|
||||
import Init.Data.Range.Polymorphic.Iterators
|
||||
@@ -71,10 +72,16 @@ public def Theorem.rewrite (thm : Theorem) (e : Expr) (d : Discharger := dischar
|
||||
let expr ← instantiateRevBetaS rhs args.toArray
|
||||
if isSameExpr e expr then
|
||||
return mkRflResultCD isCD
|
||||
else if !(← checkPerm thm.perm e expr) then
|
||||
return mkRflResultCD isCD
|
||||
else
|
||||
return .step expr proof (contextDependent := isCD)
|
||||
else
|
||||
return .rfl
|
||||
where
|
||||
checkPerm (perm : Bool) (e result : Expr) : MetaM Bool := do
|
||||
if !perm then return true
|
||||
acLt result e
|
||||
|
||||
public def Theorems.rewrite (thms : Theorems) (d : Discharger := dischargeNone) : Simproc := fun e => do
|
||||
-- Track `cd` across all attempted theorems. If theorem A fails with cd=true
|
||||
|
||||
@@ -10,6 +10,7 @@ public import Lean.Meta.DiscrTree
|
||||
import Lean.Meta.Sym.Simp.DiscrTree
|
||||
import Lean.Meta.AppBuilder
|
||||
import Lean.ExtraModUses
|
||||
import Init.Omega
|
||||
public section
|
||||
namespace Lean.Meta.Sym.Simp
|
||||
|
||||
@@ -26,6 +27,10 @@ structure Theorem where
|
||||
pattern : Pattern
|
||||
/-- Right-hand side of the equation. -/
|
||||
rhs : Expr
|
||||
/-- If `true`, the theorem is a permutation rule (e.g., `x + y = y + x`).
|
||||
Rewriting is only applied when the result is strictly less than the input
|
||||
(using `acLt`), preventing infinite loops. -/
|
||||
perm : Bool := false
|
||||
deriving Inhabited
|
||||
|
||||
instance : BEq Theorem where
|
||||
@@ -45,6 +50,49 @@ def Theorems.getMatch (thms : Theorems) (e : Expr) : Array Theorem :=
|
||||
def Theorems.getMatchWithExtra (thms : Theorems) (e : Expr) : Array (Theorem × Nat) :=
|
||||
Sym.getMatchWithExtra thms.thms e
|
||||
|
||||
/--
|
||||
Check whether `lhs` and `rhs` (with `numVars` pattern variables represented as `.bvar` indices
|
||||
`≥ 0` before any binder entry) are permutations of each other — same structure with only
|
||||
pattern variable indices rearranged via a consistent bijection.
|
||||
|
||||
Bvars with index `< offset` are "local" (introduced by binders inside the pattern) and must
|
||||
match exactly. Bvars with index `≥ offset` are pattern variables and may be permuted,
|
||||
but the mapping must be a bijection.
|
||||
|
||||
Simplified compared to `Meta.simp`'s `isPerm`:
|
||||
- Uses de Bruijn indices instead of metavariables
|
||||
- No `.proj` (folded into applications) or `.letE` (zeta-expanded) cases
|
||||
-/
|
||||
private abbrev IsPermM := ReaderT Nat $ StateT (Array (Option Nat)) $ Except Unit
|
||||
|
||||
private partial def isPermAux (a b : Expr) : IsPermM Unit := do
|
||||
match a, b with
|
||||
| .bvar i, .bvar j =>
|
||||
let offset ← read
|
||||
if i < offset && j < offset then
|
||||
unless i == j do throw ()
|
||||
else if i >= offset && j >= offset then
|
||||
let pi := i - offset
|
||||
let pj := j - offset
|
||||
let fwd ← get
|
||||
if h : pi >= fwd.size then throw () else
|
||||
match fwd[pi] with
|
||||
| none =>
|
||||
-- Check injectivity: pj must not already be a target of another mapping
|
||||
if fwd.contains (some pj) then throw ()
|
||||
set (fwd.set pi (some pj))
|
||||
| some pj' => unless pj == pj' do throw ()
|
||||
else throw ()
|
||||
| .app f₁ a₁, .app f₂ a₂ => isPermAux f₁ f₂; isPermAux a₁ a₂
|
||||
| .mdata _ s, t => isPermAux s t
|
||||
| s, .mdata _ t => isPermAux s t
|
||||
| .forallE _ d₁ b₁ _, .forallE _ d₂ b₂ _ => isPermAux d₁ d₂; withReader (· + 1) (isPermAux b₁ b₂)
|
||||
| .lam _ d₁ b₁ _, .lam _ d₂ b₂ _ => isPermAux d₁ d₂; withReader (· + 1) (isPermAux b₁ b₂)
|
||||
| s, t => unless s == t do throw ()
|
||||
|
||||
def isPerm (numVars : Nat) (lhs rhs : Expr) : Bool :=
|
||||
((isPermAux lhs rhs).run 0 |>.run (Array.replicate numVars none)) matches .ok _
|
||||
|
||||
/-- Describes how a theorem's conclusion was adapted to an equality for use in `Sym.simp`. -/
|
||||
private inductive EqAdaptation where
|
||||
/-- Already an equality `lhs = rhs`. Proof is used as-is. -/
|
||||
@@ -99,13 +147,15 @@ where
|
||||
def mkTheoremFromDecl (declName : Name) : MetaM Theorem := do
|
||||
let (pattern, (rhs, adaptation)) ← mkPatternFromDeclWithKey declName selectEqKey
|
||||
let expr ← wrapProof pattern.varTypes.size (mkConst declName) adaptation
|
||||
return { expr, pattern, rhs }
|
||||
let perm := isPerm pattern.varTypes.size pattern.pattern rhs
|
||||
return { expr, pattern, rhs, perm }
|
||||
|
||||
/-- Create a `Theorem` from a proof expression. Handles equalities, `¬`, `↔`, and propositions. -/
|
||||
def mkTheoremFromExpr (e : Expr) : MetaM Theorem := do
|
||||
let (pattern, (rhs, adaptation)) ← mkPatternFromExprWithKey e [] selectEqKey
|
||||
let expr ← wrapProof pattern.varTypes.size e adaptation
|
||||
return { expr, pattern, rhs }
|
||||
let perm := isPerm pattern.varTypes.size pattern.pattern rhs
|
||||
return { expr, pattern, rhs, perm }
|
||||
|
||||
/--
|
||||
Environment extension storing a set of `Sym.Simp` theorems.
|
||||
|
||||
@@ -15,6 +15,50 @@ register_builtin_option sym.debug : Bool := {
|
||||
descr := "check invariants"
|
||||
}
|
||||
|
||||
builtin_initialize registerTraceClass `sym.issues
|
||||
|
||||
/-!
|
||||
## Sym Extensions
|
||||
|
||||
Extensible state mechanism for `SymM`, allowing modules to register persistent state
|
||||
that lives across `simp` invocations within a `sym =>` block. Follows the same pattern
|
||||
as `Grind.SolverExtension` in `Lean/Meta/Tactic/Grind/Types.lean`.
|
||||
-/
|
||||
|
||||
/-- Opaque extension state type used to store type-erased extension values. -/
|
||||
opaque SymExtensionStateSpec : (α : Type) × Inhabited α := ⟨Unit, ⟨()⟩⟩
|
||||
@[expose] def SymExtensionState : Type := SymExtensionStateSpec.fst
|
||||
instance : Inhabited SymExtensionState := SymExtensionStateSpec.snd
|
||||
|
||||
/--
|
||||
A registered extension for `SymM`. Each extension gets a unique index into the
|
||||
extensions array in `Sym.State`. Can only be created via `registerSymExtension`.
|
||||
-/
|
||||
structure SymExtension (σ : Type) where private mk ::
|
||||
id : Nat
|
||||
mkInitial : IO σ
|
||||
deriving Inhabited
|
||||
|
||||
private builtin_initialize symExtensionsRef : IO.Ref (Array (SymExtension SymExtensionState)) ← IO.mkRef #[]
|
||||
|
||||
/--
|
||||
Registers a new `SymM` state extension. Extensions can only be registered during initialization.
|
||||
Returns a handle for typed access to the extension's state.
|
||||
-/
|
||||
def registerSymExtension {σ : Type} (mkInitial : IO σ) : IO (SymExtension σ) := do
|
||||
unless (← initializing) do
|
||||
throw (IO.userError "failed to register `Sym` extension, extensions can only be registered during initialization")
|
||||
let exts ← symExtensionsRef.get
|
||||
let id := exts.size
|
||||
let ext : SymExtension σ := { id, mkInitial }
|
||||
symExtensionsRef.modify fun exts => exts.push (unsafe unsafeCast ext)
|
||||
return ext
|
||||
|
||||
/-- Returns initial state for all registered extensions. -/
|
||||
def SymExtensions.mkInitialStates : IO (Array SymExtensionState) := do
|
||||
let exts ← symExtensionsRef.get
|
||||
exts.mapM fun ext => ext.mkInitial
|
||||
|
||||
/--
|
||||
Information about a single argument position in a function's type signature.
|
||||
|
||||
@@ -92,9 +136,16 @@ structure SharedExprs where
|
||||
ordEqExpr : Expr
|
||||
intExpr : Expr
|
||||
|
||||
/-- Configuration options for the symbolic computation framework. -/
|
||||
structure Config where
|
||||
/-- When `true`, issues are collected during proof search and reported on failure. -/
|
||||
verbose : Bool := true
|
||||
deriving Inhabited
|
||||
|
||||
/-- Readonly context for the symbolic computation framework. -/
|
||||
structure Context where
|
||||
sharedExprs : SharedExprs
|
||||
config : Config := {}
|
||||
|
||||
/-- Mutable state for the symbolic computation framework. -/
|
||||
structure State where
|
||||
@@ -133,6 +184,13 @@ structure State where
|
||||
congrInfo : PHashMap ExprPtr CongrInfo := {}
|
||||
/-- Cache for `isDefEqI` results -/
|
||||
defEqI : PHashMap (ExprPtr × ExprPtr) Bool := {}
|
||||
/-- State for registered `SymExtension`s, indexed by extension id. -/
|
||||
extensions : Array SymExtensionState := #[]
|
||||
/--
|
||||
Issues found during symbolic computation. Accumulated across operations
|
||||
within a `sym =>` block and reported when a tactic fails.
|
||||
-/
|
||||
issues : List MessageData := []
|
||||
debug : Bool := false
|
||||
|
||||
abbrev SymM := ReaderT Context <| StateRefT State MetaM
|
||||
@@ -150,7 +208,8 @@ private def mkSharedExprs : AlphaShareCommonM SharedExprs := do
|
||||
def SymM.run (x : SymM α) : MetaM α := do
|
||||
let (sharedExprs, share) := mkSharedExprs |>.run {}
|
||||
let debug := sym.debug.get (← getOptions)
|
||||
x { sharedExprs } |>.run' { debug, share }
|
||||
let extensions ← SymExtensions.mkInitialStates
|
||||
x { sharedExprs } |>.run' { debug, share, extensions }
|
||||
|
||||
/-- Returns maximally shared commonly used terms -/
|
||||
def getSharedExprs : SymM SharedExprs :=
|
||||
@@ -221,6 +280,55 @@ abbrev share (e : Expr) : SymM Expr :=
|
||||
@[inline] def isDebugEnabled : SymM Bool :=
|
||||
return (← get).debug
|
||||
|
||||
def getConfig : SymM Config :=
|
||||
return (← readThe Context).config
|
||||
|
||||
/-- Adds an issue message to the issue tracker. -/
|
||||
def reportIssue (msg : MessageData) : SymM Unit := do
|
||||
let msg ← addMessageContext msg
|
||||
modify fun s => { s with issues := .trace { cls := `issue } msg #[] :: s.issues }
|
||||
trace[sym.issues] msg
|
||||
|
||||
/-- Reports an issue if `verbose` mode is enabled. Does nothing if `verbose` is `false`. -/
|
||||
@[inline] def reportIssueIfVerbose (msg : MessageData) : SymM Unit := do
|
||||
if (← getConfig).verbose then
|
||||
reportIssue msg
|
||||
|
||||
private meta def expandReportIssueMacro (s : Syntax) : MacroM (TSyntax `doElem) := do
|
||||
let msg ← if s.getKind == interpolatedStrKind then `(m! $(⟨s⟩)) else `(($(⟨s⟩) : MessageData))
|
||||
`(doElem| Sym.reportIssueIfVerbose $msg)
|
||||
|
||||
/-- Reports an issue if `verbose` mode is enabled. -/
|
||||
macro "reportIssue!" s:(interpolatedStr(term) <|> term) : doElem => do
|
||||
expandReportIssueMacro s.raw
|
||||
|
||||
/-- Reports an issue if both `verbose` and `sym.debug` are enabled. Does nothing otherwise. -/
|
||||
@[inline] def reportDbgIssue (msg : MessageData) : SymM Unit := do
|
||||
if (← getConfig).verbose then
|
||||
if sym.debug.get (← getOptions) then
|
||||
reportIssue msg
|
||||
|
||||
meta def expandReportDbgIssueMacro (s : Syntax) : MacroM (TSyntax `doElem) := do
|
||||
let msg ← if s.getKind == interpolatedStrKind then `(m! $(⟨s⟩)) else `(($(⟨s⟩) : MessageData))
|
||||
`(doElem| Sym.reportDbgIssue $msg)
|
||||
|
||||
/-- Similar to `reportIssue!`, but only reports issue if `sym.debug` is set to `true`. -/
|
||||
macro "reportDbgIssue!" s:(interpolatedStr(term) <|> term) : doElem => do
|
||||
expandReportDbgIssueMacro s.raw
|
||||
|
||||
/-- Returns all accumulated issues without clearing them. -/
|
||||
def getIssues : SymM (List MessageData) :=
|
||||
return (← get).issues
|
||||
|
||||
/--
|
||||
Runs `x` with a fresh issue context. Issues reported during `x` are
|
||||
prepended to the issues that existed before the call.
|
||||
-/
|
||||
def withNewIssueContext (x : SymM α) : SymM α := do
|
||||
let saved := (← get).issues
|
||||
modify fun s => { s with issues := [] }
|
||||
try x finally modify fun s => { s with issues := s.issues ++ saved }
|
||||
|
||||
/-- Similar to `Meta.isDefEqI`, but the result is cache using pointer equality. -/
|
||||
def isDefEqI (s t : Expr) : SymM Bool := do
|
||||
let key := (⟨s⟩, ⟨t⟩)
|
||||
@@ -230,4 +338,26 @@ def isDefEqI (s t : Expr) : SymM Bool := do
|
||||
modify fun s => { s with defEqI := s.defEqI.insert key result }
|
||||
return result
|
||||
|
||||
instance : Inhabited (SymM α) where
|
||||
default := throwError "<SymM default value>"
|
||||
|
||||
/-! ### SymExtension accessors -/
|
||||
|
||||
private unsafe def SymExtension.getStateCoreImpl (ext : SymExtension σ) (extensions : Array SymExtensionState) : IO σ :=
|
||||
return unsafeCast extensions[ext.id]!
|
||||
|
||||
@[implemented_by SymExtension.getStateCoreImpl]
|
||||
opaque SymExtension.getStateCore (ext : SymExtension σ) (extensions : Array SymExtensionState) : IO σ
|
||||
|
||||
def SymExtension.getState (ext : SymExtension σ) : SymM σ := do
|
||||
ext.getStateCore (← get).extensions
|
||||
|
||||
private unsafe def SymExtension.modifyStateImpl (ext : SymExtension σ) (f : σ → σ) : SymM Unit := do
|
||||
modify fun s => { s with
|
||||
extensions := s.extensions.modify ext.id fun state => unsafeCast (f (unsafeCast state))
|
||||
}
|
||||
|
||||
@[implemented_by SymExtension.modifyStateImpl]
|
||||
opaque SymExtension.modifyState (ext : SymExtension σ) (f : σ → σ) : SymM Unit
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
@@ -944,6 +944,7 @@ def synthInstance (type : Expr) (maxResultSize? : Option Nat := none) : MetaM Ex
|
||||
| none => throwFailedToSynthesize type)
|
||||
(fun _ => throwFailedToSynthesize type)
|
||||
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_synth_pending]
|
||||
private def synthPendingImp (mvarId : MVarId) : MetaM Bool := withIncRecDepth <| mvarId.withContext do
|
||||
let mvarDecl ← mvarId.getDecl
|
||||
|
||||
@@ -206,7 +206,7 @@ def handleApp : Simproc := fun e => do
|
||||
match fn with
|
||||
| .const constName _ =>
|
||||
if (← isCbvOpaque constName) then
|
||||
return (← tryCbvTheorems e).markAsDone
|
||||
return markAsDoneIfFailed <| ← tryCbvTheorems e
|
||||
let info ← getConstInfo constName
|
||||
tryCbvTheorems <|> (guardSimproc (fun _ => info.hasValue) handleConstApp) <|> reduceRecMatcher <| e
|
||||
| .lam .. => betaReduce e
|
||||
@@ -215,7 +215,7 @@ def handleApp : Simproc := fun e => do
|
||||
def handleOpaqueConst : Simproc := fun e => do
|
||||
let .const constName _ := e | return .rfl
|
||||
if (← isCbvOpaque constName) then
|
||||
return (← tryCbvTheorems e).markAsDone
|
||||
return markAsDoneIfFailed <| ← tryCbvTheorems e
|
||||
return .rfl
|
||||
|
||||
def foldLit : Simproc := fun e => do
|
||||
|
||||
@@ -108,4 +108,8 @@ public partial def getListLitElems (e : Expr) (acc : Array Expr := #[]) : Option
|
||||
| List.cons _ a as => getListLitElems as <| acc.push a
|
||||
| _ => none
|
||||
|
||||
public def markAsDoneIfFailed : Result → Result
|
||||
| .rfl _ cd => .rfl true cd
|
||||
| .step e h d cd => .step e h d cd
|
||||
|
||||
end Lean.Meta.Tactic.Cbv
|
||||
|
||||
@@ -10,18 +10,18 @@ import Lean.Meta.Transform
|
||||
public section
|
||||
namespace Lean.Meta
|
||||
|
||||
def delta? (e : Expr) (p : Name → Bool := fun _ => true) : CoreM (Option Expr) :=
|
||||
def delta? (e : Expr) (p : Name → Bool := fun _ => true) (allowOpaque := false) : CoreM (Option Expr) :=
|
||||
matchConst e.getAppFn (fun _ => return none) fun fInfo fLvls => do
|
||||
if p fInfo.name && fInfo.hasValue && fInfo.levelParams.length == fLvls.length then
|
||||
let f ← instantiateValueLevelParams fInfo fLvls
|
||||
if p fInfo.name && fInfo.hasValue (allowOpaque := allowOpaque) && fInfo.levelParams.length == fLvls.length then
|
||||
let f ← instantiateValueLevelParams fInfo fLvls (allowOpaque := allowOpaque)
|
||||
return some (f.betaRev e.getAppRevArgs (useZeta := true))
|
||||
else
|
||||
return none
|
||||
|
||||
/-- Low-level delta expansion. It is used to implement equation lemmas and elimination principles for recursive definitions. -/
|
||||
def deltaExpand (e : Expr) (p : Name → Bool) : CoreM Expr :=
|
||||
def deltaExpand (e : Expr) (p : Name → Bool) (allowOpaque := false) : CoreM Expr :=
|
||||
Core.transform e fun e => do
|
||||
match (← delta? e p) with
|
||||
match (← delta? e p (allowOpaque := allowOpaque)) with
|
||||
| some e' => return .visit e'
|
||||
| none => return .continue
|
||||
|
||||
|
||||
@@ -347,11 +347,13 @@ partial def foldAndCollect (oldIH newIH : FVarId) (isRecCall : Expr → Option E
|
||||
|
||||
if e.getAppArgs.any (·.isFVarOf oldIH) then
|
||||
-- Sometimes Fix.lean abstracts over oldIH in a proof definition.
|
||||
-- So beta-reduce that definition. We need to look through theorems here!
|
||||
if let some e' ← withTransparency .all do unfoldDefinition? e then
|
||||
return ← foldAndCollect oldIH newIH isRecCall e'
|
||||
else
|
||||
throwError "Internal error in `foldAndCollect`: Cannot reduce application of `{e.getAppFn}` in:{indentExpr e}"
|
||||
-- So delta-beta-reduce that definition. We need to look through theorems here!
|
||||
if let .const declName lvls := e.getAppFn then
|
||||
if let some cinfo := (← getEnv).find? declName then
|
||||
if let some val := cinfo.value? (allowOpaque := true) then
|
||||
let e' := (val.instantiateLevelParams cinfo.levelParams lvls).betaRev e.getAppRevArgs
|
||||
return ← foldAndCollect oldIH newIH isRecCall e'
|
||||
throwError "Internal error in `foldAndCollect`: Cannot reduce application of `{e.getAppFn}` in:{indentExpr e}"
|
||||
|
||||
match e with
|
||||
| .app e1 e2 =>
|
||||
@@ -742,6 +744,13 @@ partial def buildInductionBody (toErase toClear : Array FVarId) (goal : Expr)
|
||||
let b' ← buildInductionBody toErase toClear goal' oldIH newIH isRecCall (b.instantiate1 x)
|
||||
mkLambdaFVars #[x] b'
|
||||
|
||||
-- Unfold constant applications that take `oldIH` as an argument (e.g. `_f` auxiliary
|
||||
-- definitions from structural recursion), so that we can see their body structure.
|
||||
-- Similar to the case in `foldAndCollect`.
|
||||
if e.getAppFn.isConst && e.getAppArgs.any (·.isFVarOf oldIH) then
|
||||
if let some e' ← withTransparency .all (unfoldDefinition? e) then
|
||||
return ← buildInductionBody toErase toClear goal oldIH newIH isRecCall e'
|
||||
|
||||
liftM <| buildInductionCase oldIH newIH isRecCall toErase toClear goal e
|
||||
|
||||
/--
|
||||
|
||||
@@ -63,7 +63,6 @@ builtin_initialize registerTraceClass `grind.ematch.instance
|
||||
builtin_initialize registerTraceClass `grind.ematch.instance.assignment
|
||||
builtin_initialize registerTraceClass `grind.ematch.instance.delayed
|
||||
builtin_initialize registerTraceClass `grind.eqResolution
|
||||
builtin_initialize registerTraceClass `grind.issues
|
||||
builtin_initialize registerTraceClass `grind.simp
|
||||
builtin_initialize registerTraceClass `grind.split
|
||||
builtin_initialize registerTraceClass `grind.split.candidate
|
||||
|
||||
@@ -53,7 +53,7 @@ def mkEqCnstr (p : Poly) (h : EqCnstrProof) : RingM EqCnstr := do
|
||||
Returns the ring expression denoting the given Lean expression.
|
||||
Recall that we compute the ring expressions during internalization.
|
||||
-/
|
||||
private def toRingExpr? [Monad m] [MonadLiftT GrindM m] [MonadRing m] (e : Expr) : m (Option RingExpr) := do
|
||||
private def toRingExpr? [Monad m] [MonadLiftT GrindM m] [MonadLiftT Sym.SymM m] [MonadRing m] (e : Expr) : m (Option RingExpr) := do
|
||||
let ring ← getRing
|
||||
if let some re := ring.denote.find? { expr := e } then
|
||||
return some re
|
||||
@@ -67,7 +67,7 @@ private def toRingExpr? [Monad m] [MonadLiftT GrindM m] [MonadRing m] (e : Expr)
|
||||
Returns the semiring expression denoting the given Lean expression.
|
||||
Recall that we compute the semiring expressions during internalization.
|
||||
-/
|
||||
private def toSemiringExpr? [Monad m] [MonadLiftT GrindM m] [MonadSemiring m] (e : Expr) : m (Option SemiringExpr) := do
|
||||
private def toSemiringExpr? [Monad m] [MonadLiftT GrindM m] [MonadLiftT Sym.SymM m] [MonadSemiring m] (e : Expr) : m (Option SemiringExpr) := do
|
||||
let semiring ← getSemiring
|
||||
if let some re := semiring.denote.find? { expr := e } then
|
||||
return some re
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user