Compare commits

...

138 Commits

Author SHA1 Message Date
Lean stage0 autoupdater
7f424b371e chore: update stage0 2026-03-26 22:47:08 +00:00
Henrik Böving
d56424b587 feat: weak_specialize annotations (#13138)
This PR introduces the `weak_specialize` attribute. Unlike the
`nospecialize` attribute it does not
block specialization for parameters marked with this type completely.
Instead, `weak_specialize`
parameters are only specialized for if another parameter provokes
specialization. If no such
parameter exists, they are treated like `nospecialize`.
2026-03-26 21:58:52 +00:00
Henrik Böving
144db355ea fix: rebootstrap cache in github CI (#13143)
The old approach isn't smart enough to trick the lake cache anymore.
Making an explicit
update-stage0 commit will make it work again.
2026-03-26 20:50:03 +00:00
Lean stage0 autoupdater
0975b7136a chore: update stage0 2026-03-26 16:38:25 +00:00
Wojciech Różowski
ccef9588ae feat: add further cbv annotations (#13135)
This PR adds several `cbv_opaque` and `cbv_eval` annotations to the
standard library.
2026-03-26 14:55:40 +00:00
Garmelon
a8bbc95d9f chore: remove lean4checker from release repos (#13121)
Lean4checker has been merged into this repository and is no longer a
standalone repo.
2026-03-26 12:15:37 +00:00
Sebastian Graf
a54eafb84f refactor: decouple solve from grind in sym-based mvcgen (#13133)
This PR refactors the sym-based VCGen (`tests/bench/mvcgen/sym`) to
separate concerns between
goal decomposition and VC discharge, following the architecture of
loom2's `mvcgen'`.

- `solve` now operates on plain `MVarId` with no knowledge of grind,
returning `List MVarId`
  in `SolveResult.goals`.
- `work` handles grind E-graph internalization: after `solve` returns
multiple subgoals, it
calls `processHypotheses` on the parent goal to share context before
forking.
- `emitVC` dispatches on a new `PreTac` enum (`.none`, `.grind`,
`.tactic`) to try solving
each VC, replacing the previous inline grind logic and post-hoc tactic
loop in the elaborator.
- The redundant `WorkItem` wrapper (which duplicated `Grind.Goal`'s
`mvarId`) is removed; the
  worklist operates directly on `Grind.Goal`.
- `GrindContext` is replaced by `PreTac` + `hypSimpMethods` fields in
`VCGen.Context`, cleanly
  separating hypothesis simplification from the discharge strategy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 11:19:08 +00:00
Markus Himmel
6f2745d88b feat: verification of backwards string patterns (#13129)
This PR implements verification infrastructure for backwards patterns
that is analogous to the existing infrastructure for forward patterns.
Based on this it adds verification for the `skipSuffix?`, `endsWith` and
`dropSuffix?` functions on strings.

To enable this, we add some supporting theory about `String.slice`
(that's a lowercase `s`) and `String.Pos.prev`.
2026-03-26 08:44:47 +00:00
Eric Wieser
25c71d91aa feat: add rfl lemmas about ExceptCpsT.runK (#12912)
This PR adds trivial lemmas about `ExceptCpsT.runK` to match the
existing lemmas about `.run`.
2026-03-26 08:13:20 +00:00
Leonardo de Moura
db491ddd35 refactor: move issue tracker from grind to SymM (#13125)
This PR moves the issue tracking infrastructure from `GrindM` to `SymM`.
Issues can occur in different places within a `sym =>` block (e.g.,
during
arithmetic normalization, simplification), not just during `grind`
invocations. Moving them to `SymM` makes them available to all modules
operating within the symbolic computation framework.

- `Sym.reportIssue`: adds an issue to the `SymM` state
- `Sym.getIssues`: retrieves accumulated issues
- `Sym.withNewIssueContext`: saves/restores the issue list around a
  computation, used at grind entry points to isolate per-invocation
  issues while preserving them in the outer context
- `GrindM.State.issues` removed; `Grind.reportIssue` delegates to
`Sym.reportIssue`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 02:27:27 +00:00
Wojciech Różowski
e8c3485e08 fix: cbv getting stuck after a rewrite of cbv_opaque function (#13122)
This PR fixes `cbv` tactic getting stuck after rewriting functions
marked with `cbv_opaque` that have a `cbv_eval` lemma registered.
2026-03-25 18:13:13 +00:00
Sebastian Graf
dee571e13b chore: revert mvcgen witnesses syntax (#12882) (#13120)
This PR reverts the `mvcgen witnesses` syntax addition and undoes the
back compat hack in `elabMVCGen`.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:56:04 +00:00
Sebastian Graf
51f67be2bd chore: remove unnecessary level normalization from Sym-based mvcgen (#13119)
This PR removes level normalization from Sym-based mvcgen that is
unnecessary after #12923.
2026-03-25 16:06:57 +00:00
Sebastian Ullrich
c77f2124fb fix: include modPkgExt in .ir file exports (#13118)
This PR fixes an incompatibility of `--load-dynlib` with the module
system.

When a transitive non-public import's data came from the `.ir` file
instead of `.olean`, `getModulePackageByIdx?` returned `none`, producing
wrong symbol names (e.g., `initialize_Batteries_Data_UInt` instead of
`initialize_batteries_Batteries_Data_UInt`), leading to `dlsym` failures
and double-registration errors.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:49:42 +00:00
Lean stage0 autoupdater
d634f80149 chore: update stage0 2026-03-25 15:45:01 +00:00
Sebastian Graf
40cdec76c5 chore: revert @[mvcgen_witness_type] attribute (#12882) (#13111)
This PR reverts #12882 which added the `@[mvcgen_witness_type]` tag
attribute and `witnesses` section to `mvcgen`. Théophile Wallez
confirmed he doesn't need this feature and can get by with `invariants`,
so there is no use in having it.

The actual `mvcgen` syntax needs to be adjusted after a stage0 update in
order for `elabMVCGen` to cope with both old and new syntax.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 14:38:59 +00:00
Lean stage0 autoupdater
4227765e2b chore: update stage0 2026-03-25 14:58:54 +00:00
Henrik Böving
438d1f1fe1 perf: reading from persistent values should count as borrowing (#13116)
This PR ensures that reads from constants count as borrows in the eyes
of the borrow inference analysis. This reduces RC pressure in the
presence of constant reads.
2026-03-25 12:22:00 +00:00
Kim Morrison
4786e082dc doc: update inferInstanceAs docstring and rename normalizeInstance to wrapInstance (#13115)
This PR updates the `inferInstanceAs` docstring to reflect current
behavior: it requires an
expected type from context and should not be used as a simple
`inferInstance` synonym. The
old example (`#check inferInstanceAs (Inhabited Nat)`) no longer works,
so it's replaced
with one demonstrating the intended transport use case.

Additionally, renames `InstanceNormalForm.lean` to `WrapInstance.lean`,
`normalizeInstance`
to `wrapInstance`, and the trace class `Meta.instanceNormalForm` to
`Meta.wrapInstance`,
removing the "instance normal form" terminology from both documentation
and code.

Context:
https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/inferInstanceAs.20is.20broken/near/581449313

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 02:20:49 +00:00
Kim Morrison
bd5fb4e90c fix: handle duplicate nightly tag in scheduled CI runs (#13114)
This PR fixes the scheduled nightly CI run failing with `fatal: tag
'nightly-YYYY-MM-DD' already exists` when a manual `workflow_dispatch`
has already created today's nightly tag.

The scheduled path now uses the same `-revK` revision logic that the
manual re-release path already has: if `nightly-2026-03-24` exists, it
creates `nightly-2026-03-24-rev1` (and so on). The existing guard
against creating nightlies when HEAD has a non-nightly tag (e.g. a
release tag) is preserved.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 01:10:54 +00:00
Sebastian Graf
e60078db3b test: harden sym mvcgen bench script and tune benchmark sizes (#13107)
This PR fixes the sym mvcgen benchmark script and tunes input sizes.

**run_bench.sh**: Replace `| tee` with the `capture` helper from
`util.sh`.
Without `pipefail`, piping through `tee` masks non-zero exit codes from
`lake build`, so build failures (OOM, stack overflow) go unnoticed.

**Benchmark sizes**: Scale down inputs for benchmarks that exceeded the
2s
budget so each benchmark completes in 1-2s across its 3 linearly
increasing
inputs.

**Metric collision**: Copy `GetThrowSet.Goal` into a `GetThrowSetGrind`
namespace so the grind variant reports as `GetThrowSetGrind(n)` instead
of
colliding with `GetThrowSet(n)` in `measurements.jsonl`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:14:36 +00:00
Mac Malone
f7102363de fix: lake: race condition in Cache.saveArtifact (#13110)
This PR fixes a race condition in `Cache.saveArtifact` that caused
intermittent "permission denied" errors when two library facets (e.g.,
`static` and `static.export`) produce artifacts with the same content
hash and attempt to cache them concurrently.

The race occurs because `saveArtifact` checks `cacheFile.pathExists`,
then writes the file and makes it read-only. When two tasks race past
the existence check, the second task's write fails because the first
task already created the file and set it to read-only. On Linux, this is
common for `static` vs `static.export` since both resolve to the same
`coExport` object files, producing byte-identical archives.

The fix introduces `writeFileIfNew` and `writeBinFileIfNew` helpers that
use `O_CREAT | O_EXCL` (via `IO.FS.Mode.writeNew`) to atomically
create-or-skip, eliminating the race window. For the binary path, hard
link `alreadyExists` errors are also handled explicitly to avoid an
unnecessary copy fallback.

Additionally, `IO.setAccessRights` for the cache file is moved outside
the `unless pathExists` block so that permissions are always enforced,
and the `getMTime` call no longer silently swallows errors.

🤖 Prepared with Claude Code
2026-03-24 19:05:56 +00:00
Markus Himmel
ce073771b1 feat: String.drop lemmas (#13109)
This PR adds lemmas about the `String` operations `drop`, `dropEnd`,
`take`, `takeEnd`.
2026-03-24 17:51:06 +00:00
Markus Himmel
dec394d3a4 feat: lemmas about String.Pos.nextn (#13106)
This PR verifies `String.Pos.nextn` by providing the low-level API
`nextn_zero`/`nextn_add_one` as well as a `Splits` lemma.

The `Splits` lemma trivially implies, for a string `s`, the statement
`(s.drop n).copy.toList = s.toList.drop n`, to be included in a later
PR.
2026-03-24 16:12:57 +00:00
Markus Himmel
6457e3686f feat: lemmas for String.front? (#13105)
This PR proves `theorem front?_eq {s : String} : s.front? =
s.toList.head?` and related results.
2026-03-24 14:38:27 +00:00
Lean stage0 autoupdater
c14fa66068 chore: update stage0 2026-03-24 14:42:18 +00:00
Henrik Böving
d0aa7d2faa perf: mark inhabited arguments to extern as borrowed (#13094)
This PR marks the `Inhabited` arguments of all functions in core marked
as `extern` as borrowed
(panicking array accessors and `panic!` itself). This in turn causes a
transitive effect throughout
the codebase and promotes most, if not all, `Inhabited` arguments to
functions to borrowed.
2026-03-24 13:54:06 +00:00
JadAbouHawili
4117ceaf84 doc: typo fix for strict implicit binder (#13099)
This PR fixes a typo of implicit binders in doc-strings which was `{{
}}` instead of `⦃ ⦄`
2026-03-24 13:15:23 +00:00
Sebastian Graf
a824e5b85e test: add iota reduction via reduceRecMatcher? to sym-based mvcgen' (#13100)
This PR adds iota reduction to the sym-based `mvcgen'` tactic by calling
`reduceRecMatcher?` before falling back to the match split backward
rule.
When a matcher/recursor has a concrete discriminant, it is reduced
directly
instead of constructing and applying a splitting backward rule, which is
significantly faster for benchmarks like `MatchIota` (previously
`MatchSplit`)
where `loop n` unrolls into `n` nested matches with known `Nat`
discriminants.

The old `MatchSplit` test case (concrete discriminants) is renamed to
`MatchIota`
and a new `MatchSplit` test case with symbolic discriminants (matching
on state)
is added to keep exercising the split backward rule code path.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 12:52:01 +00:00
Sebastian Graf
83c6f6e5ac test: add mvcgen' with <tac> and mvcgen' with grind to sym-based VCGen (#12893)
This PR extends the sym-based `mvcgen'` tactic with two new modes:

1. `mvcgen' with <tac>`: run VCGen, then apply `<tac>` to each remaining
VC.
2. `mvcgen' with grind`: integrate grind into the VCGen loop for
incremental context internalization. Each VC inherits the parent's
E-graph state, so hypothesis processing is shared across sibling VCs,
avoiding O(n) re-internalization per VC.

The grind mode accepts the full grind configuration syntax (`mvcgen'
with grind (config := { ... }) [params]`).

A persistent `Sym.Simp` cache with a `reassocNatAdd` simproc normalizes
hypothesis types (e.g., `s + 1 + 1 + 1` → `s + 3`) before grind
internalization, achieving O(1) amortized simplification per VC.

Benchmark results for GetThrowSet (`mvcgen' with grind`):
- n=100: 400ms total, 180ms kernel
- n=250: 855ms total, 1.8s kernel
- n=500: 1.9s total, 11.8s kernel

Kernel checking time grows superlinearly and is the dominant cost at
larger sizes. This is a separate issue from VCGen performance.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 11:27:13 +00:00
Markus Himmel
9ffd748104 chore: generalize theorems about Nat.ofDigitChars (#13098)
This PR generalizes some theorems about `Nat.ofDigitChars` which were
needlessly restricted to base 10.
2026-03-24 11:01:20 +00:00
Henrik Böving
fd8d89853b feat: print more information for LCNF RC ops (#13097)
This PR makes the compiler traces contain more information about the
kind of `inc`/`dec` that are
being conducted (`persistent`, `checked` etc.)
2026-03-24 10:54:08 +00:00
Markus Himmel
0260c91d03 feat: lemmas comparing List.Cursor.pos to List.length (#13096)
This PR show the trivial result that given `c : l.Cursor`, we have that
`c.pos ≤ l.length`.
2026-03-24 10:40:03 +00:00
Henrik Böving
7ef25b8fe3 chore: remove dead code (#13093) 2026-03-24 09:07:47 +00:00
Lean stage0 autoupdater
50544489a9 chore: update stage0 2026-03-24 08:45:44 +00:00
Markus Himmel
e9a8b965aa fix: remove extra universe parameter fromStd.Iter.intercalateString (#13092)
This PR fixes an issue where `Std.Iter.joinString` had an extra universe
parameter because of an `IteratorLoop` instance which was actually
unnecessary.
2026-03-24 08:21:55 +00:00
Markus Himmel
0f277c72bf feat: verify String.join (#13091)
This PR adds the function `String.Slice.join` and adds lemmas about
`String.join` and `String.Slice.join`.
2026-03-24 07:42:41 +00:00
Markus Himmel
59ce52473a feat: Char.toNat_mk (#13090)
This PR adds the single lemma `Char.toNat_mk`.
2026-03-24 07:16:29 +00:00
Leonardo de Moura
2b55144c3f feat: add extensible state mechanism for SymM (#13080)
This PR adds `SymExtension`, a typed extensible state mechanism for
`SymM`,
following the same pattern as `Grind.SolverExtension`. Extensions are
registered at initialization time via `registerSymExtension` and provide
typed `getState`/`modifyState` accessors. Extension state persists
across
`simp` invocations within a `sym =>` block and is re-initialized on each
`SymM.run`.

This enables modules (e.g., the upcoming arithmetic normalizer) to
register persistent state without modifying `Sym.State` directly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 03:58:45 +00:00
Mac Malone
c381c62060 chore: use Lake remote cache in CI (#10880)
This PR alters the `Linux Lake` CI job to enable the Lake cache and
upload the builds results to the remote cache storage. It also adds a
`Linux Lake (Cached)` secondary build job which fetches a build from the
Lake remote cache (if possible) and tests it.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-03-24 00:06:19 +00:00
Sebastian Ullrich
e6df474dd9 chore: improve inferInstanceAs error message on missing expected type and back compat (#13051)
Co-authored-by: Kim Morrison <477956+kim-em@users.noreply.github.com>
2026-03-23 23:21:26 +00:00
Kim Morrison
e0de32ad48 fix: use declName? pattern for normalizeInstance meta marking (#13059)
This PR switches `normalizeInstance` from using `isMetaSection` to the
existing `declName?` pattern (already used by `unsafe` in
`BuiltinNotation.lean` and `private_decl%` in `BuiltinTerm.lean`) for
determining whether aux defs should be marked `meta`.

#13043 used `isMetaSection` to determine whether `normalizeInstance` aux
defs should be marked `meta`. This caused `deriving` in meta sections to
fail: the deriving handler doesn't mark the instance itself as meta, so
the non-meta instance couldn't access its meta-marked aux defs:

```
Invalid definition `instInhabitedLibraryNote`, may not access declaration
`instInhabitedLibraryNote._aux_1` marked as `meta`
```

The `declName?` pattern inherits meta status from the parent declaration
rather than the scope. This correctly handles both cases:
- **`inferInstanceAs`**: parent declaration is marked meta by
`processHeaders`, so `declName?.any (isMarkedMeta env)` is true and aux
defs are correctly marked meta
- **`deriving`**: `declName?` is `none` (the deriving handler runs
outside `withDeclName`), so `isMeta` is `false` and aux defs are not
marked meta — matching the instance itself, which the deriving handler
also does not mark meta

Found while adapting Batteries to nightly-2026-03-23.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 23:01:01 +00:00
Henrik Böving
fb1dc9112b perf: forward and backward borrow propagation is non-forced (#13066)
This PR changes the behavior of forward and backward projection
propagation in the context of user defined borrows. The reason to have
them be "forced" override (i.e. override user annotations as well) was
that a user annotated borrowed value can potentially flow into a
reset-reuse transitively through a projection and must thus have
accurate reference count. The reasons that this is no longer necessary
are:
1. Forward never had to be forced anyways, it can only affect the `z` in
`let z := oproj x i` which can't be annotated by a user
2. Backward is no longer necessary as the forward propagator for user
annotations prevents the reset-reuse insertion from working with values
that have user defined borrow annotations entirely.
2026-03-23 21:39:17 +00:00
Henrik Böving
86175bea00 perf: teach borrow inference about arrays (#13064)
This PR informs the borrow inference that if an `Array` is borrowed and
we index into it, the value we obtain is effectively a borrowed value as
well. This helps improve the ABI of operations that recurse on linked
structures containing arrays such as tries or persistent hash maps.
2026-03-23 18:10:50 +00:00
Mac Malone
9eb249e38c fix: lake: error on executables with duplicate root module names (#13028)
This PR adds a check that rejects Lake configurations where multiple
executables share the same root module name. Previously, Lake would
silently compile the root module once and link it into all executables,
producing identical binaries regardless of differing `srcDir` settings.

Lake (and Lean) rely on module names being unique within a package.
Rather than attempting to support duplicate module names, Lake now
produces a clear error at configuration load time, for both TOML and
Lean configuration files.

Closes #13013

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 18:10:10 +00:00
Joachim Breitner
b5036e4d81 doc: rewrite ReducibilityHints docstring (#13065)
This PR rewrites the docstring on `Lean.ReducibilityHints` to accurately
describe the
kernel's lazy delta reduction strategy: which side gets unfolded when
comparing two
definitions, how definitional height is computed, and how hints relate
to the
`@[reducible]`/`@[irreducible]` elaborator attributes.

The old docstring referenced a `selfOpt` flag that no longer exists and
contained a few
inaccuracies (e.g. `irrelevance` instead of `irreducible`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 17:02:27 +00:00
Lean stage0 autoupdater
fb1eb9aaa7 chore: update stage0 2026-03-23 15:31:56 +00:00
Henrik Böving
33e63bb6c3 perf: mark ReaderT context argument as borrow (#12942)
This PR marks the context argument of `ReaderT` as borrowed, causing a
wide spread of useful borrow annotations throughout the entire meta
stack which reduces RC pressure. This introduces a crucial new behavior:
When modifying `ReaderT` context, e.g. through `withReader` this will
almost always cause an allocation. Given that the `ReaderT` context is
frequently used in a non-linear fashion anyways we think this is an
acceptable behavior.
2026-03-23 14:45:52 +00:00
Garmelon
482d7a11f2 chore: handle empty dirs more gracefully (#13062)
This PR demotes the cmake error to a warning because it tends to get
triggered by a combination of add_dir_of_test_dirs and git checkout not
removing untracked files.
2026-03-23 14:23:47 +00:00
Lean stage0 autoupdater
aef0cea683 chore: update stage0 2026-03-23 14:42:07 +00:00
Joachim Breitner
720cbd6434 feat: theorems are opaque (#12973)
This PR makes theorems opaque in almost all ways, including in the
kernel.

Already now, because of proof irrelevance, theorems are almost never
unfolded. Furthermore, the import handling allows conflicting theorem
declaration with same type and different values. This is sound, but
would be confusing if the value, and thus the import order, matters for
completeness.

So with this change, a `theorem` becomes more like an `opaque`: It has a
value (for soundness), but it is never unfolded during reduction or type
checking. There are still some places in meta code that have to peek
into theorems (e.g. `FunInd`, wfrec processing), but these are code
transformations, not reduction.

One place where reducing proofs is necessary is reducing `Acc.rec`
eliminating into Type. With this change, all proofs that need to be
reducable that way have to be `def`, not `theorem`. This is already the
case due to the module system. This does not affect uses of `Acc` via
well-founded recursion, because that has already been made opaque in
#5182. This moves the reduction behavior of `Acc.rec` further into the
“supported by the theory but not relied upon by regular Lean“ corner.

Fixes #12804

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:57:07 +00:00
Joachim Breitner
26ad4d6972 feat: name the functional argument to brecOn in structural recursion (#12987)
This PR extracts the functional (lambda) passed to `brecOn` in
structural
recursion into a named `_f` helper definition (e.g. `foo._f`), similar
to
how well-founded recursion uses `._unary`. This way the functional shows
up
with a helpful name in kernel diagnostics rather than as an anonymous
lambda.

The `_f` definition is added with `.abbrev` kernel reducibility hints
and
the `@[reducible]` elaborator attribute, so the kernel unfolds it
eagerly
after `brecOn` iota-reduces. For inductive predicates, the previous
inline
lambda behavior is kept.

To ensure that parent definitions still get the correct reducibility
height
(since `getMaxHeight` ignores `.abbrev` definitions), each `_f`'s body
height is registered via a new `defHeightOverrideExt` environment
extension.
`getMaxHeight` checks this extension for all definitions, making the
height
computation transparent to the extraction.

This change improves code size (a bit). It may regress kernel reduction
times,
especially if a function defined by structural recursion is used in
kernel reduction
proofs on the hot path. Functions defined by structural recursion are
not particularly
fast to reduce anyways (due to the `.brecOn` construction), so already
now it may be
worth writing a kernel-reduction-friendly function manually (using the
recursor directly,
avoiding overloaded operations). This change will guide you in knowing
which function to
optimize.


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 13:40:18 +00:00
Markus Himmel
4a17b2f471 chore: lemmas about BEq on List String.Slice (#13061)
This PR adds lemmas about `BEq` on `List String.Slice`.

We show `(l == l') = false ↔ l.map copy ≠ l'.map copy` and deduce a
`BEq` version of the theorem about "intercalate-then-split":
```lean
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
```
2026-03-23 13:34:46 +00:00
Markus Himmel
fcdd9d1ae8 feat: EquivBEq and LawfulHashable for String.Slice (#13058)
This PR adds `EquivBEq` and `LawfulHashable` instances to
`String.Slice`.

To this end, we redefine `String.Slice.hash`, which used to be
completely opaque, to be defined as `String.hash s.copy` (and then
`String.hash` remains opaque). We add tests that the `lean_slice_hash`
and `lean_string_hash` functions do indeed satisfy this relationship.

Of course, it would be even better to have a streaming MurmurHash64A
implementation in core that could be used to implement both of these so
that we can avoid the `opaque`, but that is a project for another day.
2026-03-23 11:10:00 +00:00
Lean stage0 autoupdater
47427f8c77 chore: update stage0 2026-03-23 10:33:50 +00:00
Henrik Böving
08595c5f8f fix: interaction of extern annotations and calls to functions with borrowed parameters (#13052)
This PR fixes a bug in the borrow inference in connection with `export`
annotations.

Previously parameters of `export` functions were presumed as owned from
the beginning of the
analysis. However, they were not added into the set of owned parameters
and thus sometimes failed to
force necessary changes to borrowedness of other values that the
parameters flowed into.
2026-03-23 10:03:26 +00:00
Markus Himmel
019b104a7d chore: variants of String.toNat? lemmas (#13057)
This PR adds some variants of existing lemmas about `String.toNat?` and
friends.
2026-03-23 09:32:32 +00:00
Markus Himmel
2e421c9970 feat: Std.Iter.intercalateString (#13056)
This PR adds the functions `Std.Iter.joinString` and
`Std.Iter.intercalateString`.

`it.intercalateString s` is a more efficient version of
`s.copy.intercalate (it.toList.map toString)`, and we have a lemmas
proving exactly that.
2026-03-23 09:28:42 +00:00
Markus Himmel
e381960614 feat: simproc for turning "c" into String.singleton 'c' (#13054)
This PR adds the simproc String.reduceToSingleton`, which is disabled by
default and turns `"c"` into `String.singleton 'c'`.

Recall that the simproc `reduceSingleton`, which does the reverse, is
part of the default `simp` set.
2026-03-23 09:06:49 +00:00
Sebastian Ullrich
346c9cb16a chore: CI: bump git cache to 5GB (#13053) 2026-03-23 08:58:35 +00:00
Kim Morrison
189cea9f80 chore: check for empty PRs in CI (#12956)
This PR adds a CI check that fails when a PR introduces no changes
compared to its base branch. This catches cases where a duplicate PR is
queued for merge after an identical PR has already landed (as happened
with https://github.com/leanprover/lean4/pull/12876 and
https://github.com/leanprover/lean4/pull/12877).

The check is added as a second job in the existing `check-stage0.yml`
workflow, which already has the same trigger conditions and git setup
pattern. On `pull_request` events it diffs against the merge base; on
`merge_group` events it diffs `HEAD^1..HEAD` (the PR's contribution to
the synthetic merge commit). Note that batched merge groups are treated
as a unit — if the entire group is non-empty the check passes, which is
the right behaviour for lean4's typical single-PR queuing.

🤖 Prepared with Claude Code

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 03:09:52 +00:00
Kim Morrison
b9028fa6e9 fix: handle lean4-nightly toolchain prefix in release checklist (#12865)
This PR fixes a crash in release_checklist.py when a repository uses the
`leanprover/lean4-nightly:` toolchain prefix (e.g. leansqlite). The
`is_version_gte` function only checked for `leanprover/lean4:nightly-`
but
not `leanprover/lean4-nightly:`, causing a `ValueError: invalid literal
for
int() with base 10: 'nightly'` when trying to parse the version.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 03:01:58 +00:00
Leonardo de Moura
0c0edcc96c feat: add control and arrow_telescope simproc DSL primitives (#13048)
This PR adds two new `sym_simproc` DSL primitives and helper grind-mode
tactics.

Simproc primitives:
- `control` — simplifies control-flow expressions (`if-then-else`,
  `match`, `cond`, `dite`), visiting only conditions and discriminants.
  Intended as a `pre` simproc.
- `arrow_telescope` — simplifies arrow telescopes
  (`p₁ → p₂ → ... → q`) without entering binders. Intended as a `pre`
  simproc.

Grind-mode tactics:
- `show_goals` — displays pending goals (non-terminal `trace_state` for
  grind mode)
- `exact e` — macro delegating to `tactic => exact e`

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 02:19:13 +00:00
Leonardo de Moura
9f4db470c4 feat: add permutation theorem support to Sym.simp (#13046)
This PR prevents `Sym.simp` from looping on permutation theorems like
`∀ x y, x + y = y + x`.

- Add `perm : Bool` field to `Theorem`
- Add `isPerm` that checks if LHS and RHS have the same structure with
  pattern variables (de Bruijn indices) rearranged via a consistent
  bijection. Uses `ReaderT` (offset for binder entry), `StateT`
  (forward/backward maps), `ExceptT` (failure).
- Compute `perm` in `mkTheoremFromDecl` / `mkTheoremFromExpr`
- In `Theorem.rewrite`, when `perm` is true, only apply the rewrite if
  the result is strictly less than the input (using `acLt`)
- Tests include the classic AC normalization stress test with
  `add_comm`, `add_assoc`, `add_left_comm`

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:22:36 +00:00
Kim Morrison
8ae39633d1 fix: mark auxiliary definitions from normalizeInstance as meta (#13043)
This PR fixes a bug where `inferInstanceAs` and the default `deriving`
handler, when used inside a `meta section`, would create auxiliary
definitions (via `normalizeInstance`) that were not marked as `meta`.
This caused the compiler to reject the parent `meta` definition with:

```
Invalid `meta` definition `instEmptyCollectionNamePrefixRel`, `instEmptyCollectionNamePrefixRel._aux_1` not marked `meta`
```

The fix adds an `isMeta` parameter to `normalizeInstance` that is
propagated from the elaboration context (`isMarkedMeta` for
`inferInstanceAs`, `Scope.isMeta` for the deriving handler), and marks
each auxiliary definition created by `mkAuxDefinition` as `meta` when
appropriate.

Found while adapting Mathlib to
https://github.com/leanprover/lean4/pull/12897.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:41:57 +00:00
Leonardo de Moura
cffacf1b10 feat: support local hypotheses in simp [h] for sym => mode (#13042)
This PR extends the `simp` tactic in `sym =>` mode to support local
hypotheses in the extra theorem list.

`simp myVariant [h]` now resolves `h` against the local context first,
falling back to global constants. Local hypotheses are converted to
rewrite rules via `mkTheoremFromExpr`, which applies the `eq_true`/
`eq_false`/`propext` adapter from #13041.

- Add `ExtraTheorem` inductive (`.const` / `.fvar`) for cache keying
- Add `resolveExtraTheorems` that checks the local context before
globals
- Update `addExtraTheorems`, `mkDefaultMethods`, `elabVariant`
signatures

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:50:31 +00:00
Leonardo de Moura
b858d0fbf2 feat: adapt non-equality theorems in mkTheoremFromDecl (#13041)
This PR extends `mkTheoremFromDecl` and `mkTheoremFromExpr` to handle
theorems whose conclusion is not an equality, enabling `Sym.simp` to use
a broader class of lemmas as rewrite rules.

Adaptations:
- `¬ p` → `p = False` via `eq_false`
- `p ↔ q` → `p = q` via `propext`
- `p` (proposition) → `p = True` via `eq_true`

Conjunctions (`p ∧ q`) are not handled here since the `SymM` E-graph
aggressively splits them via case analysis.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:34:37 +00:00
Leonardo de Moura
9a3678935d feat: add sanity checks to register_sym_simp (#13040)
This PR adds validation to the `register_sym_simp` command:

- Reject duplicate variant names
- Validate `pre`/`post` syntax by elaborating them via `elabSymSimproc`
  in a minimal `GrindTacticM` context, catching unknown theorem names
  and unknown theorem set references at registration time

Adds `withGrindTacticM` helper for running `GrindTacticM` from
`CommandElabM`,
and `validateOptionSimprocSyntax` for optional syntax validation.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:41:30 +00:00
Chris Henson
c9708e7bd7 doc: update docstring of Lean.Parser.Tactic.Grind.first (#12998)
This PR updates the docstring for `Lean.Parser.Tactic.Grind.first`,
which changed syntax in #10828.
2026-03-22 16:54:33 +00:00
Leonardo de Moura
8f6411ad57 feat: add interactive simp tactic for sym => mode (#13039)
This PR adds the `simp` tactic to the `sym =>` interactive mode,
completing
the `Sym.simp` interactive infrastructure.

The `simp` tactic supports:
- `simp` — default variant with `@[sym_simp]` theorem set, `simpControl
>> simpArrowTelescope` pre, `evalGround >> thms.rewrite` post
- `simp myVariant` — named variant registered via `register_sym_simp`
- `simp [thm₁, thm₂]` — default variant with extra rewrite theorems
appended to `post`
- `simp myVariant [thm₁, thm₂]` — named variant with extra theorems

Per-variant persistent caching: each unique (variant name, extra theorem
list)
combination gets its own `Sym.Simp.State` cache, shared across
invocations
within a `sym =>` block. Test 10 verifies cache hits using
`trace.sym.simp.debug.cache`.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 15:55:05 +00:00
Kyle Miller
ae0d4e3ac4 chore: remove change ... with tactic syntax (#13029)
This PR removes the unused `change ... with` tactic syntax.

It's been asked about a number of times recently, but it was never
implemented. In PR #6018 we decided it was a Lean-3-ism.
2026-03-22 15:25:21 +00:00
Sebastian Ullrich
4bf7fa7447 chore: fix build after rebootstrap 2026-03-22 13:25:46 +01:00
Sebastian Ullrich
40558129cf chore: update stage0 2026-03-22 13:25:46 +01:00
Sebastian Ullrich
88b746dd48 feat: unfold and rewrap instances in inferInstanceAs and deriving
This PR adjusts the results of `inferInstanceAs` and the `def` `deriving` handler to conform to recently strengthened restrictions on reducibility. This change ensures that when deriving or inferring an instance for a semireducible type definition, the definition's RHS is not leaked when the instance is reduced at lower than semireducible transparency.

More specifically, given the "source type" and "target type" (the given and expected type for `inferInstanceAs`, the right-hand side and applied left-hand side of the `def` for `deriving`), we synthesize an instance for the source type and then unfold and rewrap its components (fields, nested instances) as necessary to make them compatible with the target type. The individual steps are represented by the following options, which all default to enabled and can be disabled to help with porting:
- `backward.inferInstanceAs.wrap`: master switch for instance adjustment in both `inferInstanceAs` and the default `deriving` handler
- `backward.inferInstanceAs.wrap.reuseSubInstances`: reuse existing instances for the target type for sub-instance fields to avoid non-defeq instance diamonds
- `backward.inferInstanceAs.wrap.instances`: wrap non-reducible instances in auxiliary definitions
- `backward.inferInstanceAs.wrap.data`: wrap data fields in auxiliary definitions (proof fields are always wrapped)

This PR is an extension and rewrite of prior work in Mathlib: https://github.com/leanprover-community/mathlib4/pull/36420

Last(?) part of fix for #9077

🤖 Prepared with Claude Code

# Breaking changes

Proofs that relied on the prior "defeq abuse" of these instance or that depended on their specific structure may need adjustments. As `inferInstanceAs A` now needs to know the source and target types exactly before it can continue, it cannot be used anymore as a synonym for `(inferInstance : A)`, use the latter instead when source and target type are identical.
2026-03-22 13:25:46 +01:00
Kim Morrison
2a25e4f3ae fix: nightly dispatch creates today's tag when retrying a failed nightly (#13035)
This PR fixes the `workflow_dispatch` path for nightly releases.
Previously,
when a scheduled nightly failed (so no tag was created) and someone
manually
re-triggered the workflow, it would find the most recent existing
nightly tag
(from a previous day) and create a `-revK` revision of that old tag. Now
it
checks if today's nightly tag exists: if not, it creates it directly; if
it
already exists, it creates a `-revK` revision as before.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:30:36 +00:00
Leonardo de Moura
13f8ce8492 feat: add register_sym_simp command and SymSimpVariant infrastructure (#13034)
This PR adds the `register_sym_simp` command for declaring named
`Sym.simp`
variants with `pre`/`post` simproc chains and optional config overrides.

```
register_sym_simp myVariant where
  pre  := telescope
  post := ground >> rewrite [thm1, thm2] with self
  maxSteps := 50000
```

- `SymSimpVariant` structure storing `pre?`/`post?` syntax (elaborated
at use
  time in `GrindTacticM`) and `Config` overrides
- `SimpleScopedEnvExtension` for persistent cross-module variant storage
- `register_sym_simp` command syntax with `sym_simp_field` category
- Command elaborator with syntax quotation matching and duplicate field
detection
- `getSymSimpVariant?` lookup function
- `deriving Inhabited` on `Simp.Config` for extension support

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 04:30:58 +00:00
Leonardo de Moura
dbfd0d35f2 fix: simp +arith and grind loop on normalized Int equalities (#13033)
This PR adds `r == e` guards to the `norm_eq_var` and
`norm_eq_var_const` branches of `Int.Linear.simpEq?`. Without these
guards, `simpEq?` returns a non-trivial proof for already-normalized
equations like `x = -1`, causing `exists_prop_congr` to fire repeatedly
and build an infinitely growing term.

The existing `Nat.simpCnstrPos?` already had the equivalent guard (`if r
!= lhs then ... else return none`).

Closes #12812

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 04:04:56 +00:00
Leonardo de Moura
b4c4f265aa fix: grind ring solver OOM on high-degree polynomials (#13032)
This PR fixes #12842 where `grind` exhausts memory on goals involving
high-degree polynomials such as `(x + y)^2 = x^128 + y^2` over `Fin 2`.

The root cause is that `incSteps` in the ring module's Groebner basis
engine increments the step counter by 1 per simplification, regardless
of polynomial size. For high-degree polynomials (e.g., degree 128),
intermediate results can have hundreds of terms, making each operation
extremely expensive — but the flat counter cannot catch this before
memory is exhausted.

The fix weights each step by `Poly.numTerms` of the result polynomial
and increases the default `ringSteps` from 10 000 to 100 000 to
accommodate the new cost model.

Note: the example from #12842 will not be *proved* by `grind` even after
this fix, because Frobenius / Fermat's little theorem (`x^p = x` in `Fin
p`) is not available in core Lean. The long-term plan is to introduce a
type class in core stating this property, with an instance provided by
Mathlib, so that `grind` can exploit it when Mathlib is imported.

Closes  #12842

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 02:52:44 +00:00
Leonardo de Moura
e848039ba9 feat: add built-in sym_simproc and sym_discharger elaborators (#13031)
This PR adds the built-in elaborators for the `sym_simproc` and
`sym_discharger` DSL syntax categories introduced in #13026.

Simproc elaborators (`@[builtin_sym_simproc]`):
- `ground` → `evalGround`
- `telescope` → `simpTelescope`
- `self` → `simp` (recursive simplification)
- `none` → identity (no simplification)
- `rewrite setName [with discharger]` → named theorem set rewriting
- `rewrite [thm₁, ...] [with discharger]` → inline theorem rewriting
- `>>` → `andThen` combinator
- `<|>` → `orElse` combinator

Discharger elaborators (`@[builtin_sym_discharger]`):
- `self` → `dischargeSimpSelf`
- `none` → `dischargeNone`

Note: `orElse` requires a fully-qualified attribute name
(`Lean.Parser.Sym.Simp.orElse`) to avoid a name resolution conflict
where the bare `orElse` resolves to a different syntax node kind.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:39:12 +00:00
Leonardo de Moura
9fa1a252f2 fix: nondeterministic crash in grind congruence closure (#13027)
This PR fixes a nondeterministic crash in `grind` caused by a
`BEq`/`Hashable` invariant
violation in the congruence table. `congrHash` uses each expression's
own `funCC` flag to
compute its hash (one-level decomposition for `funCC = true`, full
recursive decomposition
for `funCC = false`), but `isCongruent` only checked the stored
expression's flag. When two
expressions with mismatched `funCC` flags accidentally hash-collided
(via pointer-based
`ptrAddrUnsafe` hashing), `isCongruent` could declare them congruent
despite different
argument counts, leading to an assertion failure in `mkCongrProof`.

The fix requires matching `funCC` flags in `isCongruent`. The PR also
fixes the debug
invariant checker (`checkParents`) to skip `funCC` parents and adds a
regression test for
funCC congruence.

Observed as a nondeterministic crash in Mathlib at
`Analysis/ODE/PicardLindelof.lean`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 22:54:58 +00:00
Lean stage0 autoupdater
1cf030c5d4 chore: update stage0 2026-03-21 23:14:34 +00:00
Leonardo de Moura
545f27956b feat: add sym_simproc and sym_discharger DSL syntax categories (#13026)
This PR adds the infrastructure for simproc and discharger DSLs used to
specify `pre`/`post` simproc chains and conditional rewrite dischargers
in `Sym.simp` variants.

**Syntax categories** (`src/Init/Sym/Simp/SimprocDSL.lean`):
- `sym_simproc` with primitives (`ground`, `telescope`, `rewrite`,
`self`, `none`) and combinators (`>>`, `<|>`)
- `sym_discharger` with primitives (`self`, `none`) for the `with`
clause of `rewrite`

**Elaboration attributes**
(`src/Lean/Elab/Tactic/Grind/SimprocDSL.lean`):
- `builtin_sym_simproc` / `sym_simproc` mapping syntax to `Syntax →
GrindTacticM Simproc`
- `builtin_sym_discharger` / `sym_discharger` mapping syntax to `Syntax
→ GrindTacticM Discharger`
- `elabSymSimproc`, `elabSymDischarger`, and `elabWithClause`
dispatchers

Built-in elaborators for each primitive/combinator will follow in a
subsequent PR after the stage0 update.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:29:25 +00:00
Leonardo de Moura
7897dc91e6 fix: grind fails to prove conjunction of independently provable goals (#13024)
This PR fixes an issue where `grind` could prove each conjunct
individually but failed on the conjunction. The root cause:
`solverAction`'s `.propagated` path calls `processNewFacts` which drains
the `newFacts` queue, but the resulting propagation cascade (congruence
closure, or-propagation, `propagateForallPropDown`) can call
`addNewRawFact`, enqueuing to the separate `newRawFacts` queue. These
raw facts were never drained.

The fix moves `Solvers.mkAction` from `Types.lean` to `Intro.lean` where
it can compose the core solver action with `assertAll`, unconditionally
draining `newRawFacts` after every solver step.

Closes #12581

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 17:04:41 +00:00
Sebastian Ullrich
98f5266407 chore: remove temp bootstrap code (#13021) 2026-03-21 16:41:14 +00:00
Sebastian Ullrich
90b5e3185b fix: export ABI mismatch (#13022) 2026-03-21 14:52:23 +00:00
Leonardo de Moura
973062e4e1 feat: add Sym.simp theorem set attributes (#13018)
This PR adds named theorem sets for `Sym.simp` with associated
attributes, following the same pattern as `Meta.simp`'s
`register_simp_attr`.

- `register_sym_simp_attr my_set` creates a named set with its own
`PersistentEnvExtension` and attribute
- `@[my_set] theorem ...` adds a rewrite theorem
- `@[my_set] def ...` adds equation theorems from the definition
- `builtin_initialize symSimpExtension` registers a default
`@[sym_simp]` set
- `getSymSimpTheorems` / `getSymSimpExtension?` retrieve theorem sets at
tactic time

New files:
- `Sym/Simp/Attr.lean`: attribute logic (`mkSymSimpAttr`,
`registerSymSimpAttr`)
- `Sym/Simp/RegisterCommand.lean`: `register_sym_simp_attr` macro

Tests:
- `tests/pkg/sym_simp_attr/`: package test with user-defined set
(`my_sym_simp`)
- `tests/elab/sym_simp_set.lean`: tests for the builtin `@[sym_simp]`
set
2026-03-21 03:53:39 +00:00
Lean stage0 autoupdater
4a62d4a79b chore: update stage0 2026-03-21 00:19:46 +00:00
Henrik Böving
e2120d85c0 feat: throw an error when export declarations have borrow annotations (#13017)
This PR ensures that when a declaration is marked with `@[export]`, the
compiler throws an error if
any of its arguments are marked as borrowed.
2026-03-20 23:22:52 +00:00
Lean stage0 autoupdater
e33d0d33da chore: update stage0 2026-03-20 19:39:29 +00:00
Henrik Böving
d2ecad2e91 perf: forward propagation of user defined borrrow annotations (#13001)
This PR introduces additional propagation mechanisms for user defined
borrows to make them have priority over reset-reuse opportunities.
2026-03-20 19:03:17 +00:00
Mac Malone
7097e37a1c feat: lake: improve artifact transfer errors (#13014)
This PR makes errors in `lake cache get` / `lake cache put` artifact
transfers more verbose, which helps with debugging. It also fixes an
issue with error reporting when downloading artifacts on demand.
2026-03-20 18:53:05 +00:00
Joachim Breitner
1362cc6041 refactor: simplify structural recursion elaboration (#13008)
This PR removes the custom `M`/`State` monad from structural recursion
elaboration, replacing it with plain `MetaM`. This simplifies the code
and makes the control flow more explicit, in preparation for #12987
which
introduces named `_f` auxiliary definitions for structural recursion.

Key changes:
- Remove `State`/`M` types from `Structural.Basic`, use `MetaM`
throughout
- Extract `withRecFunsAsAxioms` helper for adding recursive functions as
temporary axioms
- Split `tryAllArgs` into `findRecArgCandidates` (analysis) and
`tryCandidates` (backtracking execution)
- Move `withoutModifyingEnv` into each phase that needs it
- For inductive predicates, return matchers from `mkIndPredBRecOnF`
instead of accumulating in state
- Pass `fnTypes` explicitly to `mkBRecOnMotive` instead of re-inferring

This is a pure refactoring with no behavior changes (except matcher
numbering in `inductive_pred` test due to changed
`saveState`/`restoreState` boundaries).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 16:10:27 +00:00
Lean stage0 autoupdater
4d6accd55d chore: update stage0 2026-03-20 16:28:23 +00:00
Sebastian Ullrich
6f98a76d01 feat: stricter meta check for temporary programs in native_decide etc (#13005)
This PR further enforces that all modules used in compile-time execution
must be meta imported in preparation for enabling
https://github.com/leanprover/lean4/pull/10291

# Breaking changes

Metaprograms that call `compileDecl` directly may now need to call
`markMeta` first where appropriate, possibly based on the value of
`isMarkedMeta` of existing decls. `addAndCompile` should be split into
`addDecl` and `compileDecl` for this in order to insert the call in
between.
2026-03-20 15:51:18 +00:00
Sebastian Ullrich
609a05a90a feat: increase default stack size from 8MB to 1GB (#12971)
This PR increases Lean's default stack size, including for the main
thread of Lean executables, to 1GB.

As stack pages are allocated dynamically, this should not change the
memory usage of programs but can prevent them from stack overflowing.

The stack size (of any Lean thread) can now be customized via the
`LEAN_STACK_SIZE_KB` environment variable. `main` can be prevented from
running on a new thread by setting `LEAN_MAIN_USE_THREAD=0`, in which
case the default OS stack size management applies to the main thread
again.
2026-03-20 15:40:00 +00:00
Henrik Böving
511be304d7 feat: respect user provided borrow annotations (#12830)
This PR enables support for respecting user provided borrow annotations.
This allows user to mark arguments of their definitions or local
functions with `(x : @&Ty)` and have the borrow inference try its best
to preserve this annotation, thus potentially reducing RC pressure. Note
that in some cases this might not be possible. For example, the compiler
prioritizes preserving tail calls over preserving borrow annotations. A
precise reasoning of why the compiler chose to make its inference
decisions can be obtained with `trace.Compiler.inferBorrow`.

The implementation consists of two parts:
1. A propagator in ToLCNF. This is required because the elaborator does
not place the borrow annotations in the function binders themselves but
just in type annotations of let binders/global declarations while LCNF
expects them in the lambda variable binders themselves. Thus ToLCNF now
implements a (very weak but strong enough for this purpose) propagator
of the borrow annotations of a type annotation into the variable binders
of the term affected by the annotations
2. A weakening of the InferBorrow heuristic. It now has a set of
"forced" and "non-forced" reasons to mark a variable as owned instead of
borrowed. If a variable is user annotated as borrowed, it will only be
marked as owned if the reason is a forced one, e.g. preservation of tail
calls.
2026-03-20 14:28:17 +00:00
Sebastian Ullrich
0b9ad3fb8d chore: be consistent about setting [inline] before compilation (#12389)
Setting the attribute influences codegen of the decl itself
2026-03-20 13:46:23 +00:00
Lean stage0 autoupdater
ae7e551934 chore: update stage0 2026-03-20 14:18:09 +00:00
Sebastian Ullrich
8e6f2750da fix: namespace used in private import and current module vanishes dowstream (#12840)
This PR fixes an issue where the use of private imports led to unknown
namespaces in downstream modules.

Fixes #12833
2026-03-20 13:27:26 +00:00
Garmelon
492fda3bca chore: speed up test suite slightly (#12969)
This PR speeds up some benchmarks when run as tests by lowering their
workload. It also stops testing some of the more expensive benchmarks
that can't be easily made smaller.
2026-03-20 12:24:32 +00:00
Henrik Böving
9676f54cc5 chore: pass the previous stage libLake as plugin (#13000)
This PR avoids bootstrapping headaches when ABI breakages affect lake.
2026-03-20 12:23:20 +00:00
Wojciech Różowski
7e3e7cf5d9 feat: add cbv annotations to iterators and strings (#12961)
This PR adds `cbv` annotations to some iterator and string operations.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 11:39:40 +00:00
Sebastian Ullrich
c6a89cc716 feat: experimental option to move non-meta compilation out of lean build step (#10291)
The ultimate goal of this work is to turn production of `.ir` files into
separate build step so that it does not block non-`meta` imports and can
be skipped entirely when not needed. This PR implements the main logic
of this new `leanir` compiler executable and runs it after `lean` inside
the same Lake build step but leaves its use disabled behind a
`compiler.postponeCompile` flag until further Lake adjustments move it
to a separate facet so that its use can be actually beneficial.

---------

Co-authored-by: Joscha <joscha@plugh.de>
2026-03-20 10:39:39 +00:00
Markus Himmel
5099f96ae7 feat: verification of String.toInt? (#13003)
This PR reorganizes the instances `ToString Int` and `Repr Int` so that
they both point at a common definition `Int.repr` (the same setup is
used for `Nat`). It then verifies the functions `Int.repr`,
`String.isInt` and `String.toInt`.

In particular, for `a : Int` we get `a.repr.toInt? = some a`, which
implies that `Int.repr` is injective.
2026-03-20 10:31:51 +00:00
Markus Himmel
5e1b6ed663 feat: verification of String.dropPrefix? (#12999)
This PR verifies the `String.dropPrefix?` function for our various
patterns.
2026-03-20 07:35:49 +00:00
Leonardo de Moura
d2907b5c96 feat: add contextDependent to Sym.simp Result with two-tier cache (#12996)
This PR adds per-result `contextDependent` tracking to `Sym.Simp.Result`
and splits the simplifier cache into persistent (context-independent)
and transient (context-dependent, cleared on binder entry). This
replaces the coarse `wellBehavedMethods` flag.

Key changes:
- Add `contextDependent : Bool := false` to `Result.rfl` and
`Result.step`
- Split `State.cache` into `persistentCache` and `transientCache`
- Remove `wellBehavedMethods` from `Methods`
- Replace `withoutModifyingCacheIfNotWellBehaved` with
`withFreshTransientCache`
- Change `DischargeResult` to an inductive (`.failed`/`.solved`)
- Add `dischargeAssumption` (context-dependent discharger for testing)
- Add `sym.simp.debug.cache` trace class
- Propagate `contextDependent` through all combinators (congruence,
transitivity, control flow, arrows, rewriting)
- Add `mkRflResult`/`mkRflResultCD` to avoid dynamic allocation of rfl
results
- Fix `isRfl` to ignore `contextDependent` (was silently broken by the
extra field)

Propagation invariant: when combining sub-results, `cd` is the
disjunction of ALL sub-results' flags — including `.rfl` results. If
`simp` returned `.rfl (contextDependent := true)`, it means `simp` might
take a completely different code path in another local context, so all
downstream results must be marked context-dependent.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 00:22:08 +00:00
Lean stage0 autoupdater
be424ada14 chore: update stage0 2026-03-19 23:32:25 +00:00
Mac Malone
d78525b302 fix: lake: ltar caching bug with build -o (#12993)
This PR fixes a bug with Lake where caching an `ltar` produced via `lake
build -o` would fail if `restoreAllArtifacts` was also `true`.
2026-03-19 22:51:09 +00:00
Jovan Gerbscheid
518a135777 feat: Thunk is inhabited (#12469)
This PR adds the `Inhabited` instance for `Thunk`.

We need this in batteries to call `PersistentEnvExtension.getState` on a
state that is wrapped in a `Thunk`, see
https://github.com/leanprover-community/batteries/pull/1667/changes.
2026-03-19 21:58:46 +00:00
Lean stage0 autoupdater
fd5329126b chore: update stage0 2026-03-19 21:31:22 +00:00
Mac Malone
2e937ec789 chore: make leantar available in stage0 (#12992)
This PR makes `leantar` available in stage0, which is necessary for
#10880.
2026-03-19 20:43:43 +00:00
Sofia Rodrigues
90125ed205 feat: introduce URI data type for HTTP (#12128)
This PR introduces the `URI` data type.

This contains the same code as #10478, divided into separate pieces to
facilitate easier review.

The pieces of this feature are:
- Core data structures: #12126
- Headers: #12127
- URI:  #12128
- Body: #12144
- H1: #12146
- Server: #12151
- Client:

---------

Co-authored-by: Rob23oba <152706811+Rob23oba@users.noreply.github.com>
2026-03-19 19:34:28 +00:00
Lean stage0 autoupdater
1b63e7dfc6 chore: update stage0 2026-03-19 19:02:33 +00:00
Markus Himmel
34cf4575f3 feat: verify String.startsWith and String.skipPrefix? (#12990)
This PR verifies the `String.startsWith` and `String.skipPrefix?`
functions for our various pattern types.
2026-03-19 18:11:37 +00:00
Markus Himmel
0f730662de refactor: reorganize functions for skipping/dropping prefixes/suffixes of strings (#12988)
This PR introduces the functions `String.Slice.skipPrefix?`,
`String.Slice.Pos.skip?`, `String.Slice.skipPrefixWhile`,
`String.Slice.Pos.skipWhile` and redefines `String.Slice.takeWhile` and
`String.Slice.dropWhile` to use these new functions.
2026-03-19 15:45:53 +00:00
Wojciech Różowski
5cc6585c9b chore: disable cbv usage warning (#12986)
This disables `cbv` usage warning and reflects that in the corresponding
unit tests.
2026-03-19 14:12:04 +00:00
Sebastian Ullrich
d9c3bbf1b4 fix: prevent induction/cases from swallowing diagnostics when using clause contains by (#12953)
This PR fixes an issue where the `induction` and `cases` tactics would
swallow diagnostics (such as unsolved goals errors) when the `using`
clause contains a nested tactic.

Closes #12815
2026-03-19 13:52:16 +00:00
Markus Himmel
9c5d2bf62e refactor: rename ForwardPattern.dropPrefix? to ForwardPattern.skipPrefix? (#12984)
This PR renames the function `ForwardPattern.dropPrefix?` to
`ForwardPattern.skipPrefix`?

This function `(s : String.Slice) -> Option s.Pos` is not to be confused
with `String.Slice.dropPrefix? : (s : String.Slice) -> Option
String.Slice`.
2026-03-19 13:05:55 +00:00
Wojciech Różowski
8f6ade06ea fix: interaction between cbv_opaque and inline (#12981)
This PR fixes the interaction between `cbv_opaque` and
`inline`/`always_inline` annotations, to make sure that inlined
definitions marked as `cbv_opaque` are not unfolded during the
preprocessing stage of `cbv` tactic.
2026-03-19 11:23:57 +00:00
Markus Himmel
e758c0e35c feat: String.toNat? lemmas (#12828)
This PR redefines the `String.isNat` function to use less state and
perform short-circuiting. It then verifies the `String.isNat` and
`String.toNat?` functions.

Recall that `isNat` and `toNat?` allow `_` as a digit separator. This is
why we get the complicated statement
```lean
public theorem isNat_iff {s : String} :
    s.isNat = true ↔
      s ≠ "" ∧
      (∀ c ∈ s.toList, c.isDigit ∨ c = '_') ∧
      ¬ ['_', '_'] <:+: s.toList ∧
      s.toList.head? ≠ some '_' ∧
      s.toList.getLast? ≠ some '_'
```

For `toNat?`, we prove the fully general
```lean
public theorem toNat?_eq_some_ofDigitChars {s : String} (h : s.isNat = true) :
    s.toNat? = some (Nat.ofDigitChars 10 (s.toList.filter (· != '_')) 0)
```
as well as the useful `(Nat.repr n).toNat? = some n` (and the corollary
that `Nat.repr` is injective).

For people implementing formatting routines that involve digit
separators, we have
```lean
public theorem isNat_of_isDigit {s : String} (hne : s ≠ "")
    (hdigit : ∀ c ∈ s.toList, c.isDigit) : s.isNat = true

public theorem isNat_append_underscore_append {s t : String}
    (hs : s.isNat = true) (ht : t.isNat = true) :
    (s ++ "_" ++ t).isNat = true

public theorem toNat?_append_underscore_append_eq_some {s t : String} {n m : Nat}
    (hs : s.toNat? = some n) (ht : t.toNat? = some m) :
   (s ++ "_" ++ t).toNat? =
      some (10 ^ (t.toList.filter (· != '_')).length * n + m)
```

The missing bit here is `(s.leftpad k '0').toNat? = s.toNat?`, which is
missing because we don't have `String.leftpad` (yet). For any reasonable
definition of `leftpad`, this will follow from
`toNat?_eq_some_ofDigitChars` since we prove the necessary ingredients
about `ofDigitChars`.

There are some rough edges around `ofDigitChars`, and in the future it
will be nice to connect this all to mathlib's `Nat.digits` and
`Nat.ofDigits`, which are similar but different.
2026-03-19 11:02:56 +00:00
Joachim Breitner
747262e498 fix: respect pp.privateNames in #print signature (#12979)
This PR makes `#print` show the full internal private name (including
module prefix) in the declaration signature when `pp.privateNames` is
set to true. Previously, `pp.privateNames` only affected names in the
body but the signature always stripped the private prefix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 09:16:48 +00:00
Markus Himmel
f8a3c13e0b feat: assorted lemmas (#12980)
This PR adds theorems about `Char`, `Nat` and `List`.
2026-03-19 09:14:54 +00:00
Markus Himmel
a045a7c094 perf: remove simp annotations (#12977)
This PR removes most of the `simp` annotations added in #12945, to
mitigate the performance impact. The lemmas remain.
2026-03-19 07:58:32 +00:00
Derrik Petrin
87180a09c4 fix: fix a collection of docstring errors (#12959)
This PR fixes a series of errors in docstrings.

This includes:
- incorrect gramar
- errant reference to "dependent" in the non-dependent `HashMap` files
- reference to expression metavariables as universe level metavariables
- outdated reference to `usizeSz` instead of `USize.size`
- syntax errors in code examples
- a broken link to a paper

---------

Co-authored-by: Derrik Petrin <derrik.petrin@pm.me>
2026-03-19 06:42:11 +00:00
Mac Malone
c1bbc6abaa feat: lake: parallel cache artifact transfers (#12974)
This PR changes `lake cache get` and `lake cache put` to transfer
artifacts in parallel (using `curl --parallel`) when uploading or
eagerly downloading artifacts. Transfers are still recorded one-by-one
in the output -- no progress meter yet.
2026-03-19 04:03:58 +00:00
Joachim Breitner
b7380758ae refactor: remove Lean.Environment.replay from core (#12972)
This PR removes the obsolete `Lean.Environment.replay` from
`src/Lean/Replay.lean` and replaces it with the improved version from
`src/LeanChecker/Replay.lean`, which includes fixes for duplicate
theorem handling and Quot/Eq dependency ordering. The primed names
(`Replay'`, `replay'`) are renamed back to `Replay` and `replay`.

A test for the original issue (nested inductives failing with `replay`)
is added as `tests/elab/issue12819.lean`.

Closes #12819

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 22:11:42 +00:00
Wojciech Nawrocki
24ee19e405 chore: add aarch64-darwin to flake (#12915) 2026-03-18 15:55:34 +00:00
Sebastian Ullrich
b13325f95d fix: shake: avoid panic on header-only files without trailing newline (#12963)
This PR fixes a panic in `lake shake` when applied to a header-only file
without trailing newline
2026-03-18 15:44:39 +00:00
Leonardo de Moura
58ef418dda feat: add sym => interactive mode (#12970)
This PR adds a `sym =>` tactic that enters an interactive symbolic
simulation
mode built on `grind`. Unlike `grind =>`, it does not eagerly introduce
hypotheses or apply by-contradiction, giving users explicit control over
`intro`, `apply`, and `internalize` steps.

New tactics available in `sym =>` mode:
- `intro` / `intros`: introduce binders and internalize into the E-graph
by
  default. Use `intro~` or `intro (internalize := false)` to skip
  internalization.
- `apply t`: apply backward rules with caching for `repeat`.
- `internalize` / `internalize_all`: internalize hypotheses into the
E-graph.
- `by_contra`: apply proof by contradiction, negating the target.

Satellite solvers (`lia`, `ring`, `linarith`) automatically introduce
remaining
binders and apply by-contradiction in `sym =>` mode, matching their
behavior in
default tactic mode. All existing `grind =>` tactics (`finish`,
`instantiate`,
`cases`, etc.) also work in `sym =>` mode. The sym-specific tactics are
guarded
and rejected in regular `grind =>` mode.

```lean
example (x : Nat) : myP x → myQ x := by
  sym [myP_myQ] =>
    intro h
    finish

example (x y z : Nat) : x > 1 → x + y + z > 0 := by
  sym =>
    lia
```
2026-03-18 14:29:18 +00:00
Joachim Breitner
b2aec782eb fix: re-privatize constant name prefix in realizeConst to avoid diamond import collisions (#12964)
This PR fixes an issue where `realizeConst` would generate auxiliary
declarations
(like `_sparseCasesOn`) using the original defining module's private
name prefix
rather than the realizing module's prefix. When two modules
independently realized
the same imported constant, they produced identically-named auxiliary
declarations,
causing "environment already contains" errors on diamond import.

The fix re-privatizes the constant name under the current module before
passing it
to `withDeclNameForAuxNaming`, ensuring each realizing module generates
distinctly
named auxiliary declarations.

Fixes #12825

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 13:54:50 +00:00
Lean stage0 autoupdater
ea49bc9bcf chore: update stage0 2026-03-18 14:07:05 +00:00
Kim Morrison
7ee8c4aaeb fix: use libtool instead of ar for static libs on macOS (#12957)
This PR fixes a build failure on macOS introduced by #12540. macOS BSD
`ar` does not support the `@file` response file syntax that #12540
enabled unconditionally. On macOS, when building core (i.e., `bootsrap
:= true`), `recBuildStatic` now uses `libtool -static -filelist`, which
handles long argument lists natively.

Includes a `stage0/src/stdlib_flags.h` trigger so CI will automatically
run `update-stage0` after merge.

🤖 Prepared with Claude Code

Implementation adjusted by @tydeu

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Mac Malone <mac@lean-fro.org>
Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-03-18 12:41:19 +00:00
Lean stage0 autoupdater
09da0d22a1 chore: update stage0 2026-03-18 12:37:41 +00:00
Markus Himmel
cb0455e379 feat: simprocs for n.digitChar = c (#12966)
This PR adds simp lemmas that simplify `n.digitChar = '0'` to `n = 0`
and a simproc that simplifies `n.digitChar = '!'` to `False`.
2026-03-18 12:00:24 +00:00
Garmelon
e14230c0f3 chore: import measure.py instead of calling it (#12962)
Thereby avoiding the overhead of one python interpreter per wrapped lean
call during the build benchmarks.
2026-03-18 10:23:32 +00:00
Joachim Breitner
0717cb73d5 chore: hints about ccache in CLAUDE.md (#12960)
This PR adds instructions for building Lean with ccache in sandboxes.
2026-03-18 10:01:26 +00:00
Leonardo de Moura
f0b367d7aa fix: mark List.length as @[implicit_reducible] (#12924)
This PR fixes a regression introduced in Lean 4.29.0-rc2 where `simp` no
longer simplifies inside type class instance arguments due to the
`backward.isDefEq.respectTransparency` change. This breaks proofs where
a term like `(a :: l).length` appears both in the main expression and
inside implicit instance arguments (e.g., determining a `BitVec` width).

**The problem:** After `simp only [List.length_cons]`, the main
expression has `l.length + 1` but instances still have `(a ::
l).length`. Since `simp` no longer simplifies inside instances, and
`isDefEq` won't unfold `List.length` at the default transparency,
subsequent lemma applications fail.

**Reproducer** (from Son Ho, reported by Sebastian Ullrich):
```lean
theorem BitVec.getElem!_eq_testBit_toNat {w : Nat} (x : BitVec w) (i : Nat) :
     x[i]! = x.toNat.testBit i := by sorry

example (l : List Nat) (a : Nat) (j : Nat) :
  (0#((a :: l).length))[j]! = (0#((a :: l).length)).toNat.testBit j := by
  simp only [List.length_cons]
  simp only [BitVec.getElem!_eq_testBit_toNat] -- works in 4.28.0-rc1, fails in 4.29.0-rc6
```

**The fix:** Mark `List.length` as `@[implicit_reducible]`, allowing
`isDefEq` to unfold it when checking implicit arguments. Several proofs
that previously needed a trailing `rfl` after `simp` now close directly,
since `simp` can see through `List.length` in more positions.

**Longer term:** The root cause is that `GetElem` carries complex proof
obligations in its type class instances, making implicit arguments
sensitive to definitional equality of collection sizes. We are
considering a redesign with a noncomputable `GetElemV` variant based on
`Nonempty` that avoids these casts entirely, but that is a larger change
planned for a future release.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 08:45:15 +00:00
Bhavik Mehta
0917260341 feat: add simp lemmas for kernel-friendly functions (#12950)
This PR adds simp lemmas equating kernel-friendly function names with
their operator notation equivalents: `Nat.land_eq`, `Nat.lor_eq`,
`Nat.xor_eq`, `Nat.shiftLeft_eq'`, `Nat.shiftRight_eq'`, and
`Bool.rec_eq`. These are useful when proofs involve reflection and need
to simplify kernel-reduced terms back to operator notation.

Closes #12716

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-18 07:22:06 +00:00
2092 changed files with 16127 additions and 3270 deletions

View File

@@ -1,7 +1,12 @@
(In the following, use `sysctl -n hw.logicalcpu` instead of `nproc` on macOS)
## Building
To build Lean you should use `make -j$(nproc) -C build/release`.
The build uses `ccache`, and in a sandbox `ccache` may complain about read-only file systems.
Use `CCACHE_READONLY` and `CCACHE_TEMPDIR` instead of disabling ccache completely.
## Running Tests
See `tests/README.md` for full documentation. Quick reference:
@@ -11,19 +16,32 @@ See `tests/README.md` for full documentation. Quick reference:
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
make -C build/release -j "$(nproc)" test
# Specific test by name (supports regex via ctest -R)
# Specific test by name (supports regex via ctest -R; double-quote special chars like |)
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
make -C build/release -j "$(nproc)" test ARGS='-R grind_ematch'
make -C build/release -j "$(nproc)" test ARGS="-R 'grind_ematch'"
# Multiple tests matching a pattern
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
make -C build/release -j "$(nproc)" test ARGS="-R 'treemap|phashmap'"
# Rerun only previously failed tests
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
make -C build/release -j "$(nproc)" test ARGS='--rerun-failed'
# Single test from tests/foo/bar/ (quick check during development)
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
make -C build/release -j "$(nproc)" test ARGS=-R testname'
# Run a test manually without ctest (test pile: pass filename relative to the pile dir)
tests/with_stage1_test_env.sh tests/elab_bench/run_bench.sh cbv_decide.lean
tests/with_stage1_test_env.sh tests/elab/run_test.sh grind_indexmap.lean
```
## Benchmark vs Test Problem Sizes
Benchmarks are also run as tests. Use the `TEST_BENCH` environment variable (unset in tests, set to `1` in benchmarks) to scale problem sizes:
- In `compile_bench` `.init.sh` files: check `$TEST_BENCH` and set `TEST_ARGS` accordingly
- In `elab_bench` Lean files: use `(← IO.getEnv "TEST_BENCH") == some "1"` to switch between small (test) and large (bench) inputs
See `tests/README.md` for the full benchmark writing guide.
## Testing stage 2
When requested to test stage 2, build it as follows:

View File

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

View File

@@ -61,20 +61,35 @@ jobs:
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
git fetch nightly --tags
if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then
# Manual re-release: create a revision of the most recent nightly
BASE_NIGHTLY=$(git tag -l 'nightly-*' | sort -rV | head -1)
# Strip any existing -revK suffix to get the base date tag
BASE_NIGHTLY="${BASE_NIGHTLY%%-rev*}"
REV=1
while git rev-parse "refs/tags/${BASE_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
REV=$((REV + 1))
done
LEAN_VERSION_STRING="${BASE_NIGHTLY}-rev${REV}"
# Manual re-release: retry today's nightly, or create a revision if it already exists
TODAY_NIGHTLY="nightly-$(date -u +%F)"
if git rev-parse "refs/tags/${TODAY_NIGHTLY}" >/dev/null 2>&1; then
# Today's nightly already exists, create a revision
REV=1
while git rev-parse "refs/tags/${TODAY_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
REV=$((REV + 1))
done
LEAN_VERSION_STRING="${TODAY_NIGHTLY}-rev${REV}"
else
# Today's nightly doesn't exist yet (e.g. scheduled run failed), create it
LEAN_VERSION_STRING="${TODAY_NIGHTLY}"
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
@@ -240,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)
@@ -261,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,
@@ -269,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",
@@ -283,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,

View File

@@ -114,6 +114,7 @@ if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
)
endif()
endif()
list(APPEND STAGE0_ARGS -DLEANTAR=${LEANTAR})
list(APPEND CL_ARGS -DCADICAL=${CADICAL} -DLEANTAR=${LEANTAR})
endif()

View File

@@ -1,3 +1,4 @@
30
interp.lean:146:4: warning: declaration uses `sorry`
interp.lean:146:0: warning: declaration uses `sorry`
3628800

View File

@@ -67,5 +67,5 @@
oldGlibc = devShellWithDist pkgsDist-old;
oldGlibcAArch = devShellWithDist pkgsDist-old-aarch;
};
}) ["x86_64-linux" "aarch64-linux"]);
}) ["x86_64-linux" "aarch64-linux" "aarch64-darwin"]);
}

View File

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

View File

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

View File

@@ -762,7 +762,7 @@ if(STAGE GREATER 0 AND CADICAL AND INSTALL_CADICAL)
add_dependencies(leancpp copy-cadical)
endif()
if(STAGE GREATER 0 AND LEANTAR AND INSTALL_LEANTAR)
if(LEANTAR AND INSTALL_LEANTAR)
add_custom_target(
copy-leantar
COMMAND cmake -E copy_if_different "${LEANTAR}" "${CMAKE_BINARY_DIR}/bin/leantar${CMAKE_EXECUTABLE_SUFFIX}"
@@ -797,7 +797,7 @@ if(LLVM AND STAGE GREATER 0)
set(EXTRA_LEANMAKE_OPTS "LLVM=1")
endif()
set(STDLIBS Init Std Lean Leanc)
set(STDLIBS Init Std Lean Leanc LeanIR)
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
list(APPEND STDLIBS Lake LeanChecker)
endif()
@@ -904,9 +904,16 @@ if(PREV_STAGE)
add_custom_target(update-stage0-commit COMMAND git commit -m "chore: update stage0" DEPENDS update-stage0)
endif()
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
add_custom_target(leanir ALL
DEPENDS leanshared
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanir
VERBATIM)
endif()
# use Bash version for building, use Lean version in bin/ for tests & distribution
configure_file("${LEAN_SOURCE_DIR}/bin/leanc.in" "${CMAKE_BINARY_DIR}/leanc.sh" @ONLY)
if(STAGE GREATER 0 AND EXISTS "${LEAN_SOURCE_DIR}/Leanc.lean" AND NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
if(STAGE GREATER 0 AND NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
configure_file("${LEAN_SOURCE_DIR}/Leanc.lean" "${CMAKE_BINARY_DIR}/leanc/Leanc.lean" @ONLY)
add_custom_target(
leanc
@@ -926,7 +933,7 @@ if(STAGE GREATER 0 AND CADICAL AND INSTALL_CADICAL)
install(PROGRAMS "${CADICAL}" DESTINATION bin)
endif()
if(STAGE GREATER 0 AND LEANTAR AND INSTALL_LEANTAR)
if(LEANTAR AND INSTALL_LEANTAR)
install(PROGRAMS "${LEANTAR}" DESTINATION bin)
endif()
@@ -945,6 +952,7 @@ install(
PATTERN "*.hash" EXCLUDE
PATTERN "*.trace" EXCLUDE
PATTERN "*.rsp" EXCLUDE
PATTERN "*.filelist" EXCLUDE
)
# symlink source into expected installation location for go-to-definition, if file system allows it

View File

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

View File

@@ -72,11 +72,11 @@ public instance [Monad m] [LawfulMonad m] [MonadAttach m] [LawfulMonadAttach m]
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfulMonadAttach m] :
WeaklyLawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (WeaklyLawfulMonadAttach (ReaderT _ _))
inferInstanceAs (WeaklyLawfulMonadAttach (ReaderT (ST.Ref ω σ) m))
public instance [Monad m] [MonadAttach m] [LawfulMonad m] [LawfulMonadAttach m] :
LawfulMonadAttach (StateRefT' ω σ m) :=
inferInstanceAs (LawfulMonadAttach (ReaderT _ _))
inferInstanceAs (LawfulMonadAttach (ReaderT (ST.Ref ω σ) m))
section

View File

@@ -103,11 +103,11 @@ namespace StateRefT'
instance {ω σ : Type} {m : Type Type} [Monad m] : LawfulMonadLift m (StateRefT' ω σ m) where
monadLift_pure _ := by
simp only [MonadLift.monadLift, pure]
unfold StateRefT'.lift ReaderT.pure
unfold StateRefT'.lift instMonad._aux_5 ReaderT.pure
simp only
monadLift_bind _ _ := by
simp only [MonadLift.monadLift, bind]
unfold StateRefT'.lift ReaderT.bind
unfold StateRefT'.lift instMonad._aux_13 ReaderT.bind
simp only
end StateRefT'

View File

@@ -60,9 +60,6 @@ with functions defined via well-founded recursion or partial fixpoints.
The proofs produced by `cbv` only use the three standard axioms.
In particular, they do not require trust in the correctness of the code
generator.
This tactic is experimental and its behavior is likely to change in upcoming
releases of Lean.
-/
syntax (name := cbv) "cbv" : conv

View File

@@ -172,6 +172,8 @@ instance thunkCoe : CoeTail α (Thunk α) where
-- Since coercions are expanded eagerly, `a` is evaluated lazily.
coe a := fun _ => a
instance [Inhabited α] : Inhabited (Thunk α) := .pure default
/-- A variation on `Eq.ndrec` with the equality argument first. -/
abbrev Eq.ndrecOn.{u1, u2} {α : Sort u2} {a : α} {motive : α Sort u1} {b : α} (h : a = b) (m : motive a) : motive b :=
Eq.ndrec m h

View File

@@ -98,7 +98,7 @@ well-founded recursion mechanism to prove that the function terminates.
@[simp] theorem pmap_push {P : α Prop} (f : a, P a β) (a : α) (xs : Array α) (h : b xs.push a, P b) :
pmap f (xs.push a) h =
(pmap f xs (fun a m => by simp at h; exact h.1 _ m)).push (f a (h a (by simp))) := by
(pmap f xs (fun a m => by simp [forall_or_eq_imp] at h; exact h.1 _ m)).push (f a (h a (by simp))) := by
simp [pmap]
@[simp] theorem attach_empty : (#[] : Array α).attach = #[] := rfl
@@ -153,7 +153,7 @@ theorem attachWith_congr {xs ys : Array α} (w : xs = ys) {P : α → Prop} {H :
@[simp] theorem attachWith_push {a : α} {xs : Array α} {P : α Prop} {H : x xs.push a, P x} :
(xs.push a).attachWith P H =
(xs.attachWith P (fun x h => by simp at H; exact H.1 _ h)).push a, H a (by simp) := by
(xs.attachWith P (fun x h => by simp [forall_or_eq_imp] at H; exact H.1 _ h)).push a, H a (by simp) := by
cases xs
simp

View File

@@ -559,9 +559,9 @@ def modifyOp (xs : Array α) (idx : Nat) (f : αα) : Array α :=
xs.modify idx f
/--
We claim this unsafe implementation is correct because an array cannot have more than `usizeSz` elements in our runtime.
We claim this unsafe implementation is correct because an array cannot have more than `USize.size` elements in our runtime.
This kind of low level trick can be removed with a little bit of compiler support. For example, if the compiler simplifies `as.size < usizeSz` to true. -/
This kind of low level trick can be removed with a little bit of compiler support. For example, if the compiler simplifies `as.size < USize.size` to true. -/
@[inline] unsafe def forIn'Unsafe {α : Type u} {β : Type v} {m : Type v Type w} [Monad m] (as : Array α) (b : β) (f : (a : α) a as β m (ForInStep β)) : m β :=
let sz := as.usize
let rec @[specialize] loop (i : USize) (b : β) : m β := do

View File

@@ -622,12 +622,12 @@ theorem findIdx?_eq_some_le_of_findIdx?_eq_some {xs : Array α} {p q : α → Bo
/-! ### findFinIdx? -/
@[grind =]
theorem findFinIdx?_empty {p : α Bool} : findFinIdx? p #[] = none := by simp; rfl
theorem findFinIdx?_empty {p : α Bool} : findFinIdx? p #[] = none := by simp
@[grind =]
theorem findFinIdx?_singleton {a : α} {p : α Bool} :
#[a].findFinIdx? p = if p a then some 0, by simp else none := by
simp; rfl
simp
-- We can't mark this as a `@[congr]` lemma since the head of the RHS is not `findFinIdx?`.
theorem findFinIdx?_congr {p : α Bool} {xs ys : Array α} (w : xs = ys) :
@@ -801,7 +801,7 @@ theorem idxOf?_eq_map_finIdxOf?_val [BEq α] {xs : Array α} {a : α} :
xs.idxOf? a = (xs.finIdxOf? a).map (·.val) := by
simp [idxOf?, finIdxOf?]
@[grind =] theorem finIdxOf?_empty [BEq α] : (#[] : Array α).finIdxOf? a = none := by simp; rfl
@[grind =] theorem finIdxOf?_empty [BEq α] : (#[] : Array α).finIdxOf? a = none := by simp
@[simp, grind =] theorem finIdxOf?_eq_none_iff [BEq α] [LawfulBEq α] {xs : Array α} {a : α} :
xs.finIdxOf? a = none a xs := by

View File

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

View File

@@ -134,6 +134,7 @@ theorem Array.toList_mergeSort {xs : Array α} {le : αα → Bool} :
(xs.mergeSort le).toList = xs.toList.mergeSort le := by
rw [Array.mergeSort, Subarray.toList_mergeSort, Array.toList_mkSlice_rii]
@[cbv_eval]
theorem Array.mergeSort_eq_toArray_mergeSort_toList {xs : Array α} {le : α α Bool} :
xs.mergeSort le = (xs.toList.mergeSort le).toArray := by
simp [ toList_mergeSort]

View File

@@ -36,6 +36,8 @@ theorem BEq.symm [BEq α] [Std.Symm (α := α) (· == ·)] {a b : α} : a == b
theorem BEq.comm [BEq α] [PartialEquivBEq α] {a b : α} : (a == b) = (b == a) :=
Bool.eq_iff_iff.2 BEq.symm, BEq.symm
theorem bne_eq [BEq α] {a b : α} : (a != b) = !(a == b) := rfl
theorem bne_comm [BEq α] [PartialEquivBEq α] {a b : α} : (a != b) = (b != a) := by
rw [bne, BEq.comm, bne]
@@ -64,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))

View File

@@ -664,3 +664,6 @@ but may be used locally.
@[simp] theorem Bool.not'_eq_not (a : Bool) : a.not' = a.not := by
cases a <;> simp [Bool.not']
theorem Bool.rec_eq {α : Sort _} (b : Bool) {x y : α} : Bool.rec y x b = if b then x else y := by
cases b <;> simp

View File

@@ -86,4 +86,20 @@ theorem toUInt8_val {c : Char} : c.val.toUInt8 = c.toUInt8 := rfl
@[simp]
theorem toString_eq_singleton {c : Char} : c.toString = String.singleton c := rfl
@[simp]
theorem toNat_val {c : Char} : c.val.toNat = c.toNat := rfl
theorem val_inj {c d : Char} : c.val = d.val c = d :=
Char.ext_iff.symm
theorem toNat_inj {c d : Char} : c.toNat = d.toNat c = d := by
simp [ toNat_val, val_inj, UInt32.toNat_inj]
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

View File

@@ -217,7 +217,7 @@ theorem succ?_eq {c : Char} : c.succ? = (c.ordinal.addNat? 1).map Char.ofOrdinal
Nat.reduceLeDiff, UInt32.left_eq_add]
grind [UInt32.lt_iff_toNat_lt]
· grind
· simp [coe_ordinal]
· simp [coe_ordinal, -toNat_val]
grind [UInt32.lt_iff_toNat_lt]
| case2 =>
rw [Fin.addNat?_eq_some]

View File

@@ -18,3 +18,4 @@ public import Init.Data.Int.Pow
public import Init.Data.Int.Cooper
public import Init.Data.Int.Linear
public import Init.Data.Int.OfNat
public import Init.Data.Int.ToString

View File

@@ -0,0 +1,24 @@
/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
-/
module
prelude
public import Init.Data.Repr
public import Init.Data.String.Defs
namespace Int
/--
Returns the decimal string representation of an integer.
-/
public protected def repr : Int String
| ofNat m => Nat.repr m
| negSucc m => "-" ++ Nat.repr (Nat.succ m)
public instance : Repr Int where
reprPrec i prec := if i < 0 then Repr.addAppParen i.repr prec else i.repr
end Int

View File

@@ -0,0 +1,23 @@
/-
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.ToString.Extra
import all Init.Data.Int.Repr
import Init.Data.Int.Order
import Init.Data.Int.LemmasAux
namespace Int
public theorem repr_eq_if {a : Int} :
a.repr = if 0 a then a.toNat.repr else "-" ++ (-a).toNat.repr := by
cases a <;> simp [Int.repr]
@[simp]
public theorem toString_eq_repr {a : Int} : toString a = a.repr := (rfl)
end Int

View File

@@ -37,7 +37,7 @@ The standard library does not provide a `Productive` instance for this case.
This combinator incurs an additional O(1) cost with each output of `it₁` and `it₂`.
-/
@[inline, expose]
@[cbv_opaque, inline, expose]
def Iter.append {α₁ : Type w} {α₂ : Type w} {β : Type w}
[Iterator α₁ Id β] [Iterator α₂ Id β]
(it₁ : Iter (α := α₁) β) (it₂ : Iter (α := α₂) β) :

View File

@@ -13,7 +13,7 @@ public section
namespace Std
open Std.Iterators
@[always_inline, inline, expose, inherit_doc IterM.attachWith]
@[cbv_opaque, always_inline, inline, expose, inherit_doc IterM.attachWith]
def Iter.attachWith {α β : Type w}
[Iterator α Id β]
(it : Iter (α := α) β) (P : β Prop) (h : out, it.IsPlausibleIndirectOutput out P out) :

View File

@@ -282,17 +282,17 @@ def Iter.mapM {α β γ : Type w} [Iterator α Id β] {m : Type w → Type w'}
[Monad m] [MonadAttach m] (f : β m γ) (it : Iter (α := α) β) :=
(letI : MonadLift Id m := pure; it.toIterM.mapM f : IterM m γ)
@[always_inline, inline, inherit_doc IterM.filterMap, expose]
@[cbv_opaque, always_inline, inline, inherit_doc IterM.filterMap, expose]
def Iter.filterMap {α : Type w} {β : Type w} {γ : Type w} [Iterator α Id β]
(f : β Option γ) (it : Iter (α := α) β) :=
((it.toIterM.filterMap f).toIter : Iter γ)
@[always_inline, inline, inherit_doc IterM.filter, expose]
@[cbv_opaque, always_inline, inline, inherit_doc IterM.filter, expose]
def Iter.filter {α : Type w} {β : Type w} [Iterator α Id β]
(f : β Bool) (it : Iter (α := α) β) :=
((it.toIterM.filter f).toIter : Iter β)
@[always_inline, inline, inherit_doc IterM.map, expose]
@[cbv_opaque, always_inline, inline, inherit_doc IterM.map, expose]
def Iter.map {α : Type w} {β : Type w} {γ : Type w} [Iterator α Id β]
(f : β γ) (it : Iter (α := α) β) :=
((it.toIterM.map f).toIter : Iter γ)

View File

@@ -44,7 +44,7 @@ public def Iter.flatMapAfter {α : Type w} {β : Type w} {α₂ : Type w}
(f : β Iter (α := α₂) γ) (it₁ : Iter (α := α) β) (it₂ : Option (Iter (α := α₂) γ)) :=
((it₁.toIterM.flatMapAfter (fun b => (f b).toIterM) (Iter.toIterM <$> it₂)).toIter : Iter γ)
@[always_inline, expose, inherit_doc IterM.flatMap]
@[cbv_opaque, always_inline, expose, inherit_doc IterM.flatMap]
public def Iter.flatMap {α : Type w} {β : Type w} {α₂ : Type w}
{γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
(f : β Iter (α := α₂) γ) (it : Iter (α := α) β) :=

View File

@@ -168,6 +168,13 @@ instance Map.instIterator {α β γ : Type w} {m : Type w → Type w'} {n : Type
Iterator (Map α m n lift f) n γ :=
inferInstanceAs <| Iterator (FilterMap α m n lift _) n γ
theorem Map.instIterator_eq_filterMapInstIterator {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n]
[Iterator α m β] {lift : α : Type w m α n α} {f : β PostconditionT n γ} :
Map.instIterator (α := α) (β := β) (γ := γ) (m := m) (n := n) (lift := lift) (f := f) =
FilterMap.instIterator :=
rfl
private def FilterMap.instFinitenessRelation {α β γ : Type w} {m : Type w Type w'}
{n : Type w Type w''} [Monad n] [Iterator α m β] {lift : α : Type w m α n α}
{f : β PostconditionT n (Option γ)} [Finite α m] :

View File

@@ -36,7 +36,7 @@ it.take 3 ---a--⊥
This combinator incurs an additional O(1) cost with each output of `it`.
-/
@[always_inline, inline]
@[cbv_opaque, always_inline, inline]
def Iter.take {α : Type w} {β : Type w} [Iterator α Id β] (n : Nat) (it : Iter (α := α) β) :
Iter (α := Take α Id) β :=
it.toIterM.take n |>.toIter

View File

@@ -44,7 +44,7 @@ it.uLift n ---.up a----.up b---.up c--.up d---⊥
* `Finite`: only if the original iterator is finite
* `Productive`: only if the original iterator is productive
-/
@[always_inline, inline, expose]
@[cbv_opaque, always_inline, inline, expose]
def Iter.uLift (it : Iter (α := α) β) :
Iter (α := Types.ULiftIterator.{v} α Id Id β (fun _ => monadLift)) (ULift β) :=
(it.toIterM.uLift Id).toIter

View File

@@ -32,7 +32,7 @@ Traverses the given iterator and stores the emitted values in an array.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toArray` always terminates after finitely many steps.
-/
@[always_inline, inline]
@[cbv_opaque, always_inline, inline]
def Iter.toArray {α : Type w} {β : Type w}
[Iterator α Id β] (it : Iter (α := α) β) : Array β :=
it.toIterM.toArray.run
@@ -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
@@ -101,7 +101,7 @@ lists are prepend-only, `toListRev` is usually more efficient that `toList`.
If the iterator is not finite, this function might run forever. The variant
`it.ensureTermination.toList` always terminates after finitely many steps.
-/
@[always_inline, inline]
@[cbv_opaque, always_inline, inline]
def Iter.toList {α : Type w} {β : Type w}
[Iterator α Id β] (it : Iter (α := α) β) : List β :=
it.toIterM.toList.run

View File

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

View File

@@ -56,7 +56,7 @@ theorem Iter.Intermediate.step_appendSnd {α₁ α₂ β : Type w}
simp only [Iter.step, appendSnd, toIterM_toIter, IterM.Intermediate.step_appendSnd, Id.run_bind]
cases it₂.toIterM.step.run.inflate using PlausibleIterStep.casesOn <;> simp
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_append {α₁ α₂ β : Type w}
[Iterator α₁ Id β] [Iterator α₂ Id β] [Finite α₁ Id] [Finite α₂ Id]
{it₁ : Iter (α := α₁) β} {it₂ : Iter (α := α₂) β} :
@@ -70,7 +70,7 @@ theorem Iter.toListRev_append {α₁ α₂ β : Type w}
(it₁.append it₂).toListRev = it₂.toListRev ++ it₁.toListRev := by
simp [append_eq_toIter_append_toIterM, toListRev_eq_toListRev_toIterM]
@[simp]
@[cbv_eval, simp]
theorem Iter.toArray_append {α₁ α₂ β : Type w}
[Iterator α₁ Id β] [Iterator α₂ Id β] [Finite α₁ Id] [Finite α₂ Id]
{it₁ : Iter (α := α₁) β} {it₂ : Iter (α := α₂) β} :

View File

@@ -34,7 +34,7 @@ theorem Iter.unattach_toList_attachWith [Iterator α Id β]
Id.run_map (f := List.unattach), IterM.map_unattach_toList_attachWith,
Iter.toList_eq_toList_toIterM]
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] :
@@ -68,7 +68,7 @@ theorem Iter.unattach_toArray_attachWith [Iterator α Id β]
(it.attachWith P hP).toListRev.unattach = it.toListRev := by
simp [toListRev_eq]
@[simp]
@[cbv_eval, simp]
theorem Iter.toArray_attachWith [Iterator α Id β]
{it : Iter (α := α) β} {hP}
[Finite α Id] :

View File

@@ -297,7 +297,7 @@ def Iter.val_step_filter {f : β → Bool} :
· simp
· simp
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_filterMap [Finite α Id]
{f : β Option γ} :
(it.filterMap f).toList = it.toList.filterMap f := by
@@ -315,12 +315,12 @@ theorem Iter.toList_mapM [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawful
(it.mapM f).toList = it.toList.mapM f := by
simp [Iter.mapM_eq_toIter_mapM_toIterM, IterM.toList_mapM, Iter.toList_eq_toList_toIterM]
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_map [Finite α Id] {f : β γ} :
(it.map f).toList = it.toList.map f := by
simp [map_eq_toIter_map_toIterM, IterM.toList_map, Iter.toList_eq_toList_toIterM]
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_filter [Finite α Id] {f : β Bool} :
(it.filter f).toList = it.toList.filter f := by
simp [filter_eq_toIter_filter_toIterM, IterM.toList_filter, Iter.toList_eq_toList_toIterM]
@@ -369,7 +369,7 @@ theorem Iter.toListRev_filter [Finite α Id]
(it.filter f).toListRev = it.toListRev.filter f := by
simp [filter_eq_toIter_filter_toIterM, IterM.toListRev_filter, Iter.toListRev_eq_toListRev_toIterM]
@[simp]
@[cbv_eval, simp]
theorem Iter.toArray_filterMap [Finite α Id]
{f : β Option γ} :
(it.filterMap f).toArray = it.toArray.filterMap f := by
@@ -387,13 +387,13 @@ theorem Iter.toArray_mapM [Monad m] [MonadAttach m] [LawfulMonad m] [WeaklyLawfu
(it.mapM f).toArray = it.toArray.mapM f := by
simp [Iter.mapM_eq_toIter_mapM_toIterM, IterM.toArray_mapM, Iter.toArray_eq_toArray_toIterM]
@[simp]
@[cbv_eval, simp]
theorem Iter.toArray_map [Finite α Id] {f : β γ} :
(it.map f).toArray = it.toArray.map f := by
simp [map_eq_toIter_map_toIterM, IterM.toArray_map, Iter.toArray_eq_toArray_toIterM]
@[simp]
theorem Iter.toArray_filter[Finite α Id] {f : β Bool} :
@[cbv_eval, simp]
theorem Iter.toArray_filter [Finite α Id] {f : β Bool} :
(it.filter f).toArray = it.toArray.filter f := by
simp [filter_eq_toIter_filter_toIterM, IterM.toArray_filter, Iter.toArray_eq_toArray_toIterM]

View File

@@ -254,6 +254,7 @@ public theorem Iter.toArray_flatMapAfter {α α₂ β γ : Type w} [Iterator α
unfold Iter.toArray
cases it₂ <;> simp [map, IterM.toArray_map_eq_toArray_mapM, - IterM.toArray_map]
@[cbv_eval]
public theorem Iter.toList_flatMap {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id]
[Iterator α Id β] [Iterator α₂ Id γ] [Finite α Id] [Finite α₂ Id]
@@ -261,6 +262,7 @@ public theorem Iter.toList_flatMap {α α₂ β γ : Type w} [Iterator α Id β]
(it₁.flatMap f).toList = (it₁.map fun b => (f b).toList).toList.flatten := by
simp [flatMap, toList_flatMapAfter]
@[cbv_eval]
public theorem Iter.toArray_flatMap {α α₂ β γ : Type w} [Iterator α Id β] [Iterator α₂ Id γ]
[Finite α Id] [Finite α₂ Id]
[Iterator α Id β] [Iterator α₂ Id γ] [Finite α Id] [Finite α₂ Id]

View File

@@ -699,18 +699,16 @@ theorem IterM.toList_map {α β β' : Type w} {m : Type w → Type w'} [Monad m]
(it : IterM (α := α) m β) :
(it.map f).toList = (fun x => x.map f) <$> it.toList := by
rw [ List.filterMap_eq_map, toList_filterMap]
let t := type_of% (it.map f)
let t' := type_of% (it.filterMap (some f))
simp only [map, mapWithPostcondition, InternalCombinators.map, filterMap,
filterMapWithPostcondition, InternalCombinators.filterMap]
unfold Map
congr
· simp [Map]
· simp [Map.instIterator, inferInstanceAs]
· simp
· rw [Map.instIterator_eq_filterMapInstIterator]
congr
simp
· simp only [map, mapWithPostcondition, InternalCombinators.map, Function.comp_apply, filterMap,
filterMapWithPostcondition, InternalCombinators.filterMap]
congr
· simp [Map]
· simp
· simp
· simp
@[simp]
theorem IterM.toList_filter {α : Type w} {m : Type w Type w'} [Monad m] [LawfulMonad m]
@@ -1310,7 +1308,8 @@ theorem IterM.forIn_mapWithPostcondition
haveI : MonadLift n o := monadLift
forIn (it.mapWithPostcondition f) init g =
forIn it init (fun out acc => do g ( (f out).run) acc) := by
unfold mapWithPostcondition InternalCombinators.map Map.instIterator Map.instIteratorLoop Map
unfold mapWithPostcondition InternalCombinators.map Map.instIteratorLoop Map
rw [Map.instIterator_eq_filterMapInstIterator]
rw [ InternalCombinators.filterMap, filterMapWithPostcondition, forIn_filterMapWithPostcondition]
simp

View File

@@ -67,7 +67,7 @@ theorem Iter.atIdxSlow?_take {α β}
simp only [atIdxSlow?_eq_match (it := it.take k), step_take, h']
cases k <;> cases l <;> simp
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_take_of_finite {α β} [Iterator α Id β] {n : Nat}
[Finite α Id] {it : Iter (α := α) β} :
(it.take n).toList = it.toList.take n := by
@@ -89,7 +89,7 @@ theorem Iter.toListRev_take_of_finite {α β} [Iterator α Id β] {n : Nat}
(it.take n).toListRev = it.toListRev.drop (it.toList.length - n) := by
rw [toListRev_eq, toList_take_of_finite, List.reverse_take, toListRev_eq]
@[simp]
@[cbv_eval, simp]
theorem Iter.toArray_take_of_finite {α β} [Iterator α Id β] {n : Nat}
[Finite α Id] {it : Iter (α := α) β} :
(it.take n).toArray = it.toArray.take n := by

View File

@@ -38,7 +38,7 @@ theorem Iter.step_uLift [Iterator α Id β] {it : Iter (α := α) β} :
PlausibleIterStep.done, pure_bind]
cases it.toIterM.step.run.inflate using PlausibleIterStep.casesOn <;> simp
@[simp]
@[cbv_eval, simp]
theorem Iter.toList_uLift [Iterator α Id β] {it : Iter (α := α) β}
[Finite α Id] :
it.uLift.toList = it.toList.map ULift.up := by
@@ -52,7 +52,7 @@ theorem Iter.toListRev_uLift [Iterator α Id β] {it : Iter (α := α) β}
it.uLift.toListRev = it.toListRev.map ULift.up := by
rw [toListRev_eq, toListRev_eq, toList_uLift, List.map_reverse]
@[simp]
@[cbv_eval, simp]
theorem Iter.toArray_uLift [Iterator α Id β] {it : Iter (α := α) β}
[Finite α Id] :
it.uLift.toArray = it.toArray.map ULift.up := by

View File

@@ -88,7 +88,7 @@ theorem Iter.toList_toArray_ensureTermination {α β} [Iterator α Id β] [Finit
it.ensureTermination.toArray.toList = it.toList := by
simp
@[simp]
@[cbv_eval , simp]
theorem Iter.toArray_toList {α β} [Iterator α Id β] [Finite α Id]
{it : Iter (α := α) β} :
it.toList.toArray = it.toArray := by
@@ -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

View File

@@ -449,7 +449,7 @@ theorem Iter.toArray_eq_fold {α β : Type w} [Iterator α Id β]
rw [ fold_hom (List.toArray)]
simp
@[simp]
@[cbv_eval , simp]
theorem Iter.foldl_toList {α β : Type w} {γ : Type x} [Iterator α Id β] [Finite α Id]
[IteratorLoop α Id Id] [LawfulIteratorLoop α Id Id]
{f : γ β γ} {init : γ} {it : Iter (α := α) β} :
@@ -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

View File

@@ -33,12 +33,12 @@ theorem List.step_iter_cons {x : β} {xs : List β} :
((x :: xs).iter).step = .yield xs.iter x, rfl := by
simp [List.iter, List.iterM, IterM.toIter, Iter.step_eq]
@[simp, grind =]
@[cbv_eval, simp, grind =]
theorem List.toArray_iter {l : List β} :
l.iter.toArray = l.toArray := by
simp [List.iter, List.toArray_iterM, Iter.toArray_eq_toArray_toIterM]
@[simp, grind =]
@[cbv_eval, simp, grind =]
theorem List.toList_iter {l : List β} :
l.iter.toList = l := by
simp [List.iter, List.toList_iterM]

View File

@@ -29,7 +29,7 @@ The monadic version of this iterator is `List.iterM`.
* `Finite` instance: always
* `Productive` instance: always
-/
@[always_inline, inline]
@[cbv_opaque, always_inline, inline]
def List.iter {α : Type w} (l : List α) :
Iter (α := ListIterator α) α :=
((l.iterM Id).toIter : Iter α)

View File

@@ -46,7 +46,7 @@ The non-monadic version of this iterator is `List.iter`.
* `Finite` instance: always
* `Productive` instance: always
-/
@[always_inline, inline]
@[cbv_opaque, always_inline, inline]
def _root_.List.iterM {α : Type w} (l : List α) (m : Type w Type w') [Pure m] :
IterM (α := ListIterator α) m α :=
{ list := l }

View File

@@ -1246,6 +1246,24 @@ def IsInfix (l₁ : List α) (l₂ : List α) : Prop := Exists fun s => Exists f
/-- not `isInfix` -/
recommended_spelling "infix" for "<:+:" in [IsInfix, «term_<:+:_»]
/--
Checks whether the first list is a contiguous sub-list of the second.
The relation `List.IsInfixOf` expresses this property with respect to logical equality.
Examples:
* `[2, 3].isInfixOf_internal [1, 2, 3, 4] = true`
* `[2, 3].isInfixOf_internal [1, 3, 2, 4] = false`
* `[2, 3].isInfixOf_internal [2, 3] = true`
* `[2, 3].isInfixOf_internal [1] = false`
Used internally by the `cbv` tactic.
-/
def isInfixOf_internal [BEq α] (l₁ l₂ : List α) : Bool :=
l₁.isPrefixOf l₂ || match l₂ with
| [] => false
| _ :: l₂ => isInfixOf_internal l₁ l₂
/-! ### splitAt -/
/--

View File

@@ -1050,7 +1050,7 @@ theorem findFinIdx?_append {xs ys : List α} {p : α → Bool} :
@[simp, grind =] theorem findFinIdx?_singleton {a : α} {p : α Bool} :
[a].findFinIdx? p = if p a then some 0, by simp else none := by
simp [findFinIdx?_cons, findFinIdx?_nil]; rfl
simp [findFinIdx?_cons, findFinIdx?_nil]
@[simp, grind =] theorem findFinIdx?_eq_none_iff {l : List α} {p : α Bool} :
l.findFinIdx? p = none x l, ¬ p x := by

View File

@@ -877,6 +877,11 @@ theorem getLast_eq_iff_getLast?_eq_some {xs : List α} (h) :
theorem getLast?_cons {a : α} : (a::l).getLast? = some (l.getLast?.getD a) := by
cases l <;> simp [getLast?, getLast]
theorem getLast?_cons_of_ne_nil {x : α} {xs : List α} (h : xs []) : (x::xs).getLast? = xs.getLast? := by
cases xs with
| nil => contradiction
| cons => simp [getLast?_cons]
@[simp] theorem getLast?_cons_cons : (a :: b :: l).getLast? = (b :: l).getLast? := by
simp [getLast?_cons]
@@ -1283,6 +1288,13 @@ theorem filter_eq_self {l} : filter p l = l ↔ ∀ a ∈ l, p a := by
cases h : p a <;> simp [*]
intro h; exact Nat.lt_irrefl _ (h length_filter_le p l)
theorem filter_bne_eq_self_of_not_mem [BEq α] [LawfulBEq α] {a : α} {l : List α} (h : a l) :
l.filter (· != a) = l := by
rw [List.filter_eq_self]
intro c hc
simp only [bne_iff_ne, ne_eq]
exact fun heq => absurd (heq hc) h
@[simp]
theorem length_filter_eq_length_iff {l} : (filter p l).length = l.length a l, p a := by
induction l with
@@ -1336,6 +1348,16 @@ theorem foldl_filter {p : α → Bool} {f : β → α → β} {l : List α} {ini
simp only [filter_cons, foldl_cons]
split <;> simp [ih]
theorem foldl_ite_left {P : α Prop} [DecidablePred P] {l : List α} {f : β α β} {init : β} :
(l.foldl (init := init) fun sofar a => if P a then f sofar a else sofar) = (l.filter P).foldl (init := init) f := by
simp [List.foldl_filter]
theorem foldl_ite_right {P : α Prop} [DecidablePred P] {l : List α} {f : β α β} {init : β} :
(l.foldl (init := init) fun sofar a => if P a then sofar else f sofar a) =
(l.filter (fun a => ¬ P a)).foldl (init := init) f := by
simp +singlePass only [ ite_not]
rw [foldl_ite_left]
theorem foldr_filter {p : α Bool} {f : α β β} {l : List α} {init : β} :
(l.filter p).foldr f init = l.foldr (fun x y => if p x then f x y else y) init := by
induction l generalizing init with

View File

@@ -481,13 +481,13 @@ protected theorem maxIdxOn_nil_eq_iff_false [LE β] [DecidableLE β] {f : α
@[simp]
protected theorem maxIdxOn_singleton [LE β] [DecidableLE β] {x : α} {f : α β} :
[x].maxIdxOn f (of_decide_eq_false rfl) = 0 :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_singleton
@[simp]
protected theorem maxIdxOn_lt_length [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : xs.maxIdxOn f h < xs.length :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_lt_length h
protected theorem maxIdxOn_le_of_apply_getElem_le_apply_maxOn [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -495,7 +495,7 @@ protected theorem maxIdxOn_le_of_apply_getElem_le_apply_maxOn [LE β] [Decidable
{k : Nat} (hi : k < xs.length) (hle : f (xs.maxOn f h) f xs[k]) :
xs.maxIdxOn f h k := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn] at hle
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minIdxOn_le_of_apply_getElem_le_apply_minOn h hi (by simpa [LE.le_opposite_iff] using hle)
protected theorem apply_maxOn_lt_apply_getElem_of_lt_maxIdxOn [LE β] [DecidableLE β] [LT β] [IsLinearPreorder β]
@@ -513,7 +513,7 @@ protected theorem getElem_maxIdxOn [LE β] [DecidableLE β] [IsLinearPreorder β
{f : α β} {xs : List α} (h : xs []) :
xs[xs.maxIdxOn f h] = xs.maxOn f h := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.getElem_minIdxOn h
protected theorem le_maxIdxOn_of_apply_getElem_lt_apply_getElem [LE β] [DecidableLE β] [LT β]
@@ -562,14 +562,14 @@ protected theorem maxIdxOn_cons
else if f (xs.maxOn f h) f x then 0
else (xs.maxIdxOn f h) + 1 := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minIdxOn_cons (f := f)
protected theorem maxIdxOn_eq_zero_iff [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} (h : xs []) :
xs.maxIdxOn f h = 0 x xs, f x f (xs.head h) := by
simp only [List.maxIdxOn_eq_minIdxOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minIdxOn_eq_zero_iff h (f := f)
protected theorem maxIdxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -580,26 +580,26 @@ protected theorem maxIdxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β]
else
xs.length + ys.maxIdxOn f hys := by
simp only [List.maxIdxOn_eq_minIdxOn, List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minIdxOn_append hxs hys (f := f)
protected theorem left_le_maxIdxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs ys : List α} {f : α β} (h : xs []) :
xs.maxIdxOn f h (xs ++ ys).maxIdxOn f (by simp [h]) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.left_le_minIdxOn_append h
protected theorem maxIdxOn_take_le [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} {i : Nat} (h : xs.take i []) :
(xs.take i).maxIdxOn f h xs.maxIdxOn f (List.ne_nil_of_take_ne_nil h) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_take_le h
@[simp]
protected theorem maxIdxOn_replicate [LE β] [DecidableLE β] [Refl (α := β) (· ·)]
{n : Nat} {a : α} {f : α β} (h : replicate n a []) :
(replicate n a).maxIdxOn f h = 0 :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minIdxOn_replicate h
@[simp]

View File

@@ -297,13 +297,13 @@ protected theorem maxOn_cons
(x :: xs).maxOn f (by exact of_decide_eq_false rfl) =
if h : xs = [] then x else maxOn f x (xs.maxOn f h) := by
simp only [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn_cons (f := f)
protected theorem maxOn_cons_cons [LE β] [DecidableLE β] {a b : α} {l : List α} {f : α β} :
(a :: b :: l).maxOn f (by simp) = (maxOn f a b :: l).maxOn f (by simp) := by
simp only [List.maxOn_eq_minOn, maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn_cons_cons
@[simp]
@@ -334,51 +334,51 @@ protected theorem maxOn_id [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLea
{xs : List α} (h : xs []) :
xs.maxOn id h = xs.max h := by
simp only [List.maxOn_eq_minOn]
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : Min α := (inferInstanceAs (Max α)).oppositeMin
letI : LE α := (inferInstance : LE α).opposite
letI : Min α := (inferInstance : Max α).oppositeMin
simpa only [List.max_eq_min] using List.minOn_id h
@[simp]
protected theorem maxOn_mem [LE β] [DecidableLE β] {xs : List α}
{f : α β} {h : xs []} : xs.maxOn f h xs :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_mem (f := f)
protected theorem le_apply_maxOn_of_mem [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} {y : α} (hx : y xs) :
f y f (xs.maxOn f (List.ne_nil_of_mem hx)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_le_of_mem (f := f) hx
protected theorem apply_maxOn_le_iff [LE β] [DecidableLE β] [IsLinearPreorder β] {xs : List α}
{f : α β} (h : xs []) {b : β} :
f (xs.maxOn f h) b x xs, f x b := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.le_apply_minOn_iff (f := f) h
protected theorem le_apply_maxOn_iff [LE β] [DecidableLE β] [IsLinearPreorder β] {xs : List α}
{f : α β} (h : xs []) {b : β} :
b f (xs.maxOn f h) x xs, b f x := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_le_iff (f := f) h
protected theorem apply_maxOn_lt_iff
[LE β] [DecidableLE β] [LT β] [IsLinearPreorder β] [LawfulOrderLT β]
{xs : List α} {f : α β} (h : xs []) {b : β} :
f (xs.maxOn f h) < b x xs, f x < b := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LT β := (inferInstanceAs (LT β)).opposite
letI : LE β := (inferInstance : LE β).opposite
letI : LT β := (inferInstance : LT β).opposite
simpa [LT.lt_opposite_iff] using List.lt_apply_minOn_iff (f := f) h
protected theorem lt_apply_maxOn_iff
[LE β] [DecidableLE β] [LT β] [IsLinearPreorder β] [LawfulOrderLT β]
{xs : List α} {f : α β} (h : xs []) {b : β} :
b < f (xs.maxOn f h) x xs, b < f x := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LT β := (inferInstanceAs (LT β)).opposite
letI : LE β := (inferInstance : LE β).opposite
letI : LT β := (inferInstance : LT β).opposite
simpa [LT.lt_opposite_iff] using List.apply_minOn_lt_iff (f := f) h
protected theorem apply_maxOn_le_apply_maxOn_of_subset [LE β] [DecidableLE β]
@@ -386,14 +386,14 @@ protected theorem apply_maxOn_le_apply_maxOn_of_subset [LE β] [DecidableLE β]
haveI : xs [] := by intro h; rw [h] at hxs; simp_all [subset_nil]
f (ys.maxOn f hys) f (xs.maxOn f this) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_le_apply_minOn_of_subset (f := f) hxs hys
protected theorem apply_maxOn_take_le [LE β] [DecidableLE β] [IsLinearPreorder β]
{xs : List α} {f : α β} {i : Nat} (h : xs.take i []) :
f ((xs.take i).maxOn f h) f (xs.maxOn f (List.ne_nil_of_take_ne_nil h)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.le_apply_minOn_take (f := f) h
protected theorem le_apply_maxOn_append_left [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -401,7 +401,7 @@ protected theorem le_apply_maxOn_append_left [LE β] [DecidableLE β] [IsLinearP
f (xs.maxOn f h)
f ((xs ++ ys).maxOn f (append_ne_nil_of_left_ne_nil h ys)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_append_le_left (f := f) h
protected theorem le_apply_maxOn_append_right [LE β] [DecidableLE β] [IsLinearPreorder β]
@@ -409,7 +409,7 @@ protected theorem le_apply_maxOn_append_right [LE β] [DecidableLE β] [IsLinear
f (ys.maxOn f h)
f ((xs ++ ys).maxOn f (append_ne_nil_of_right_ne_nil xs h)) := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_minOn_append_le_right (f := f) h
@[simp]
@@ -417,21 +417,21 @@ protected theorem maxOn_append [LE β] [DecidableLE β] [IsLinearPreorder β] {x
{f : α β} (hxs : xs []) (hys : ys []) :
(xs ++ ys).maxOn f (by simp [hxs]) = maxOn f (xs.maxOn f hxs) (ys.maxOn f hys) := by
simp only [List.maxOn_eq_minOn, maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minOn_append (f := f) hxs hys
protected theorem maxOn_eq_head [LE β] [DecidableLE β] [IsLinearPreorder β] {xs : List α}
{f : α β} (h : xs []) (h' : x xs, f x f (xs.head h)) :
xs.maxOn f h = xs.head h := by
rw [List.maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.minOn_eq_head (f := f) h (by simpa [LE.le_opposite_iff] using h')
protected theorem max_map
[LE β] [DecidableLE β] [Max β] [IsLinearPreorder β] [LawfulOrderLeftLeaningMax β] {xs : List α}
{f : α β} (h : xs []) : (xs.map f).max (by simpa) = f (xs.maxOn f h) := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : Min β := (inferInstanceAs (Max β)).oppositeMin
letI : LE β := (inferInstance : LE β).opposite
letI : Min β := (inferInstance : Max β).oppositeMin
simpa [List.max_eq_min] using List.min_map (f := f) h
protected theorem maxOn_eq_max [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLeaningMax α]
@@ -458,7 +458,7 @@ protected theorem max_map_eq_max [Max α] [LE α] [DecidableLE α] [LawfulOrderL
protected theorem maxOn_replicate [LE β] [DecidableLE β] [IsLinearPreorder β]
{n : Nat} {a : α} {f : α β} (h : replicate n a []) :
(replicate n a).maxOn f h = a :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_replicate (f := f) h
/-! # minOn? -/
@@ -579,7 +579,7 @@ protected theorem maxOn?_nil [LE β] [DecidableLE β] {f : α → β} :
protected theorem maxOn?_cons_eq_some_maxOn
[LE β] [DecidableLE β] {f : α β} {x : α} {xs : List α} :
(x :: xs).maxOn? f = some ((x :: xs).maxOn f (fun h => nomatch h)) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_cons_eq_some_minOn
protected theorem maxOn?_cons
@@ -588,7 +588,7 @@ protected theorem maxOn?_cons
have : maxOn f x = (letI : LE β := LE.opposite inferInstance; minOn f x) := by
ext; simp only [maxOn_eq_minOn]
simp only [List.maxOn?_eq_minOn?, this]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn?_cons
@[simp]
@@ -599,8 +599,8 @@ protected theorem maxOn?_singleton [LE β] [DecidableLE β] {x : α} {f : α
@[simp]
protected theorem maxOn?_id [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLeaningMax α]
{xs : List α} : xs.maxOn? id = xs.max? := by
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : Min α := (inferInstanceAs (Max α)).oppositeMin
letI : LE α := (inferInstance : LE α).opposite
letI : Min α := (inferInstance : Max α).oppositeMin
simpa only [List.maxOn?_eq_minOn?, List.max?_eq_min?] using List.minOn?_id (α := α)
protected theorem maxOn?_eq_if
@@ -610,7 +610,7 @@ protected theorem maxOn?_eq_if
some (xs.maxOn f h)
else
none :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_eq_if
@[simp]
@@ -620,55 +620,55 @@ protected theorem isSome_maxOn?_iff [LE β] [DecidableLE β] {f : α → β} {xs
protected theorem maxOn_eq_get_maxOn? [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : xs.maxOn f h = (xs.maxOn? f).get (List.isSome_maxOn?_iff.mpr h) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_eq_get_minOn? (f := f) h
protected theorem maxOn?_eq_some_maxOn [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : xs.maxOn? f = some (xs.maxOn f h) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_eq_some_minOn (f := f) h
@[simp]
protected theorem get_maxOn? [LE β] [DecidableLE β] {f : α β} {xs : List α}
(h : xs []) : (xs.maxOn? f).get (List.isSome_maxOn?_iff.mpr h) = xs.maxOn f h :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.get_minOn? (f := f) h
protected theorem maxOn_eq_of_maxOn?_eq_some
[LE β] [DecidableLE β] {f : α β} {xs : List α} {x : α} (h : xs.maxOn? f = some x) :
xs.maxOn f (List.isSome_maxOn?_iff.mp (Option.isSome_of_eq_some h)) = x :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn_eq_of_minOn?_eq_some (f := f) h
protected theorem isSome_maxOn?_of_mem
[LE β] [DecidableLE β] {f : α β} {xs : List α} {x : α} (h : x xs) :
(xs.maxOn? f).isSome :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.isSome_minOn?_of_mem (f := f) h
protected theorem le_apply_get_maxOn?_of_mem
[LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β} {xs : List α} {x : α} (h : x xs) :
f x f ((xs.maxOn? f).get (List.isSome_maxOn?_of_mem h)) := by
simp only [List.maxOn?_eq_minOn?]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa [LE.le_opposite_iff] using List.apply_get_minOn?_le_of_mem (f := f) h
protected theorem maxOn?_mem [LE β] [DecidableLE β] {xs : List α}
{f : α β} (h : xs.maxOn? f = some a) : a xs :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_mem (f := f) h
protected theorem maxOn?_replicate [LE β] [DecidableLE β] [IsLinearPreorder β]
{n : Nat} {a : α} {f : α β} :
(replicate n a).maxOn? f = if n = 0 then none else some a :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_replicate
@[simp]
protected theorem maxOn?_replicate_of_pos [LE β] [DecidableLE β] [IsLinearPreorder β]
{n : Nat} {a : α} {f : α β} (h : 0 < n) :
(replicate n a).maxOn? f = some a :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
List.minOn?_replicate_of_pos (f := f) h
@[simp]
@@ -678,7 +678,7 @@ protected theorem maxOn?_append [LE β] [DecidableLE β] [IsLinearPreorder β]
have : maxOn f = (letI : LE β := LE.opposite inferInstance; minOn f) := by
ext; simp only [maxOn_eq_minOn]
simp only [List.maxOn?_eq_minOn?, this]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
exact List.minOn?_append xs ys f
end List

View File

@@ -311,7 +311,7 @@ theorem drop_length_cons {l : List α} (h : l ≠ []) (a : α) :
| nil =>
cases h rfl
| cons y l ih =>
simp only [drop, length]
simp only [drop]
by_cases h₁ : l = []
· simp [h₁]
rw [getLast_cons h₁]

View File

@@ -182,7 +182,6 @@ private theorem mergeSortTR_run_eq_mergeSort : {n : Nat} → (l : { l : List α
simp only [mergeSortTR.run, mergeSortTR.run, mergeSort]
rw [merge_eq_mergeTR]
rw [mergeSortTR_run_eq_mergeSort, mergeSortTR_run_eq_mergeSort]
rfl
-- We don't make this a `@[csimp]` lemma because `mergeSort_eq_mergeSortTR₂` is faster.
theorem mergeSort_eq_mergeSortTR : @mergeSort = @mergeSortTR := by

View File

@@ -706,6 +706,11 @@ theorem infix_refl (l : List α) : l <:+: l := prefix_rfl.isInfix
grind_pattern suffix_cons => _ <:+ a :: l
@[simp]
theorem suffix_cons_append {a : α} {l₁ l₂ : List α} : l₂ <:+ a :: (l₁ ++ l₂) := by
rw [ List.cons_append]
exact List.suffix_append (a :: l₁) l₂
theorem infix_cons : l₁ <:+: l₂ l₁ <:+: a :: l₂ := fun l₁', l₂', h => a :: l₁', l₂', h rfl
theorem infix_concat : l₁ <:+: l₂ l₁ <:+: concat l₂ a := fun l₁', l₂', h =>
@@ -1292,6 +1297,31 @@ instance [DecidableEq α] (l₁ l₂ : List α) : Decidable (l₁ <+: l₂) :=
instance [DecidableEq α] (l₁ l₂ : List α) : Decidable (l₁ <:+ l₂) :=
decidable_of_iff (l₁.isSuffixOf l₂) isSuffixOf_iff_suffix
/-
Used internally by the `cbv` tactic.
-/
theorem isInfixOf_internal_iff_isInfix [BEq α] [LawfulBEq α] {l₁ l₂ : List α} :
l₁.isInfixOf_internal l₂ l₁ <:+: l₂ := by
induction l₂ with
| nil => simp [isInfixOf_internal, IsInfix]
| cons a l₂ ih =>
simp only [isInfixOf_internal, Bool.or_eq_true]
constructor
· rintro (h | h)
· exact (isPrefixOf_iff_prefix.mp h).isInfix
· exact infix_cons <| ih.mp h
· intro s, t, h
match s with
| [] => left; exact isPrefixOf_iff_prefix.mpr t, h
| a' :: s' =>
right; exact ih.mpr s', t, List.cons.inj h |>.2
/-
Used internally by the `cbv` tactic.
-/
instance [DecidableEq α] (l₁ l₂ : List α) : Decidable (l₁ <:+: l₂) :=
decidable_of_iff (l₁.isInfixOf_internal l₂) isInfixOf_internal_iff_isInfix
theorem prefix_iff_eq_append : l₁ <+: l₂ l₁ ++ drop (length l₁) l₂ = l₂ :=
by rintro r, rfl; rw [drop_left], fun e => _, e
@@ -1299,6 +1329,121 @@ theorem prefix_iff_eq_take : l₁ <+: l₂ ↔ l₁ = take (length l₁) l₂ :=
fun h => append_cancel_right <| (prefix_iff_eq_append.1 h).trans (take_append_drop _ _).symm,
fun e => e.symm take_prefix _ _
theorem prefix_iff_exists_append_eq {l₁ l₂ : List α} : l₁ <+: l₂ l₃, l₁ ++ l₃ = l₂ :=
Iff.rfl
theorem prefix_iff_exists_eq_append {l₁ l₂ : List α} : l₁ <+: l₂ l₃, l₂ = l₁ ++ l₃ := by
simp [prefix_iff_exists_append_eq, eq_comm]
-- See `Init.Data.List.Nat.Sublist` for `suffix_iff_eq_append`, `prefix_take_iff`, and `suffix_iff_eq_drop`.
theorem suffix_iff_exists_append_eq {l₁ l₂ : List α} : l₁ <:+ l₂ l₃, l₃ ++ l₁ = l₂ :=
Iff.rfl
theorem suffix_iff_exists_eq_append {l₁ l₂ : List α} : l₁ <:+ l₂ l₃, l₂ = l₃ ++ l₁ := by
simp [suffix_iff_exists_append_eq, eq_comm]
theorem suffix_append_self_iff {l₁ l₂ l₃ : List α} : l₁ ++ l₃ <:+ l₂ ++ l₃ l₁ <:+ l₂ := by
constructor
· rintro t, h
exact t, List.append_cancel_right (by rwa [ List.append_assoc] at h)
· rintro t, h
exact t, by rw [ List.append_assoc, h]
theorem prefix_self_append_iff {l₁ l₂ l₃ : List α} : l₃ ++ l₁ <+: l₃ ++ l₂ l₁ <+: l₂ := by
constructor
· rintro t, h
exact t, List.append_cancel_left (by rwa [List.append_assoc] at h)
· rintro t, h
exact t, by rw [List.append_assoc, h]
theorem suffix_append_inj_of_length_eq {l₁ l₂ s₁ s₂ : List α} (hs : s₁.length = s₂.length) :
l₁ ++ s₁ <:+ l₂ ++ s₂ l₁ <:+ l₂ s₁ = s₂ := by
simp only [suffix_iff_exists_eq_append]
refine ?_, ?_
· rintro l₃, h
rw [ List.append_assoc] at h
obtain rfl, rfl := List.append_inj' h hs.symm
refine l₃, by simp, by simp
· rintro l₃, rfl, rfl
refine l₃, by simp
theorem prefix_append_inj_of_length_eq {l₁ l₂ s₁ s₂ : List α} (hs : s₁.length = s₂.length) :
s₁ ++ l₁ <+: s₂ ++ l₂ s₁ = s₂ l₁ <+: l₂ := by
constructor
· rintro t, h
rw [List.append_assoc] at h
obtain rfl, rfl := List.append_inj h.symm hs.symm
exact rfl, t, rfl
· rintro rfl, t, rfl
exact t, by simp
theorem singleton_suffix_iff_getLast?_eq_some {a : α} {l : List α} : [a] <:+ l l.getLast? = some a := by
rw [suffix_iff_exists_eq_append, getLast?_eq_some_iff]
theorem singleton_prefix_iff_head?_eq_some {a : α} {l : List α} : [a] <+: l l.head? = some a := by
simp [prefix_iff_exists_eq_append, head?_eq_some_iff]
theorem singleton_prefix_cons_iff {a b : α} {l : List α} : [a] <+: b :: l a = b := by
simp
@[simp]
theorem singleton_suffix_append_singleton_iff {a b : α} {l : List α} :
[a] <:+ l ++ [b] a = b := by
refine fun h => Eq.symm ?_, by rintro rfl; simp
simpa [List.suffix_iff_exists_eq_append] using h
@[simp]
theorem singleton_suffix_cons_append_singleton_iff {a b c : α} {l : List α} :
[a] <:+ b :: (l ++ [c]) a = c := by
rw [ List.cons_append]
exact singleton_suffix_append_singleton_iff
theorem infix_append_iff {α : Type u} {l xs ys : List α} : l <:+: xs ++ ys
l <:+: xs l <:+: ys ( l₁ l₂, l = l₁ ++ l₂ l₁ <:+ xs l₂ <+: ys) := by
constructor
· rintro s, t, ht
rcases List.append_eq_append_iff.mp ht with as, hxs, _ | bs, hsl, hys
· exact Or.inl s, as, hxs.symm
· rcases List.append_eq_append_iff.mp hsl with cs, hxs', hl | ds, _, hbs
· exact Or.inr (Or.inr cs, bs, hl,
List.suffix_iff_exists_eq_append.mpr s, hxs',
List.prefix_iff_exists_eq_append.mpr t, hys)
· exact Or.inr (Or.inl ds, t, by rw [hys, hbs])
· rintro (s, t, ht | s, t, ht | l₁, l₂, rfl, hl₁, hl₂)
· exact s, t ++ ys, by rw [ List.append_assoc, ht]
· exact xs ++ s, t, by
rw [List.append_assoc] at ht
rw [List.append_assoc (xs ++ s), List.append_assoc xs, ht]
· rw [List.suffix_iff_exists_eq_append] at hl₁
rw [List.prefix_iff_exists_eq_append] at hl₂
obtain s, hxs := hl₁
obtain t, hys := hl₂
exact s, t, by rw [ List.append_assoc s l₁, List.append_assoc (s ++ l₁), hxs, hys]
theorem infix_append_iff_ne_nil {α : Type u} {l xs ys : List α} : l <:+: xs ++ ys
l <:+: xs l <:+: ys ( l₁ l₂, l₁ [] l₂ [] l = l₁ ++ l₂ l₁ <:+ xs l₂ <+: ys) := by
rw [List.infix_append_iff]
constructor
· rintro (h | h | l₁, l₂, hl, hl₁, hl₂)
· exact Or.inl h
· exact Or.inr (Or.inl h)
· cases l₁ with
| nil =>
simp only [List.nil_append] at hl
subst hl
exact Or.inr (Or.inl hl₂.isInfix)
| cons hd tl =>
cases l₂ with
| nil =>
simp only [List.append_nil] at hl
subst hl
exact Or.inl hl₁.isInfix
| cons hd' tl' =>
exact Or.inr (Or.inr _, _, List.cons_ne_nil _ _, List.cons_ne_nil _ _, hl, hl₁, hl₂)
· rintro (h | h | l₁, l₂, -, -, hl, hl₁, hl₂)
· exact Or.inl h
· exact Or.inr (Or.inl h)
· exact Or.inr (Or.inr l₁, l₂, hl, hl₁, hl₂)
end List

View File

@@ -297,6 +297,14 @@ theorem dropWhile_cons :
(a :: l).dropWhile p = a :: l := by
simp [dropWhile_cons, h]
theorem dropWhile_beq_eq_self_of_head?_ne [BEq α] [LawfulBEq α] {a : α} {l : List α}
(h : l.head? some a) : l.dropWhile (· == a) = l := by
cases l with
| nil => simp
| cons hd tl =>
rw [List.dropWhile_cons_of_neg]
simpa [beq_iff_eq] using h
theorem head?_takeWhile {p : α Bool} {l : List α} : (l.takeWhile p).head? = l.head?.filter p := by
cases l with
| nil => rfl

View File

@@ -102,6 +102,12 @@ instance : XorOp Nat := ⟨Nat.xor⟩
instance : ShiftLeft Nat := Nat.shiftLeft
instance : ShiftRight Nat := Nat.shiftRight
@[simp] theorem land_eq {m n : Nat} : m.land n = m &&& n := rfl
@[simp] theorem lor_eq {m n : Nat} : m.lor n = m ||| n := rfl
@[simp] theorem xor_eq {m n : Nat} : m.xor n = m ^^^ n := rfl
@[simp] theorem shiftLeft_eq' {m n : Nat} : m.shiftLeft n = m <<< n := rfl
@[simp] theorem shiftRight_eq' {m n : Nat} : m.shiftRight n = m >>> n := rfl
theorem shiftLeft_eq (a b : Nat) : a <<< b = a * 2 ^ b :=
match b with
| 0 => (Nat.mul_one _).symm

View File

@@ -867,7 +867,7 @@ theorem and_le_right {n m : Nat} : n &&& m ≤ m :=
le_of_testBit (by simp)
theorem left_le_or {n m : Nat} : n n ||| m :=
le_of_testBit (by simp)
le_of_testBit (by simp [imp_or_left_iff_true])
theorem right_le_or {n m : Nat} : m n ||| m :=
le_of_testBit (by simp)
le_of_testBit (by simp [imp_or_right_iff_true])

View File

@@ -253,4 +253,16 @@ theorem ext_div_mod {n a b : Nat} (h0 : a / n = b / n) (h1 : a % n = b % n) : a
theorem ext_div_mod_iff (n a b : Nat) : a = b a / n = b / n a % n = b % n :=
fun h => h rfl, h rfl, fun h0, h1 => ext_div_mod h0 h1
/-- An induction principle mirroring the base-`b` representation of the number. -/
theorem base_induction {P : Nat Prop} {n : Nat} (b : Nat) (hb : 1 < b) (single : m, m < b P m)
(digit : m k, k < b 0 < m P m P (b * m + k)) : P n := by
induction n using Nat.strongRecOn with | ind n ih
rcases Nat.lt_or_ge n b with hn | hn
· exact single _ hn
· have := div_add_mod n b
rw [ this]
apply digit _ _ (mod_lt _ (by omega)) _ (ih _ _)
· exact Nat.div_pos_iff.mpr by omega, hn
· exact div_lt_self (by omega) (by omega)
end Nat

View File

@@ -19,6 +19,7 @@ import Init.Data.Nat.Bitwise
import Init.Data.Nat.Simproc
import Init.WFTactics
import Init.Data.Char.Lemmas
import Init.Data.Nat.Div.Lemmas
public section
@@ -37,6 +38,71 @@ theorem isDigit_digitChar : n.digitChar.isDigit = decide (n < 10) :=
simp only [digitChar, reduceIte, Nat.reduceEqDiff]
(repeat' split) <;> simp
private theorem digitChar_iff_aux :
n, (n.digitChar = '0' n = 0) (n.digitChar = '1' n = 1)
(n.digitChar = '2' n = 2) (n.digitChar = '3' n = 3)
(n.digitChar = '4' n = 4) (n.digitChar = '5' n = 5)
(n.digitChar = '6' n = 6) (n.digitChar = '7' n = 7)
(n.digitChar = '8' n = 8) (n.digitChar = '9' n = 9)
(n.digitChar = 'a' n = 10) (n.digitChar = 'b' n = 11)
(n.digitChar = 'c' n = 12) (n.digitChar = 'd' n = 13)
(n.digitChar = 'e' n = 14) (n.digitChar = 'f' n = 15)
(n.digitChar = '*' 16 n)
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | _ + 16 => by simp [digitChar]
@[simp] theorem digitChar_eq_zero : n.digitChar = '0' n = 0 := (digitChar_iff_aux n).1
@[simp] theorem digitChar_eq_one : n.digitChar = '1' n = 1 := (digitChar_iff_aux n).2.1
@[simp] theorem digitChar_eq_two : n.digitChar = '2' n = 2 := (digitChar_iff_aux n).2.2.1
@[simp] theorem digitChar_eq_three : n.digitChar = '3' n = 3 := (digitChar_iff_aux n).2.2.2.1
@[simp] theorem digitChar_eq_four : n.digitChar = '4' n = 4 := (digitChar_iff_aux n).2.2.2.2.1
@[simp] theorem digitChar_eq_five : n.digitChar = '5' n = 5 := (digitChar_iff_aux n).2.2.2.2.2.1
@[simp] theorem digitChar_eq_six : n.digitChar = '6' n = 6 := (digitChar_iff_aux n).2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_seven : n.digitChar = '7' n = 7 := (digitChar_iff_aux n).2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_eight : n.digitChar = '8' n = 8 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_nine : n.digitChar = '9' n = 9 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_a : n.digitChar = 'a' n = 10 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_b : n.digitChar = 'b' n = 11 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_c : n.digitChar = 'c' n = 12 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_d : n.digitChar = 'd' n = 13 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_e : n.digitChar = 'e' n = 14 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_f : n.digitChar = 'f' n = 15 := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.1
@[simp] theorem digitChar_eq_star : n.digitChar = '*' 16 n := (digitChar_iff_aux n).2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2
@[simp] theorem zero_eq_digitChar : '0' = n.digitChar n = 0 := digitChar_eq_zero |> eq_comm.trans
@[simp] theorem one_eq_digitChar : '1' = n.digitChar n = 1 := digitChar_eq_one |> eq_comm.trans
@[simp] theorem two_eq_digitChar : '2' = n.digitChar n = 2 := digitChar_eq_two |> eq_comm.trans
@[simp] theorem three_eq_digitChar : '3' = n.digitChar n = 3 := digitChar_eq_three |> eq_comm.trans
@[simp] theorem four_eq_digitChar : '4' = n.digitChar n = 4 := digitChar_eq_four |> eq_comm.trans
@[simp] theorem five_eq_digitChar : '5' = n.digitChar n = 5 := digitChar_eq_five |> eq_comm.trans
@[simp] theorem six_eq_digitChar : '6' = n.digitChar n = 6 := digitChar_eq_six |> eq_comm.trans
@[simp] theorem seven_eq_digitChar : '7' = n.digitChar n = 7 := digitChar_eq_seven |> eq_comm.trans
@[simp] theorem eight_eq_digitChar : '8' = n.digitChar n = 8 := digitChar_eq_eight |> eq_comm.trans
@[simp] theorem nine_eq_digitChar : '9' = n.digitChar n = 9 := digitChar_eq_nine |> eq_comm.trans
@[simp] theorem a_eq_digitChar : 'a' = n.digitChar n = 10 := digitChar_eq_a |> eq_comm.trans
@[simp] theorem b_eq_digitChar : 'b' = n.digitChar n = 11 := digitChar_eq_b |> eq_comm.trans
@[simp] theorem c_eq_digitChar : 'c' = n.digitChar n = 12 := digitChar_eq_c |> eq_comm.trans
@[simp] theorem d_eq_digitChar : 'd' = n.digitChar n = 13 := digitChar_eq_d |> eq_comm.trans
@[simp] theorem e_eq_digitChar : 'e' = n.digitChar n = 14 := digitChar_eq_e |> eq_comm.trans
@[simp] theorem f_eq_digitChar : 'f' = n.digitChar n = 15 := digitChar_eq_f |> eq_comm.trans
@[simp] theorem star_eq_digitChar : '*' = n.digitChar 16 n := digitChar_eq_star |> eq_comm.trans
/-- Auxiliary theorem for `Nat.reduceDigitCharEq` simproc. -/
protected theorem digitChar_ne {n : Nat} (c : Char)
(h : c != '0' && c != '1' && c != '2' && c != '3' && c != '4' && c != '5' &&
c != '6' && c != '7' && c != '8' && c != '9' && c != 'a' && c != 'b' &&
c != 'c' && c != 'd' && c != 'e' && c != 'f' && c != '*') : n.digitChar c := by
rintro rfl
match n with
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | _ + 16 => simp [digitChar] at h
theorem toNat_digitChar_of_lt_ten {n : Nat} (hn : n < 10) : n.digitChar.toNat = 48 + n :=
match n with
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 => by simp [digitChar]
| _ + 10 => by omega
theorem toNat_digitChar_sub_48_of_lt_ten {n : Nat} (hn : n < 10) : n.digitChar.toNat - 48 = n := by
simp [toNat_digitChar_of_lt_ten hn]
private theorem isDigit_of_mem_toDigitsCore
(hc : c cs c.isDigit) (hb₁ : 0 < b) (hb₂ : b 10) (h : c toDigitsCore b fuel n cs) :
c.isDigit := by
@@ -53,6 +119,11 @@ private theorem isDigit_of_mem_toDigitsCore
theorem isDigit_of_mem_toDigits (hb₁ : 0 < b) (hb₂ : b 10) (hc : c toDigits b n) : c.isDigit :=
isDigit_of_mem_toDigitsCore (fun _ => by contradiction) hb₁ hb₂ hc
@[simp]
theorem underscore_not_in_toDigits {n : Nat} : ¬'_' Nat.toDigits 10 n := by
intro h
simpa using isDigit_of_mem_toDigits (by decide) (by decide) h
private theorem toDigitsCore_of_lt_base (hb : n < b) (hf : n < fuel) :
toDigitsCore b fuel n cs = n.digitChar :: cs := by
unfold toDigitsCore
@@ -129,6 +200,11 @@ theorem length_toDigits_pos {b n : Nat} :
· rw [toDigitsCore_eq_toDigitsCore_nil_append]
simp
@[simp]
theorem toDigits_ne_nil {n b : Nat} : Nat.toDigits b n [] := by
rw [ List.length_pos_iff]
exact Nat.length_toDigits_pos
theorem length_toDigits_le_iff {n k : Nat} (hb : 1 < b) (h : 0 < k) :
(Nat.toDigits b n).length k n < b ^ k := by
match k with
@@ -154,6 +230,14 @@ theorem repr_eq_ofList_toDigits {n : Nat} :
n.repr = .ofList (toDigits 10 n) :=
(rfl)
@[simp]
theorem toList_repr {n : Nat} : n.repr.toList = Nat.toDigits 10 n := by
simp [repr_eq_ofList_toDigits]
@[simp]
theorem repr_ne_empty {n : Nat} : n.repr "" := by
simp [ String.toList_inj]
theorem toString_eq_ofList_toDigits {n : Nat} :
toString n = .ofList (toDigits 10 n) :=
(rfl)
@@ -194,4 +278,59 @@ theorem length_repr_le_iff {n k : Nat} (h : 0 < k) :
n.repr.length k n < 10 ^ k := by
simpa [repr_eq_ofList_toDigits] using length_toDigits_le_iff (by omega) h
/--
Transforms a list of characters into a natural number, *assuming that all characters are in the
range from `'0'` to `'9'`*.
-/
def ofDigitChars (b : Nat) (l : List Char) (init : Nat) : Nat :=
l.foldl (init := init) (fun sofar c => b * sofar + (c.toNat - '0'.toNat))
theorem ofDigitChars_eq_foldl {b : Nat} {l : List Char} {init : Nat} :
ofDigitChars b l init = l.foldl (init := init) (fun sofar c => b * sofar + (c.toNat - '0'.toNat)) :=
(rfl)
@[simp]
theorem ofDigitChars_nil {init : Nat} : ofDigitChars b [] init = init := by
simp [ofDigitChars]
theorem ofDigitChars_cons {c : Char} {cs : List Char} {init : Nat} :
ofDigitChars b (c::cs) init = ofDigitChars b cs (b * init + (c.toNat - '0'.toNat)) := by
simp [ofDigitChars]
theorem ofDigitChars_cons_digitChar_of_lt_ten {n : Nat} (hn : n < 10) {cs : List Char} {init : Nat} :
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} :
ofDigitChars 10 l init = 10 ^ l.length * init + ofDigitChars 10 l 0 := by
induction l generalizing init with
| nil => simp [ofDigitChars]
| cons hd tl ih =>
simp only [ofDigitChars, Char.isValue, Char.reduceToNat, List.foldl_cons, List.length_cons,
Nat.mul_zero, Nat.zero_add] at ih
rw [ih, ih (init := hd.toNat - 48), Nat.pow_succ, Nat.mul_add, Nat.mul_assoc, Nat.add_assoc]
theorem ofDigitChars_append {l m : List Char} (init : Nat) :
ofDigitChars b (l ++ m) init = ofDigitChars b m (ofDigitChars b l init) := by
simp [ofDigitChars]
@[simp]
theorem ofDigitChars_replicate_zero {n : Nat} : ofDigitChars b (List.replicate n '0') init = b ^ n * init := by
induction n generalizing init with
| zero => simp
| succ n ih => simp [List.replicate_succ, ofDigitChars_cons, ih, Nat.pow_succ, Nat.mul_assoc]
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 (by omega : m < 10)]
| digit m k hk hm ih =>
rw [ Nat.toDigits_append_toDigits hb' hm hk,
ofDigitChars_append, ih, Nat.toDigits_of_lt_base hk,
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

View File

@@ -39,8 +39,8 @@ public theorem minOn_id [Min α] [LE α] [DecidableLE α] [LawfulOrderLeftLeanin
public theorem maxOn_id [Max α] [LE α] [DecidableLE α] [LawfulOrderLeftLeaningMax α] {x y : α} :
maxOn id x y = max x y := by
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : Min α := (inferInstanceAs (Max α)).oppositeMin
letI : LE α := (inferInstance : LE α).opposite
letI : Min α := (inferInstance : Max α).oppositeMin
simp [maxOn, minOn_id, Max.min_oppositeMin, this]
public theorem minOn_eq_or [LE β] [DecidableLE β] {f : α β} {x y : α} :
@@ -168,32 +168,32 @@ public theorem maxOn_eq_right_of_lt
[LE β] [DecidableLE β] [LT β] [Total (α := β) (· ·)] [LawfulOrderLT β]
{f : α β} {x y : α} (h : f x < f y) :
maxOn f x y = y :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LT β := (inferInstanceAs (LT β)).opposite
letI : LE β := (inferInstance : LE β).opposite
letI : LT β := (inferInstance : LT β).opposite
minOn_eq_right_of_lt (h := by simpa [LT.lt_opposite_iff] using h) ..
public theorem left_le_apply_maxOn [le : LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y : α} : f x f (maxOn f x y) := by
rw [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa only [LE.le_opposite_iff] using apply_minOn_le_left (f := f) ..
public theorem right_le_apply_maxOn [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y : α} : f y f (maxOn f x y) := by
rw [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa only [LE.le_opposite_iff] using apply_minOn_le_right (f := f)
public theorem apply_maxOn_le_iff [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y : α} {b : β} :
f (maxOn f x y) b f x b f y b := by
rw [maxOn_eq_minOn]
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
simpa only [LE.le_opposite_iff] using le_apply_minOn_iff (f := f)
public theorem maxOn_assoc [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β}
{x y z : α} : maxOn f (maxOn f x y) z = maxOn f x (maxOn f y z) :=
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : LE β := (inferInstance : LE β).opposite
minOn_assoc (f := f)
public instance [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α β} :
@@ -203,8 +203,8 @@ public instance [LE β] [DecidableLE β] [IsLinearPreorder β] {f : α → β} :
public theorem max_apply [LE β] [DecidableLE β] [Max β] [LawfulOrderLeftLeaningMax β]
{f : α β} {x y : α} : max (f x) (f y) = f (maxOn f x y) := by
letI : LE β := (inferInstanceAs (LE β)).opposite
letI : Min β := (inferInstanceAs (Max β)).oppositeMin
letI : LE β := (inferInstance : LE β).opposite
letI : Min β := (inferInstance : Max β).oppositeMin
simpa [Max.min_oppositeMin] using min_apply (f := f)
public theorem apply_maxOn [LE β] [DecidableLE β] [Max β] [LawfulOrderLeftLeaningMax β]

View File

@@ -44,7 +44,7 @@ def min' [LE α] [DecidableLE α] (a b : α) : α :=
open scoped Std.OppositeOrderInstances in
def max' [LE α] [DecidableLE α] (a b : α) : α :=
letI : LE α := (inferInstanceAs (LE α)).opposite
letI : LE α := (inferInstance : LE α).opposite
-- `DecidableLE` for the opposite order is derived automatically via `OppositeOrderInstances`
min' a b
```

View File

@@ -411,6 +411,7 @@ private theorem Rii.Internal.toArray_eq_toArray_iter [Least? α]
r.toArray = (Internal.iter r).toArray := by
rfl
@[cbv_eval]
public theorem Rxc.Iterator.toList_eq_match [LE α] [DecidableLE α]
[UpwardEnumerable α] [Rxc.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
[LawfulUpwardEnumerableLE α]
@@ -428,6 +429,7 @@ public theorem Rxc.Iterator.toList_eq_match [LE α] [DecidableLE α]
· simp [*]
· split <;> rename_i heq' <;> simp [*]
@[cbv_eval]
public theorem Rxc.Iterator.toArray_eq_match [LE α] [DecidableLE α]
[UpwardEnumerable α] [Rxc.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
[LawfulUpwardEnumerableLE α]
@@ -443,6 +445,7 @@ public theorem Rxc.Iterator.toArray_eq_match [LE α] [DecidableLE α]
· rfl
· split <;> simp
@[cbv_eval]
public theorem Rxo.Iterator.toList_eq_match [LT α] [DecidableLT α]
[UpwardEnumerable α] [Rxo.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
[LawfulUpwardEnumerableLT α]
@@ -459,6 +462,7 @@ public theorem Rxo.Iterator.toList_eq_match [LT α] [DecidableLT α]
· simp [*]
· split <;> rename_i heq' <;> simp [*]
@[cbv_eval]
public theorem Rxo.Iterator.toArray_eq_match [LT α] [DecidableLT α]
[UpwardEnumerable α] [Rxo.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
[LawfulUpwardEnumerableLT α]
@@ -491,6 +495,7 @@ public theorem Rxc.Iterator.toList_eq_toList_rxoIterator [LE α] [DecidableLE α
· simpa [UpwardEnumerable.lt_iff, UpwardEnumerable.le_iff, UpwardEnumerable.lt_succ_iff] using h
· simpa [UpwardEnumerable.lt_iff, UpwardEnumerable.le_iff, UpwardEnumerable.lt_succ_iff] using h
@[cbv_eval]
public theorem Rxi.Iterator.toList_eq_match
[UpwardEnumerable α] [Rxi.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
{it : Iter (α := Rxi.Iterator α) α} :
@@ -502,6 +507,7 @@ public theorem Rxi.Iterator.toList_eq_match
simp only [Iter.toList_eq_match_step (it := it), Rxi.Iterator.step_eq_step, Rxi.Iterator.step]
split <;> rename_i heq <;> simp [*]
@[cbv_eval]
public theorem Rxi.Iterator.toArray_eq_match
[UpwardEnumerable α] [Rxi.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
{it : Iter (α := Rxi.Iterator α) α} :
@@ -608,6 +614,7 @@ namespace Rcc
variable {r : Rcc α}
@[cbv_eval]
public theorem toList_eq_if_roc [LE α] [DecidableLE α] [UpwardEnumerable α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α] [Rxc.IsAlwaysFinite α] :
r.toList = if r.lower r.upper then
@@ -755,6 +762,7 @@ public theorem ClosedOpen.toList_succ_succ_eq_map [LE α] [DecidableLE α] [Upwa
(lo...=hi).toList.map succ :=
Rcc.toList_succ_succ_eq_map
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LE α] [DecidableLE α] [UpwardEnumerable α]
[LawfulUpwardEnumerableLE α] [Rxc.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
{γ : Type u} {init : γ} {m : Type u Type w} [Monad m] [LawfulMonad m]
@@ -844,6 +852,7 @@ namespace Rco
variable {r : Rco α}
@[cbv_eval]
public theorem toList_eq_if_roo [UpwardEnumerable α] [LT α] [DecidableLT α]
[LawfulUpwardEnumerable α] [Rxo.IsAlwaysFinite α] [LawfulUpwardEnumerableLT α] :
r.toList = if r.lower < r.upper then
@@ -1011,6 +1020,7 @@ public theorem toArray_succ_succ_eq_map [LE α] [DecidableLE α] [LT α] [Decida
(lo...hi).toArray.map succ := by
simp [ toArray_toList, toList_succ_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LE α] [LT α] [DecidableLT α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLE α] [LawfulUpwardEnumerableLT α]
[Rxo.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
@@ -1224,6 +1234,7 @@ public theorem toArray_succ_succ_eq_map [LE α] [DecidableLE α]
((succ lo)...*).toArray = (lo...*).toArray.map succ := by
simp [ toArray_toList, toList_succ_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LE α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLE α]
[Rxi.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
@@ -1330,6 +1341,7 @@ public theorem toArray_eq_match [LE α] [DecidableLE α] [UpwardEnumerable α]
rw [Internal.toArray_eq_toArray_iter, Rxc.Iterator.toArray_eq_match (it := Internal.iter r)]
simp [Internal.iter, Internal.toArray_eq_toArray_iter]
@[cbv_eval]
public theorem toList_eq_match_rcc [LE α] [DecidableLE α] [UpwardEnumerable α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α] [Rxc.IsAlwaysFinite α] :
r.toList = match UpwardEnumerable.succ? r.lower with
@@ -1473,6 +1485,7 @@ public theorem toArray_succ_succ_eq_map [LE α] [DecidableLE α] [LT α] [Decida
(lo<...=hi).toArray.map succ := by
simp [ toArray_toList, toList_succ_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LE α] [DecidableLE α] [LT α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLE α] [LawfulUpwardEnumerableLT α]
[Rxc.IsAlwaysFinite α] [LawfulUpwardEnumerable α] {γ : Type u} {init : γ} {m : Type u Type w}
@@ -1572,6 +1585,7 @@ public theorem toArray_eq_match [LE α] [LT α] [DecidableLT α] [UpwardEnumerab
#[] := by
rw [Internal.toArray_eq_toArray_iter, Rxo.Iterator.toArray_eq_match]; rfl
@[cbv_eval]
public theorem toList_eq_match_rco [UpwardEnumerable α] [LT α] [DecidableLT α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α] [Rxo.IsAlwaysFinite α] :
r.toList = match UpwardEnumerable.succ? r.lower with
@@ -1705,6 +1719,7 @@ public theorem toArray_succ_succ_eq_map [LT α] [DecidableLT α]
(lo<...hi).toArray.map succ := by
simp [ toArray_toList, toList_succ_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LT α] [DecidableLT α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[Rxo.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
@@ -1939,6 +1954,7 @@ public theorem toArray_succ_succ_eq_map [LT α] [DecidableLT α]
((succ lo)<...*).toArray = (lo<...*).toArray.map succ := by
simp [ toArray_toList, toList_succ_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LT α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[Rxi.IsAlwaysFinite α] [LawfulUpwardEnumerable α]
@@ -2039,6 +2055,7 @@ public theorem toList_toArray [Least? α] [LE α] [DecidableLE α] [UpwardEnumer
r.toArray.toList = r.toList := by
simp [Internal.toList_eq_toList_iter, Internal.toArray_eq_toArray_iter]
@[cbv_eval]
public theorem toList_eq_match_rcc [LE α] [DecidableLE α] [Least? α] [UpwardEnumerable α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLE α]
[Rxc.IsAlwaysFinite α] :
@@ -2231,6 +2248,7 @@ public theorem toArray_succ_eq_map [LE α] [DecidableLE α] [Least? α]
#[UpwardEnumerable.least (hn := r.upper)] ++ (*...=hi).toArray.map succ := by
simp [ toArray_toList, toList_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LE α] [DecidableLE α] [Least? α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLE α] [LawfulUpwardEnumerableLeast? α]
[Rxc.IsAlwaysFinite α] [LawfulUpwardEnumerable α] {γ : Type u} {init : γ} {m : Type u Type w}
@@ -2340,6 +2358,7 @@ public theorem toList_toArray [Least? α] [LT α] [DecidableLT α] [UpwardEnumer
r.toArray.toList = r.toList := by
simp [Internal.toList_eq_toList_iter, Internal.toArray_eq_toArray_iter]
@[cbv_eval]
public theorem toList_eq_match_rco [LT α] [DecidableLT α] [Least? α] [UpwardEnumerable α]
[LawfulUpwardEnumerable α] [LawfulUpwardEnumerableLT α]
[Rxo.IsAlwaysFinite α] :
@@ -2550,6 +2569,7 @@ public theorem toArray_succ_eq_map [LT α] [DecidableLT α] [Least? α]
#[UpwardEnumerable.least (hn := r.upper)] ++ (*...hi).toArray.map succ := by
simp [ toArray_toList, toList_succ_eq_map]
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [LT α] [DecidableLT α] [Least? α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLT α] [LawfulUpwardEnumerableLeast? α]
[Rxo.IsAlwaysFinite α] [LawfulUpwardEnumerable α] {γ : Type u} {init : γ} {m : Type u Type w}
@@ -2788,6 +2808,7 @@ public theorem pairwise_toList_le [LE α] [Least? α]
|> List.Pairwise.imp UpwardEnumerable.le_of_lt
|> List.Pairwise.imp (fun hle => (UpwardEnumerable.le_iff ..).mpr hle)
@[cbv_eval]
public theorem forIn'_eq_forIn'_toList [Least? α]
[UpwardEnumerable α] [LawfulUpwardEnumerableLeast? α]
[Rxi.IsAlwaysFinite α] [LawfulUpwardEnumerable α] {γ : Type u} {init : γ} {m : Type u Type w}

View File

@@ -354,16 +354,6 @@ end Nat
instance : Repr Nat where
reprPrec n _ := Nat.repr n
/--
Returns the decimal string representation of an integer.
-/
protected def Int.repr : Int String
| ofNat m => Nat.repr m
| negSucc m => String.Internal.append "-" (Nat.repr (succ m))
instance : Repr Int where
reprPrec i prec := if i < 0 then Repr.addAppParen i.repr prec else i.repr
def hexDigitRepr (n : Nat) : String :=
String.singleton <| Nat.digitChar n

View File

@@ -126,7 +126,7 @@ public theorem forIn_toList {α : Type u} {s : Subarray α}
ForIn.forIn s.toList init f = ForIn.forIn s init f :=
Slice.forIn_toList
@[grind =]
@[cbv_eval, grind =]
public theorem forIn_eq_forIn_toList {α : Type u} {s : Subarray α}
{m : Type v Type w} [Monad m] [LawfulMonad m] {γ : Type v} {init : γ}
{f : α γ m (ForInStep γ)} :
@@ -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
@@ -243,6 +244,7 @@ private theorem Std.Internal.List.extract_eq_drop_take' {l : List α} {start sto
List.length_take, ge_iff_le, h₁]
omega
@[cbv_eval]
public theorem Subarray.toList_eq_drop_take {xs : Subarray α} :
xs.toList = (xs.array.toList.take xs.stop).drop xs.start := by
rw [Subarray.toList_eq, Array.toList_extract, Std.Internal.List.extract_eq_drop_take']

View File

@@ -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]
@@ -1266,9 +1270,11 @@ theorem Pos.toSlice_comp_ofToSlice {s : String} :
theorem Pos.ofToSlice_comp_toSlice {s : String} :
Pos.ofToSlice (toSlice (s := s)) = id := by ext; simp
@[simp]
theorem Pos.toSlice_inj {s : String} {p q : s.Pos} : p.toSlice = q.toSlice p = q :=
fun h => by simpa using congrArg Pos.ofToSlice h, (· rfl)
@[simp]
theorem Pos.ofToSlice_inj {s : String} {p q : s.toSlice.Pos} : ofToSlice p = ofToSlice q p = q :=
fun h => by simpa using congrArg Pos.toSlice h, (· rfl)
@@ -1687,7 +1693,7 @@ def Pos.next {s : @& String} (pos : @& s.Pos) (h : pos ≠ s.endPos) : s.Pos :=
@[simp]
theorem Pos.ofToSlice_next_toSlice {s : String} {pos : s.Pos} {h} :
ofToSlice (Slice.Pos.next pos.toSlice h) = pos.next (ne_of_apply_ne Pos.toSlice (by simpa)) :=
ofToSlice (Slice.Pos.next pos.toSlice h) = pos.next (ne_of_apply_ne Pos.toSlice (by simpa using h)) :=
rfl
@[simp]
@@ -1922,7 +1928,7 @@ theorem Pos.toSlice_next {s : String} {p : s.Pos} {h} :
simp [next, -ofToSlice_next_toSlice]
theorem Pos.next_toSlice {s : String} {p : s.Pos} {h} :
p.toSlice.next h = (p.next (ne_of_apply_ne Pos.toSlice (by simpa))).toSlice := by
p.toSlice.next h = (p.next (ne_of_apply_ne Pos.toSlice (by simpa using h))).toSlice := by
simp [Pos.toSlice_next]
theorem Pos.byteIdx_lt_utf8ByteSize {s : String} (p : s.Pos) (h : p s.endPos) :

View File

@@ -64,7 +64,7 @@ public theorem Char.utf8Size_eq (c : Char) : c.utf8Size = 1 c.utf8Size = 2
match c.utf8Size, c.utf8Size_pos, c.utf8Size_le_four with
| 1, _, _ | 2, _, _ | 3, _, _ | 4, _, _ => simp
theorem Char.toNat_val_le {c : Char} : c.val.toNat 0x10ffff := by
theorem Char.toNat_le {c : Char} : c.toNat 0x10ffff := by
have := c.valid
simp [UInt32.isValidChar, Nat.isValidChar] at this
omega
@@ -193,10 +193,10 @@ theorem helper₄ (s : Nat) (c : BitVec w₀) (v : BitVec w') (w : Nat) :
-- TODO: possibly it makes sense to factor out this proof
theorem String.toBitVec_getElem_utf8EncodeChar_zero_of_utf8Size_eq_one {c : Char} (h : c.utf8Size = 1) :
((String.utf8EncodeChar c)[0]'(by simp [h])).toBitVec = 0#1 ++ c.val.toBitVec.extractLsb' 0 7 := by
have h₀ : c.val.toNat < 128 := by
suffices c.val.toNat 127 by omega
have h₀ : c.toNat < 128 := by
suffices c.toNat 127 by omega
simpa [Char.utf8Size_eq_one_iff, UInt32.le_iff_toNat_le] using h
have h₁ : c.val.toNat < 256 := by omega
have h₁ : c.toNat < 256 := by omega
rw [ BitVec.toNat_inj, BitVec.toNat_append]
simp [-Char.toUInt8_val, utf8EncodeChar_eq_singleton h, Nat.mod_eq_of_lt h₀, Nat.mod_eq_of_lt h₁]
@@ -977,9 +977,9 @@ theorem assemble₄_eq_some_iff_utf8EncodeChar_eq {w x y z : UInt8} {c : Char} :
BitVec.extractLsb'_append_extractLsb'_eq_extractLsb' (by simp),
BitVec.extractLsb'_append_extractLsb'_eq_extractLsb' (by simp),
BitVec.setWidth_eq_extractLsb' (by simp), BitVec.setWidth_setWidth_eq_self]
have := c.toNat_val_le
have := c.toNat_le
simp only [Nat.reduceAdd, BitVec.lt_def, UInt32.toNat_toBitVec, BitVec.toNat_twoPow,
Nat.reducePow, Nat.reduceMod, gt_iff_lt]
Nat.reducePow, Nat.reduceMod, gt_iff_lt, Char.toNat_val]
omega
theorem verify₄_eq_isSome_assemble₄ {w x y z : UInt8} :

View File

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

View File

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

View 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

View 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

View File

@@ -27,6 +27,7 @@ deriving Inhabited
/--
Creates an iterator over the valid positions within {name}`s`, starting at {name}`p`.
-/
@[cbv_opaque]
def positionsFrom {s : Slice} (p : s.Pos) :
Std.Iter (α := PosIterator s) { p : s.Pos // p s.endPos } :=
{ internalState := { currPos := p } }
@@ -99,7 +100,7 @@ Examples:
* {lean}`"abc".toSlice.chars.toList = ['a', 'b', 'c']`
* {lean}`"ab∀c".toSlice.chars.toList = ['a', 'b', '∀', 'c']`
-/
@[expose, inline]
@[cbv_opaque, expose, inline]
def chars (s : Slice) :=
Std.Iter.map (fun pos, h => pos.get h) (positions s)
@@ -188,7 +189,7 @@ Example:
* {lean}`"abc".toSlice.revChars.toList = ['c', 'b', 'a']`
* {lean}`"ab∀c".toSlice.revChars.toList = ['c', '∀', 'b', 'a']`
-/
@[expose, inline]
@[cbv_opaque, expose, inline]
def revChars (s : Slice) :=
Std.Iter.map (fun pos, h => pos.get h) (revPositions s)
@@ -347,7 +348,7 @@ Examples:
* {lean}`"coffee tea and water".toSlice.foldl (fun n c => if c.isWhitespace then n + 1 else n) 0 = 3`
* {lean}`"coffee tea water".toSlice.foldl (·.push ·) "" = "coffee tea water"`
-/
@[inline]
@[cbv_opaque, inline]
def foldl {α : Type u} (f : α Char α) (init : α) (s : Slice) : α :=
Std.Iter.fold f init (chars s)
@@ -398,7 +399,7 @@ Examples:
* {lean}`"abc".chars.toList = ['a', 'b', 'c']`
* {lean}`"ab∀c".chars.toList = ['a', 'b', '∀', 'c']`
-/
@[inline]
@[cbv_opaque, inline]
def chars (s : String) :=
(s.toSlice.chars : Std.Iter Char)
@@ -432,7 +433,7 @@ Example:
* {lean}`"abc".revChars.toList = ['c', 'b', 'a']`
* {lean}`"ab∀c".revChars.toList = ['c', '∀', 'b', 'a']`
-/
@[inline]
@[cbv_opaque, inline]
def revChars (s : String) :=
(s.toSlice.revChars : Std.Iter Char)
@@ -462,4 +463,32 @@ def revBytes (s : String) :=
instance {m : Type u Type v} [Monad m] : ForIn m String Char where
forIn s b f := ForIn.forIn s.toSlice b f
/--
Folds a function over a string from the start, accumulating a value starting with {name}`init`. The
accumulated value is combined with each character in order, using {name}`f`.
Examples:
* {lean}`"coffee tea water".foldl (fun n c => if c.isWhitespace then n + 1 else n) 0 = 2`
* {lean}`"coffee tea and water".foldl (fun n c => if c.isWhitespace then n + 1 else n) 0 = 3`
* {lean}`"coffee tea water".foldl (·.push ·) "" = "coffee tea water"`
-/
@[inline] def foldl {α : Type u} (f : α Char α) (init : α) (s : String) : α :=
s.toSlice.foldl f init
@[export lean_string_foldl]
def Internal.foldlImpl (f : String Char String) (init : String) (s : String) : String :=
String.foldl f init s
/--
Folds a function over a string from the right, accumulating a value starting with {lean}`init`. The
accumulated value is combined with each character in reverse order, using {lean}`f`.
Examples:
* {lean}`"coffee tea water".foldr (fun c n => if c.isWhitespace then n + 1 else n) 0 = 2`
* {lean}`"coffee tea and water".foldr (fun c n => if c.isWhitespace then n + 1 else n) 0 = 3`
* {lean}`"coffee tea water".foldr (fun c s => s.push c) "" = "retaw aet eeffoc"`
-/
@[inline] def foldr {α : Type u} (f : Char α α) (init : α) (s : String) : α :=
s.toSlice.foldr f init
end String

View File

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

View File

@@ -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]
@@ -78,7 +84,7 @@ theorem getUTF8Byte_toSlice {s : String} {p : String.Pos.Raw} {h} :
@[simp]
theorem Pos.byte_toSlice {s : String} {p : s.Pos} {h} :
p.toSlice.byte h = p.byte (ne_of_apply_ne Pos.toSlice (by simpa)) := by
p.toSlice.byte h = p.byte (ne_of_apply_ne Pos.toSlice (by simpa using h)) := by
simp [byte]
theorem Pos.byte_eq_byte_toSlice {s : String} {p : s.Pos} {h} :
@@ -181,6 +187,22 @@ theorem sliceTo_slice {s : String} {p₁ p₂ h p} :
(s.slice p₁ p₂ h).sliceTo p = s.slice p₁ (Pos.ofSlice p) Pos.le_ofSlice := by
ext <;> simp
@[simp]
theorem Slice.sliceFrom_startPos {s : Slice} : s.sliceFrom s.startPos = s := by
ext <;> simp
@[simp]
theorem Slice.sliceTo_endPos {s : Slice} : s.sliceTo s.endPos = s := by
ext <;> simp
@[simp]
theorem sliceFrom_startPos {s : String} : s.sliceFrom s.startPos = s := by
ext <;> simp
@[simp]
theorem sliceTo_endPos {s : String} : s.sliceTo s.endPos = s := by
ext <;> simp
end Iterate
theorem Slice.copy_eq_copy_slice {s : Slice} {pos₁ pos₂ : s.Pos} {h} :
@@ -228,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

View File

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

View 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

View File

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

View File

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

View 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

View File

@@ -76,7 +76,7 @@ theorem Model.map_get_positionsFrom_startPos {s : Slice} :
(Model.positionsFrom s.startPos).map (fun p => p.1.get p.2) = s.copy.toList :=
Model.map_get_positionsFrom_of_splits (splits_startPos s)
@[simp]
@[cbv_eval, simp]
theorem toList_positionsFrom {s : Slice} {p : s.Pos} :
(s.positionsFrom p).toList = Model.positionsFrom p := by
rw [positionsFrom]
@@ -91,7 +91,7 @@ theorem toList_positionsFrom {s : Slice} {p : s.Pos} :
theorem toList_positions {s : Slice} : s.positions.toList = Model.positionsFrom s.startPos := by
simp [positions]
@[simp]
@[cbv_eval, simp]
theorem toList_chars {s : Slice} : s.chars.toList = s.copy.toList := by
simp [chars, Model.map_get_positionsFrom_startPos]
@@ -177,19 +177,30 @@ theorem toList_revPositionsFrom {s : Slice} {p : s.Pos} :
theorem toList_revPositions {s : Slice} : s.revPositions.toList = Model.revPositionsFrom s.endPos := by
simp [revPositions]
@[simp]
@[cbv_eval, simp]
theorem toList_revChars {s : Slice} : s.revChars.toList = s.copy.toList.reverse := by
simp [revChars, Model.map_get_revPositionsFrom_endPos]
theorem forIn_eq_forIn_chars {m : Type u Type v} [Monad m] {s : Slice} {b} {f : Char β m (ForInStep β)} :
ForIn.forIn s b f = ForIn.forIn s.chars b f := rfl
@[simp]
@[cbv_eval, simp]
theorem forIn_eq_forIn_toList {m : Type u Type v} [Monad m] [LawfulMonad m] {s : Slice} {b}
{f : Char β m (ForInStep β)} :
ForIn.forIn s b f = ForIn.forIn s.copy.toList b f := by
rw [forIn_eq_forIn_chars, Std.Iter.forIn_toList, toList_chars]
@[cbv_eval, simp]
theorem foldl_eq_foldl_toList {α : Type u} {f : α Char α} {init : α} {s : Slice} :
s.foldl f init = s.copy.toList.foldl f init := by
rw [foldl, Std.Iter.foldl_toList, toList_chars]
@[simp]
theorem foldr_eq_foldr_toList {α : Type u} {f : Char α α} {init : α} {s : Slice} :
s.foldr f init = s.copy.toList.foldr f init := by
rw [foldr, Std.Iter.foldl_toList, toList_revChars, List.foldl_reverse]
congr
end Slice
/--
@@ -251,10 +262,11 @@ theorem toList_positionsFrom {s : String} {p : s.Pos} :
(s.positionsFrom p).toList = Model.positionsFrom p := by
simp [positionsFrom, Internal.ofToSliceWithProof, Model.positionsFrom_eq_map]
@[cbv_eval]
theorem toList_positions {s : String} : s.positions.toList = Model.positionsFrom s.startPos := by
simp [positions]
@[simp]
@[cbv_eval, simp]
theorem toList_chars {s : String} : s.chars.toList = s.toList := by
simp [chars]
@@ -342,7 +354,7 @@ theorem toList_revPositions {s : String} :
s.revPositions.toList = Model.revPositionsFrom s.endPos := by
simp [revPositions]
@[simp]
@[cbv_eval, simp]
theorem toList_revChars {s : String} : s.revChars.toList = s.toList.reverse := by
simp [revChars]
@@ -355,4 +367,14 @@ theorem forIn_eq_forIn_toList {m : Type u → Type v} [Monad m] [LawfulMonad m]
ForIn.forIn s b f = ForIn.forIn s.toList b f := by
rw [forIn_eq_forIn_chars, Std.Iter.forIn_toList, toList_chars]
@[simp]
theorem foldl_eq_foldl_toList {α : Type u} {f : α Char α} {init : α} {s : String} :
s.foldl f init = s.toList.foldl f init := by
simp [foldl]
@[simp]
theorem foldr_eq_foldr_toList {α : Type u} {f : Char α α} {init : α} {s : String} :
s.foldr f init = s.toList.foldr f init := by
simp [foldr]
end String

View File

@@ -49,6 +49,14 @@ theorem toList_mapAux {f : Char → Char} {s : String} {p : s.Pos}
theorem toList_map {f : Char Char} {s : String} : (s.map f).toList = s.toList.map f := by
simp [map, toList_mapAux s.splits_startPos]
/-
Used internally by the `cbv` tactic.
-/
@[cbv_eval]
theorem map_eq_internal {f : Char Char} {s : String} : s.map f = .ofList (s.toList.map f) := by
apply String.toList_injective
simp only [toList_map, toList_ofList]
@[simp]
theorem length_map {f : Char Char} {s : String} : (s.map f).length = s.length := by
simp [ length_toList]

View File

@@ -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 :=

View File

@@ -13,3 +13,4 @@ public import Init.Data.String.Lemmas.Pattern.Char
public import Init.Data.String.Lemmas.Pattern.String
public import Init.Data.String.Lemmas.Pattern.Split
public import Init.Data.String.Lemmas.Pattern.Find
public import Init.Data.String.Lemmas.Pattern.TakeDrop

View File

@@ -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,28 +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 : ρ} [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₂ => ?_
@@ -218,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
@@ -234,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
@@ -256,34 +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
dropPrefix?_eq_some_iff (pos) : ForwardPattern.dropPrefix? pat s = some pos IsLongestMatch pat pos
[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.dropPrefix?_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.dropPrefix? pat (s.sliceFrom p₀) = none ¬ MatchesAt pat p₀ := by
ForwardPattern.skipPrefix? pat (s.sliceFrom p₀) = none ¬ MatchesAt pat p₀ := by
classical
rw [ Decidable.not_iff_not]
simp [Option.ne_none_iff_exists', LawfulForwardPatternModel.dropPrefix?_eq_some_iff]
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] [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.
@@ -293,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} :
@@ -303,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') :
@@ -318,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
@@ -326,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
@@ -358,8 +603,8 @@ theorem LawfulToForwardSearcherModel.defaultImplementation {pat : ρ} [ForwardPa
Std.PlausibleIterStep.yield, Std.IterStep.yield.injEq] at heq
rw [ heq.1, heq.2]
apply IsValidSearchFrom.matched
· rw [LawfulForwardPattern.dropPrefixOfNonempty?_eq,
LawfulForwardPatternModel.dropPrefix?_eq_some_iff] at heq'
· rw [LawfulForwardPattern.skipPrefixOfNonempty?_eq,
LawfulForwardPatternModel.skipPrefix?_eq_some_iff] at heq'
exact heq'.isLongestMatchAt_ofSliceFrom
· simp only [Std.IterM.toIter]
apply ih
@@ -372,8 +617,101 @@ theorem LawfulToForwardSearcherModel.defaultImplementation {pat : ρ} [ForwardPa
apply IsValidSearchFrom.mismatched (by simp) _ (ih _ (by simp))
intro p' hp' hp''
obtain rfl : pos = p' := Std.le_antisymm hp' (by simpa using hp'')
rwa [LawfulForwardPattern.dropPrefixOfNonempty?_eq,
LawfulForwardPatternModel.dropPrefix?_eq_none_iff] at heq'
rwa [LawfulForwardPattern.skipPrefixOfNonempty?_eq,
LawfulForwardPatternModel.skipPrefix?_sliceFrom_eq_none_iff] at heq'
· 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

View File

@@ -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
dropPrefix?_eq_some_iff {s} pos := by
simp [isLongestMatch_iff, ForwardPattern.dropPrefix?, and_comm, eq_comm (b := pos)]
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} :
@@ -136,42 +236,36 @@ theorem dropPrefix?_char_eq_dropPrefix?_beq {c : Char} {s : Slice} :
theorem dropPrefix_char_eq_dropPrefix_beq {c : Char} {s : Slice} :
s.dropPrefix c = s.dropPrefix (· == c) := (rfl)
theorem Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq {c : Char} {s : Slice} :
dropPrefix? c s = dropPrefix? (· == c) s := (rfl)
theorem skipPrefix?_char_eq_skipPrefix?_beq {c : Char} {s : Slice} :
s.skipPrefix? c = s.skipPrefix? (· == c) := (rfl)
private theorem dropWhileGo_eq {c : Char} {s : Slice} (curr : s.Pos) :
dropWhile.go s c curr = dropWhile.go s (· == c) curr := by
fun_induction dropWhile.go s c curr with
theorem Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq {c : Char} {s : Slice} :
skipPrefix? c s = skipPrefix? (· == c) s := (rfl)
theorem Pos.skipWhile_char_eq_skipWhile_beq {c : Char} {s : Slice} (curr : s.Pos) :
Pos.skipWhile curr c = Pos.skipWhile curr (· == c) := by
fun_induction Pos.skipWhile curr c with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq, h₁, h₂, ih]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq, h, ih]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq, h, ih]
| case3 pos h =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq]
theorem skipPrefixWhile_char_eq_skipPrefixWhile_beq {c : Char} {s : Slice} :
s.skipPrefixWhile c = s.skipPrefixWhile (· == c) :=
Pos.skipWhile_char_eq_skipWhile_beq s.startPos
theorem dropWhile_char_eq_dropWhile_beq {c : Char} {s : Slice} :
s.dropWhile c = s.dropWhile (· == c) := by
simpa only [dropWhile] using dropWhileGo_eq s.startPos
private theorem takeWhileGo_eq {c : Char} {s : Slice} (curr : s.Pos) :
takeWhile.go s c curr = takeWhile.go s (· == c) curr := by
fun_induction takeWhile.go s c curr with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq, h, ih]
| case3 pos h =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_char_eq_dropPrefix?_beq]
simp only [dropWhile]; exact congrArg _ skipPrefixWhile_char_eq_skipPrefixWhile_beq
theorem takeWhile_char_eq_takeWhile_beq {c : Char} {s : Slice} :
s.takeWhile c = s.takeWhile (· == c) := by
simp only [takeWhile]; exact takeWhileGo_eq s.startPos
simp only [takeWhile]; exact congrArg _ skipPrefixWhile_char_eq_skipPrefixWhile_beq
theorem all_char_eq_all_beq {c : Char} {s : Slice} :
s.all c = s.all (· == c) := by
@@ -192,47 +286,41 @@ theorem contains_char_eq_contains_beq {c : Char} {s : Slice} :
theorem endsWith_char_eq_endsWith_beq {c : Char} {s : Slice} :
s.endsWith c = s.endsWith (· == c) := (rfl)
theorem skipSuffix?_char_eq_skipSuffix?_beq {c : Char} {s : Slice} :
s.skipSuffix? c = s.skipSuffix? (· == c) := (rfl)
theorem dropSuffix?_char_eq_dropSuffix?_beq {c : Char} {s : Slice} :
s.dropSuffix? c = s.dropSuffix? (· == c) := (rfl)
theorem dropSuffix_char_eq_dropSuffix_beq {c : Char} {s : Slice} :
s.dropSuffix c = s.dropSuffix (· == c) := (rfl)
theorem Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq {c : Char} {s : Slice} :
dropSuffix? c s = dropSuffix? (· == c) s := (rfl)
theorem Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq {c : Char} {s : Slice} :
skipSuffix? c s = skipSuffix? (· == c) s := (rfl)
private theorem dropEndWhileGo_eq {c : Char} {s : Slice} (curr : s.Pos) :
dropEndWhile.go s c curr = dropEndWhile.go s (· == c) curr := by
fun_induction dropEndWhile.go s c curr with
theorem Pos.revSkipWhile_char_eq_revSkipWhile_beq {c : Char} {s : Slice} (curr : s.Pos) :
Pos.revSkipWhile curr c = Pos.revSkipWhile curr (· == c) := by
fun_induction Pos.revSkipWhile curr c with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq, h₁, h₂, ih]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq, h, ih]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq, h, ih]
| case3 pos h =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq]
theorem skipSuffixWhile_char_eq_skipSuffixWhile_beq {c : Char} {s : Slice} :
s.skipSuffixWhile c = s.skipSuffixWhile (· == c) :=
Pos.revSkipWhile_char_eq_revSkipWhile_beq s.endPos
theorem dropEndWhile_char_eq_dropEndWhile_beq {c : Char} {s : Slice} :
s.dropEndWhile c = s.dropEndWhile (· == c) := by
simpa only [dropEndWhile] using dropEndWhileGo_eq s.endPos
private theorem takeEndWhileGo_eq {c : Char} {s : Slice} (curr : s.Pos) :
takeEndWhile.go s c curr = takeEndWhile.go s (· == c) curr := by
fun_induction takeEndWhile.go s c curr with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq, h, ih]
| case3 pos h =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_char_eq_dropSuffix?_beq]
simp only [dropEndWhile]; exact congrArg _ skipSuffixWhile_char_eq_skipSuffixWhile_beq
theorem takeEndWhile_char_eq_takeEndWhile_beq {c : Char} {s : Slice} :
s.takeEndWhile c = s.takeEndWhile (· == c) := by
simpa only [takeEndWhile] using takeEndWhileGo_eq s.endPos
simp only [takeEndWhile]; exact congrArg _ skipSuffixWhile_char_eq_skipSuffixWhile_beq
end String.Slice

View File

@@ -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} :

View File

@@ -183,7 +183,7 @@ theorem find?_char_eq_some_iff_splits {c : Char} {s : String} {pos : s.Pos} :
· rintro t, u, hsplit, hnotin
exact pos.toSlice, t, u, Pos.splits_toSlice_iff.mpr hsplit, hnotin, by simp
@[simp]
@[cbv_eval, simp]
theorem contains_char_eq {c : Char} {s : String} : s.contains c = decide (c s.toList) := by
simp [contains_eq_contains_toSlice, Slice.contains_char_eq, copy_toSlice]

View File

@@ -58,7 +58,7 @@ theorem find?_prop_eq_some_iff_splits {p : Char → Prop} [DecidablePred p] {s :
simp only [find?_prop_eq_find?_decide, find?_bool_eq_some_iff_splits, decide_eq_true_eq,
decide_eq_false_iff_not]
@[simp]
@[cbv_eval, simp]
theorem contains_bool_eq {p : Char Bool} {s : Slice} : s.contains p = s.copy.toList.any p := by
rw [Bool.eq_iff_iff, Pattern.Model.contains_eq_true_iff]
simp only [Pattern.Model.CharPred.matchesAt_iff, ne_eq, List.any_eq_true,

View File

@@ -90,4 +90,12 @@ theorem contains_string_eq_false_iff {t s : String} :
s.contains t = false ¬(t.toList <:+: s.toList) :=
Bool.eq_false_iff.trans (not_congr contains_string_iff)
/-
Used internally by the `cbv` tactic.
-/
@[cbv_eval]
theorem contains_string_eq_internal {t s : String} :
s.contains t = t.toList.isInfixOf_internal s.toList := by
rw [Bool.eq_iff_iff, contains_string_iff, List.isInfixOf_internal_iff_isInfix]
end String

View File

@@ -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
dropPrefix?_eq_some_iff {s} pos := by
simp [isLongestMatch_iff, ForwardPattern.dropPrefix?, and_comm, eq_comm (b := pos)]
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 dropPrefix?_eq_dropPrefix?_decide {p : Char Prop} [DecidablePred p] :
ForwardPattern.dropPrefix? p = ForwardPattern.dropPrefix? (decide <| p ·) := rfl
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
dropPrefix?_eq_some_iff {s} pos := by
rw [dropPrefix?_eq_dropPrefix?_decide, isLongestMatch_iff_isLongestMatch_decide]
exact LawfulForwardPatternModel.dropPrefix?_eq_some_iff ..
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
@@ -181,43 +316,39 @@ theorem dropPrefix?_prop_eq_dropPrefix?_decide {p : Char → Prop} [DecidablePre
theorem dropPrefix_prop_eq_dropPrefix_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.dropPrefix p = s.dropPrefix (decide <| p ·) := (rfl)
theorem Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide
{p : Char Prop} [DecidablePred p] {s : Slice} :
dropPrefix? p s = dropPrefix? (decide <| p ·) s := (rfl)
theorem skipPrefix?_prop_eq_skipPrefix?_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.skipPrefix? p = s.skipPrefix? (decide <| p ·) := (rfl)
private theorem dropWhileGo_eq {p : Char Prop} [DecidablePred p] {s : Slice} (curr : s.Pos) :
dropWhile.go s p curr = dropWhile.go s (decide <| p ·) curr := by
fun_induction dropWhile.go s p curr with
theorem Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide
{p : Char Prop} [DecidablePred p] {s : Slice} :
skipPrefix? p s = skipPrefix? (decide <| p ·) s := (rfl)
theorem Pos.skipWhile_prop_eq_skipWhile_decide {p : Char Prop} [DecidablePred p] {s : Slice}
(curr : s.Pos) :
Pos.skipWhile curr p = Pos.skipWhile curr (decide <| p ·) := by
fun_induction Pos.skipWhile curr p with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide, h₁, h₂, ih]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide, h, ih]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide, h, ih]
| case3 pos h =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide]
theorem skipPrefixWhile_prop_eq_skipPrefixWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} :
s.skipPrefixWhile p = s.skipPrefixWhile (decide <| p ·) :=
Pos.skipWhile_prop_eq_skipWhile_decide s.startPos
theorem dropWhile_prop_eq_dropWhile_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.dropWhile p = s.dropWhile (decide <| p ·) := by
simpa only [dropWhile] using dropWhileGo_eq s.startPos
private theorem takeWhileGo_eq {p : Char Prop} [DecidablePred p] {s : Slice} (curr : s.Pos) :
takeWhile.go s p curr = takeWhile.go s (decide <| p ·) curr := by
fun_induction takeWhile.go s p curr with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide, h, ih]
| case3 pos h =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_prop_eq_dropPrefix?_decide]
simp only [dropWhile]; exact congrArg _ skipPrefixWhile_prop_eq_skipPrefixWhile_decide
theorem takeWhile_prop_eq_takeWhile_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.takeWhile p = s.takeWhile (decide <| p ·) := by
simp only [takeWhile]; exact takeWhileGo_eq s.startPos
simp only [takeWhile]; exact congrArg _ skipPrefixWhile_prop_eq_skipPrefixWhile_decide
theorem all_prop_eq_all_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.all p = s.all (decide <| p ·) := by
@@ -239,52 +370,46 @@ theorem contains_prop_eq_contains_decide {p : Char → Prop} [DecidablePred p] {
theorem endsWith_prop_eq_endsWith_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.endsWith p = s.endsWith (decide <| p ·) := (rfl)
theorem skipSuffix?_prop_eq_skipSuffix?_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.skipSuffix? p = s.skipSuffix? (decide <| p ·) := (rfl)
theorem dropSuffix?_prop_eq_dropSuffix?_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.dropSuffix? p = s.dropSuffix? (decide <| p ·) := (rfl)
theorem dropSuffix_prop_eq_dropSuffix_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.dropSuffix p = s.dropSuffix (decide <| p ·) := (rfl)
theorem Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide
theorem Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide
{p : Char Prop} [DecidablePred p] {s : Slice} :
dropSuffix? p s = dropSuffix? (decide <| p ·) s := (rfl)
skipSuffix? p s = skipSuffix? (decide <| p ·) s := (rfl)
private theorem dropEndWhileGo_eq {p : Char Prop} [DecidablePred p] {s : Slice}
(curr : s.Pos) :
dropEndWhile.go s p curr = dropEndWhile.go s (decide <| p ·) curr := by
fun_induction dropEndWhile.go s p curr with
theorem Pos.revSkipWhile_prop_eq_revSkipWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} (curr : s.Pos) :
Pos.revSkipWhile curr p = Pos.revSkipWhile curr (decide <| p ·) := by
fun_induction Pos.revSkipWhile curr p with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide, h₁, h₂, ih]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide, h, ih]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide, h, ih]
| case3 pos h =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide]
theorem skipSuffixWhile_prop_eq_skipSuffixWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} :
s.skipSuffixWhile p = s.skipSuffixWhile (decide <| p ·) :=
Pos.revSkipWhile_prop_eq_revSkipWhile_decide s.endPos
theorem dropEndWhile_prop_eq_dropEndWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} :
s.dropEndWhile p = s.dropEndWhile (decide <| p ·) := by
simpa only [dropEndWhile] using dropEndWhileGo_eq s.endPos
private theorem takeEndWhileGo_eq {p : Char Prop} [DecidablePred p] {s : Slice}
(curr : s.Pos) :
takeEndWhile.go s p curr = takeEndWhile.go s (decide <| p ·) curr := by
fun_induction takeEndWhile.go s p curr with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide, h, ih]
| case3 pos h =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_prop_eq_dropSuffix?_decide]
simp only [dropEndWhile]; exact congrArg _ skipSuffixWhile_prop_eq_skipSuffixWhile_decide
theorem takeEndWhile_prop_eq_takeEndWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} :
s.takeEndWhile p = s.takeEndWhile (decide <| p ·) := by
simpa only [takeEndWhile] using takeEndWhileGo_eq s.endPos
simp only [takeEndWhile]; exact congrArg _ skipSuffixWhile_prop_eq_skipSuffixWhile_decide
end String.Slice

View File

@@ -28,14 +28,15 @@ 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.
-/
namespace String.Slice.Pattern.Model
public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {s : Slice}
@[cbv_opaque]
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]
@@ -48,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
@@ -62,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 =
@@ -79,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
@@ -102,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
@@ -153,7 +154,8 @@ end Model
open Model
public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [ForwardPatternModel pat]
@[cbv_eval]
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
@@ -166,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) :
@@ -180,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) :
@@ -198,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

View File

@@ -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
@@ -30,6 +31,7 @@ namespace String.Slice
open Pattern.Model Pattern.Model.Char
@[cbv_eval]
theorem Pattern.Model.split_char_eq_split_beq {c : Char} {s : Slice}
(f curr : s.Pos) (hle : f curr) :
Model.split c f curr hle = Model.split (· == c) f curr hle := by
@@ -69,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
@@ -77,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

View File

@@ -58,12 +58,33 @@ theorem toList_split_bool {s : Slice} {p : Char → Bool} :
(s.split p).toList.map Slice.copy = (s.copy.toList.splitOnP p).map String.ofList := by
simp [toList_split_eq_splitToSubslice, toList_splitToSubslice_bool]
/-
Used internally by the `cbv` tactic.
-/
@[cbv_eval]
theorem Pattern.Model.split_bool_eq_internal {p : Char Bool} {s : Slice} (f curr : s.Pos) (hle : f curr) :
Model.split p f curr hle =
if h : curr = s.endPos then [s.subslice _ _ hle]
else if p (curr.get h) then
s.subslice _ _ hle :: Model.split p (curr.next h) (curr.next h) (by simp [Std.le_refl])
else Model.split p f (curr.next h) (by simp [Std.le_trans hle _]) := by
by_cases h : curr = s.endPos
· simp only [h, split_endPos, subslice_endPos, reduceDIte]
· simp only [h, reduceDIte]
by_cases hp : p (curr.get h)
· simp only [hp, reduceIte]
exact split_eq_of_isLongestMatchAt (isLongestMatchAt_of_get hp)
· rw [Bool.not_eq_true] at hp
simp only [hp, Bool.false_eq_true, reduceIte]
exact split_eq_next_of_not_matchesAt h (not_matchesAt_of_get hp)
end
section
open Pattern.Model Pattern.Model.CharPred.Decidable
@[cbv_eval]
theorem Pattern.Model.split_eq_split_decide {p : Char Prop} [DecidablePred p] {s : Slice}
(f curr : s.Pos) (hle : f curr) :
Model.split p f curr hle = Model.split (decide <| p ·) f curr hle := by

View File

@@ -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,29 +32,61 @@ 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
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₂
s.copy.toByteArray.extract pos₁.offset.byteIdx pos₂.offset.byteIdx = pat.copy.toByteArray := by
@@ -65,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]
@@ -75,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]
@@ -93,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
@@ -102,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),
@@ -140,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]
@@ -173,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₂
@@ -191,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₂
@@ -199,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
@@ -206,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]
@@ -224,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),
@@ -233,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),
@@ -253,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)
@@ -269,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

View File

@@ -39,9 +39,9 @@ theorem startsWith_iff {pat s : Slice} : startsWith pat s ↔ ∃ t, s.copy = pa
· rintro t, rfl
simp [-size_toByteArray, ByteArray.extract_append]
theorem dropPrefix?_eq_some_iff {pat s : Slice} {pos : s.Pos} :
dropPrefix? pat s = some pos (s.sliceTo pos).copy = pat.copy := by
fun_cases dropPrefix? with
theorem skipPrefix?_eq_some_iff {pat s : Slice} {pos : s.Pos} :
skipPrefix? pat s = some pos (s.sliceTo pos).copy = pat.copy := by
fun_cases skipPrefix? with
| case1 h =>
simp only [offset_startPos, Pos.Raw.offsetBy_zero, Option.some.injEq]
obtain t, ht := startsWith_iff.1 h
@@ -58,8 +58,17 @@ theorem dropPrefix?_eq_some_iff {pat s : Slice} {pos : s.Pos} :
have := h (s.sliceFrom pos).copy
simp [ heq, pos.splits.eq_append] at this
theorem isSome_dropPrefix? {pat s : Slice} : (dropPrefix? pat s).isSome = startsWith pat s := by
fun_cases dropPrefix? <;> simp_all
theorem isSome_skipPrefix? {pat s : Slice} : (skipPrefix? pat s).isSome = startsWith pat s := by
fun_cases skipPrefix? <;> simp_all
public theorem startsWith_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
ForwardPattern.startsWith pat s = true := by
suffices pat.copy = "" by simp [ForwardPattern.startsWith, startsWith_iff, this]
simpa
public theorem skipPrefix?_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
ForwardPattern.skipPrefix? pat s = some s.startPos := by
simpa [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff]
end ForwardSliceSearcher
@@ -67,12 +76,14 @@ 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
dropPrefixOfNonempty?_eq h := rfl
startsWith_eq s := isSome_dropPrefix?.symm
dropPrefix?_eq_some_iff pos := by
simp [ForwardPattern.dropPrefix?, dropPrefix?_eq_some_iff, isLongestMatch_iff hpat]
skipPrefix?_eq_some_iff pos := by
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat]
end Model.ForwardSliceSearcher
@@ -80,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
dropPrefixOfNonempty?_eq h := rfl
startsWith_eq s := isSome_dropPrefix?.symm
dropPrefix?_eq_some_iff pos := by
simp [ForwardPattern.dropPrefix?, dropPrefix?_eq_some_iff, isLongestMatch_iff hpat]
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} :
@@ -100,43 +212,38 @@ public theorem dropPrefix?_string_eq_dropPrefix?_toSlice {pat : String} {s : Sli
public theorem dropPrefix_string_eq_dropPrefix_toSlice {pat : String} {s : Slice} :
s.dropPrefix pat = s.dropPrefix pat.toSlice := (rfl)
public theorem Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice
{pat : String} {s : Slice} :
dropPrefix? pat s = dropPrefix? pat.toSlice s := (rfl)
public theorem skipPrefix?_string_eq_skipPrefix?_toSlice {pat : String} {s : Slice} :
s.skipPrefix? pat = s.skipPrefix? pat.toSlice := (rfl)
private theorem dropWhileGo_string_eq {pat : String} {s : Slice} (curr : s.Pos) :
dropWhile.go s pat curr = dropWhile.go s pat.toSlice curr := by
fun_induction dropWhile.go s pat curr with
public theorem Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice
{pat : String} {s : Slice} :
skipPrefix? pat s = skipPrefix? pat.toSlice s := (rfl)
public theorem Pos.skipWhile_string_eq_skipWhile_toSlice {pat : String} {s : Slice}
(curr : s.Pos) :
Pos.skipWhile curr pat = Pos.skipWhile curr pat.toSlice := by
fun_induction Pos.skipWhile curr pat with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice, h₁, h₂, ih]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice, h, ih]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice, h, ih]
| case3 pos h =>
conv => rhs; rw [dropWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice]
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice]
public theorem skipPrefixWhile_string_eq_skipPrefixWhile_toSlice {pat : String} {s : Slice} :
s.skipPrefixWhile pat = s.skipPrefixWhile pat.toSlice :=
Pos.skipWhile_string_eq_skipWhile_toSlice s.startPos
public theorem dropWhile_string_eq_dropWhile_toSlice {pat : String} {s : Slice} :
s.dropWhile pat = s.dropWhile pat.toSlice := by
simpa only [dropWhile] using dropWhileGo_string_eq s.startPos
private theorem takeWhileGo_string_eq {pat : String} {s : Slice} (curr : s.Pos) :
takeWhile.go s pat curr = takeWhile.go s pat.toSlice curr := by
fun_induction takeWhile.go s pat curr with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice, h, ih]
| case3 pos h =>
conv => rhs; rw [takeWhile.go]
simp [ Pattern.ForwardPattern.dropPrefix?_string_eq_dropPrefix?_toSlice]
simp only [dropWhile]; exact congrArg _ skipPrefixWhile_string_eq_skipPrefixWhile_toSlice
public theorem takeWhile_string_eq_takeWhile_toSlice {pat : String} {s : Slice} :
s.takeWhile pat = s.takeWhile pat.toSlice := by
simp only [takeWhile]; exact takeWhileGo_string_eq s.startPos
simp only [takeWhile]; exact congrArg _ skipPrefixWhile_string_eq_skipPrefixWhile_toSlice
public theorem all_string_eq_all_toSlice {pat : String} {s : Slice} :
s.all pat = s.all pat.toSlice := by
@@ -145,48 +252,43 @@ public theorem all_string_eq_all_toSlice {pat : String} {s : Slice} :
public theorem endsWith_string_eq_endsWith_toSlice {pat : String} {s : Slice} :
s.endsWith pat = s.endsWith pat.toSlice := (rfl)
public theorem skipSuffix?_string_eq_skipSuffix?_toSlice {pat : String} {s : Slice} :
s.skipSuffix? pat = s.skipSuffix? pat.toSlice := (rfl)
public theorem dropSuffix?_string_eq_dropSuffix?_toSlice {pat : String} {s : Slice} :
s.dropSuffix? pat = s.dropSuffix? pat.toSlice := (rfl)
public theorem dropSuffix_string_eq_dropSuffix_toSlice {pat : String} {s : Slice} :
s.dropSuffix pat = s.dropSuffix pat.toSlice := (rfl)
public theorem Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice
public theorem Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice
{pat : String} {s : Slice} :
dropSuffix? pat s = dropSuffix? pat.toSlice s := (rfl)
skipSuffix? pat s = skipSuffix? pat.toSlice s := (rfl)
private theorem dropEndWhileGo_string_eq {pat : String} {s : Slice} (curr : s.Pos) :
dropEndWhile.go s pat curr = dropEndWhile.go s pat.toSlice curr := by
fun_induction dropEndWhile.go s pat curr with
public theorem Pos.revSkipWhile_string_eq_revSkipWhile_toSlice {pat : String} {s : Slice}
(curr : s.Pos) :
Pos.revSkipWhile curr pat = Pos.revSkipWhile curr pat.toSlice := by
fun_induction Pos.revSkipWhile curr pat with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice, h₁, h₂, ih]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice, h, ih]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice, h, ih]
| case3 pos h =>
conv => rhs; rw [dropEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice]
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice]
public theorem skipSuffixWhile_string_eq_skipSuffixWhile_toSlice {pat : String} {s : Slice} :
s.skipSuffixWhile pat = s.skipSuffixWhile pat.toSlice :=
Pos.revSkipWhile_string_eq_revSkipWhile_toSlice s.endPos
public theorem dropEndWhile_string_eq_dropEndWhile_toSlice {pat : String} {s : Slice} :
s.dropEndWhile pat = s.dropEndWhile pat.toSlice := by
simpa only [dropEndWhile] using dropEndWhileGo_string_eq s.endPos
private theorem takeEndWhileGo_string_eq {pat : String} {s : Slice} (curr : s.Pos) :
takeEndWhile.go s pat curr = takeEndWhile.go s pat.toSlice curr := by
fun_induction takeEndWhile.go s pat curr with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice, h, ih]
| case3 pos h =>
conv => rhs; rw [takeEndWhile.go]
simp [ Pattern.BackwardPattern.dropSuffix?_string_eq_dropSuffix?_toSlice]
simp only [dropEndWhile]; exact congrArg _ skipSuffixWhile_string_eq_skipSuffixWhile_toSlice
public theorem takeEndWhile_string_eq_takeEndWhile_toSlice {pat : String} {s : Slice} :
s.takeEndWhile pat = s.takeEndWhile pat.toSlice := by
simpa only [takeEndWhile] using takeEndWhileGo_string_eq s.endPos
simp only [takeEndWhile]; exact congrArg _ skipSuffixWhile_string_eq_skipSuffixWhile_toSlice
end String.Slice

View File

@@ -0,0 +1,12 @@
/-
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.String.Lemmas.Pattern.TakeDrop.Basic
public import Init.Data.String.Lemmas.Pattern.TakeDrop.Char
public import Init.Data.String.Lemmas.Pattern.TakeDrop.Pred
public import Init.Data.String.Lemmas.Pattern.TakeDrop.String

View File

@@ -0,0 +1,147 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.String.Slice
public import Init.Data.String.TakeDrop
public import Init.Data.String.Lemmas.Pattern.Basic
import all Init.Data.String.Slice
import all Init.Data.String.TakeDrop
public section
open String.Slice Pattern Model
namespace String
namespace Slice
theorem skipPrefix?_eq_forwardPatternSkipPrefix? {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : Slice} :
s.skipPrefix? pat = ForwardPattern.skipPrefix? pat s := (rfl)
theorem startsWith_eq_forwardPatternStartsWith {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : Slice} :
s.startsWith pat = ForwardPattern.startsWith pat s := (rfl)
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 : ρ} [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 : ρ} [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]
@[simp]
theorem isSome_skipPrefix? {ρ : Type} {pat : ρ} [ForwardPattern pat] [LawfulForwardPattern pat] {s : Slice} :
(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 : ρ} [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 : ρ} [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]
@[simp]
theorem skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [LawfulForwardPattern pat]
{s : Slice} : s.skipPrefix? pat = none s.startsWith pat = false := by
rw [ Option.isNone_iff_eq_none, Option.isSome_eq_false_iff, isSome_skipPrefix?]
@[simp]
theorem dropPrefix?_eq_none_iff {ρ : Type} {pat : ρ} [ForwardPattern pat] [LawfulForwardPattern pat]
{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 : ρ} [PatternModel pat] [ForwardPattern pat]
[LawfulForwardPatternModel pat] {s res : Slice} (h : s.dropPrefix? pat = some res) :
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} :
s.skipPrefix? pat = (s.toSlice.skipPrefix? pat).map Pos.ofToSlice := (rfl)
theorem startsWith_eq_startsWith_toSlice {ρ : Type} {pat : ρ} [ForwardPattern pat] {s : String} :
s.startsWith pat = s.toSlice.startsWith pat := (rfl)
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

View File

@@ -0,0 +1,156 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.String.Slice
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
open String.Slice Pattern Model
namespace String
namespace Slice
theorem skipPrefix?_char_eq_some_iff {c : Char} {s : Slice} {pos : s.Pos} :
s.skipPrefix? c = some pos h, pos = s.startPos.next h s.startPos.get h = c := by
rw [Pattern.Model.skipPrefix?_eq_some_iff, Char.isLongestMatch_iff]
theorem startsWith_char_iff_get {c : Char} {s : Slice} :
s.startsWith c h, s.startPos.get h = c := by
simp [Pattern.Model.startsWith_iff, Char.matchesAt_iff]
theorem startsWith_char_eq_false_iff_get {c : Char} {s : Slice} :
s.startsWith c = false h, s.startPos.get h c := by
simp [Pattern.Model.startsWith_eq_false_iff, Char.matchesAt_iff]
theorem startsWith_char_eq_head? {c : Char} {s : Slice} :
s.startsWith c = (s.copy.toList.head? == some c) := by
rw [Bool.eq_iff_iff, Pattern.Model.startsWith_iff, Char.matchesAt_iff_splits]
simp only [splits_startPos_iff, exists_and_left, exists_eq_left, beq_iff_eq]
refine fun t, ht => by simp [ ht], fun h => ?_
simp only [ toList_inj, toList_append, toList_singleton, List.cons_append, List.nil_append]
cases h : s.copy.toList <;> simp_all [ ofList_inj]
theorem startsWith_char_iff_exists_append {c : Char} {s : Slice} :
s.startsWith c t, s.copy = singleton c ++ t := by
simp only [startsWith_char_eq_head?, beq_iff_eq, List.head?_eq_some_iff, toList_inj,
toList_append, toList_singleton, List.cons_append, List.nil_append]
exact fun t, ht => ofList t, by simp [ht], fun t, ht => t.toList, by simp [ht]
theorem startsWith_char_eq_false_iff_forall_append {c : Char} {s : Slice} :
s.startsWith c = false t, s.copy singleton c ++ t := by
simp [ Bool.not_eq_true, startsWith_char_iff_exists_append]
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 [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
theorem skipPrefix?_char_eq_some_iff {c : Char} {s : String} {pos : s.Pos} :
s.skipPrefix? c = some pos h, pos = s.startPos.next h s.startPos.get h = c := by
simp [skipPrefix?_eq_skipPrefix?_toSlice, Slice.skipPrefix?_char_eq_some_iff, Pos.toSlice_inj,
Pos.toSlice_next]
theorem startsWith_char_iff_get {c : Char} {s : String} :
s.startsWith c h, s.startPos.get h = c := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_char_iff_get]
theorem startsWith_char_eq_false_iff_get {c : Char} {s : String} :
s.startsWith c = false h, s.startPos.get h c := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_char_eq_false_iff_get]
theorem startsWith_char_eq_head? {c : Char} {s : String} :
s.startsWith c = (s.toList.head? == some c) := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_char_eq_head?]
theorem startsWith_char_iff_exists_append {c : Char} {s : String} :
s.startsWith c t, s = singleton c ++ t := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_char_iff_exists_append]
theorem startsWith_char_eq_false_iff_forall_append {c : Char} {s : String} :
s.startsWith c = false t, s singleton c ++ t := by
simp [ Bool.not_eq_true, startsWith_char_iff_exists_append]
theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s : String} {res : Slice} (h : s.dropPrefix? c = some res) :
s = singleton c ++ res.copy := by
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

View File

@@ -0,0 +1,211 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Markus Himmel
-/
module
prelude
public import Init.Data.String.Slice
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
open String.Slice Pattern Model
namespace String
namespace Slice
theorem skipPrefix?_bool_eq_some_iff {p : Char Bool} {s : Slice} {pos : s.Pos} :
s.skipPrefix? p = some pos h, pos = s.startPos.next h p (s.startPos.get h) = true := by
rw [Pattern.Model.skipPrefix?_eq_some_iff, CharPred.isLongestMatch_iff]
theorem startsWith_bool_iff_get {p : Char Bool} {s : Slice} :
s.startsWith p h, p (s.startPos.get h) = true := by
simp [Pattern.Model.startsWith_iff, CharPred.matchesAt_iff]
theorem startsWith_bool_eq_false_iff_get {p : Char Bool} {s : Slice} :
s.startsWith p = false h, p (s.startPos.get h) = false := by
simp [Pattern.Model.startsWith_eq_false_iff, CharPred.matchesAt_iff]
theorem startsWith_bool_eq_head? {p : Char Bool} {s : Slice} :
s.startsWith p = s.copy.toList.head?.any p := by
rw [Bool.eq_iff_iff, Pattern.Model.startsWith_iff, CharPred.matchesAt_iff]
by_cases h : s.startPos = s.endPos
· refine fun h', _ => by simp_all, ?_
have : s.copy = "" := by simp_all [Slice.startPos_eq_endPos_iff]
simp [this]
· obtain t, ht := s.splits_startPos.exists_eq_singleton_append h
simp [h, ht]
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 [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} :
s.skipPrefix? P = some pos h, pos = s.startPos.next h P (s.startPos.get h) := by
simp [skipPrefix?_prop_eq_skipPrefix?_decide, skipPrefix?_bool_eq_some_iff]
theorem startsWith_prop_iff_get {P : Char Prop} [DecidablePred P] {s : Slice} :
s.startsWith P h, P (s.startPos.get h) := by
simp [startsWith_prop_eq_startsWith_decide, startsWith_bool_iff_get]
theorem startsWith_prop_eq_false_iff_get {P : Char Prop} [DecidablePred P] {s : Slice} :
s.startsWith P = false h, ¬ P (s.startPos.get h) := by
simp [startsWith_prop_eq_startsWith_decide, startsWith_bool_eq_false_iff_get]
theorem startsWith_prop_eq_head? {P : Char Prop} [DecidablePred P] {s : Slice} :
s.startsWith P = s.copy.toList.head?.any (decide <| P ·) := by
simp [startsWith_prop_eq_startsWith_decide, startsWith_bool_eq_head?]
theorem eq_append_of_dropPrefix_prop_eq_some {P : Char Prop} [DecidablePred P] {s res : Slice} (h : s.dropPrefix? P = some res) :
c, s.copy = singleton c ++ res.copy P c := by
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} :
s.skipPrefix? p = some pos h, pos = s.startPos.next h p (s.startPos.get h) = true := by
simp [skipPrefix?_eq_skipPrefix?_toSlice, Slice.skipPrefix?_bool_eq_some_iff, Pos.toSlice_inj,
Pos.toSlice_next]
theorem startsWith_bool_iff_get {p : Char Bool} {s : String} :
s.startsWith p h, p (s.startPos.get h) = true := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_bool_iff_get]
theorem startsWith_bool_eq_false_iff_get {p : Char Bool} {s : String} :
s.startsWith p = false h, p (s.startPos.get h) = false := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_bool_eq_false_iff_get]
theorem startsWith_bool_eq_head? {p : Char Bool} {s : String} :
s.startsWith p = s.toList.head?.any p := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_bool_eq_head?]
theorem eq_append_of_dropPrefix?_bool_eq_some {p : Char Bool} {s : String} {res : Slice} (h : s.dropPrefix? p = some res) :
c, s = singleton c ++ res.copy p c = true := by
rw [dropPrefix?_eq_dropPrefix?_toSlice] at h
simpa using Slice.eq_append_of_dropPrefix?_bool_eq_some h
theorem skipPrefix?_prop_eq_some_iff {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} :
s.skipPrefix? P = some pos h, pos = s.startPos.next h P (s.startPos.get h) := by
simp [skipPrefix?_eq_skipPrefix?_toSlice, Slice.skipPrefix?_prop_eq_some_iff, Pos.toSlice_inj,
Pos.toSlice_next]
theorem startsWith_prop_iff_get {P : Char Prop} [DecidablePred P] {s : String} :
s.startsWith P h, P (s.startPos.get h) := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_prop_iff_get]
theorem startsWith_prop_eq_false_iff_get {P : Char Prop} [DecidablePred P] {s : String} :
s.startsWith P = false h, ¬ P (s.startPos.get h) := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_prop_eq_false_iff_get]
theorem startsWith_prop_eq_head? {P : Char Prop} [DecidablePred P] {s : String} :
s.startsWith P = s.toList.head?.any (decide <| P ·) := by
simp [startsWith_eq_startsWith_toSlice, Slice.startsWith_prop_eq_head?]
theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char Prop} [DecidablePred P] {s : String} {res : Slice}
(h : s.dropPrefix? P = some res) : c, s = singleton c ++ res.copy P c := by
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

Some files were not shown because too many files have changed in this diff Show More