Compare commits

...

188 Commits

Author SHA1 Message Date
Sebastian Ullrich
35b4c7dbfc feat: implicit public meta import Init in non-prelude files (#13323)
Ensure metaprograms have implicit access to `Init` like everyone else.
Closes #13310.
2026-04-08 11:46:46 +00:00
Joachim Breitner
2398d2cc66 feat: no [defeq] attribute on sizeOf_spec lemmas (#13320)
This PR changes the auto-generated `sizeOf` definitions to be not
exposed and the `sizeOf_spec` theorem to be not marked `[defeq]`.
2026-04-08 11:10:50 +00:00
Kim Morrison
8353964e55 feat: wire PowIdentity into grind ring solver (#13088)
This PR wires the `PowIdentity` typeclass (from
https://github.com/leanprover/lean4/pull/13086) into the `grind` ring
solver's Groebner basis engine.

When a ring has a `PowIdentity α p` instance, the solver pushes `x ^ p =
x` as a new fact for each variable `x`, which becomes `x^p - x = 0` in
the Groebner basis. Since `p` is an `outParam`, instance discovery is
decoupled from `IsCharP` — the solver synthesizes `PowIdentity α ?p`
with a fresh metavar and lets instance search find both the instance and
the exponent.

This correctly handles non-prime finite fields: for `F_4` (char 2, 4
elements), Mathlib would provide `PowIdentity F_4 4` and the solver
would discover `p = 4`, not `p = 2`.

Note: the original motivating example `(x + y)^2 = x^128 + y^2` from
https://github.com/leanprover/lean4/issues/12842 does not yet work
because the `ToInt` module lifts `Fin 2` expressions to integers and
expands `x^128` via the binomial theorem before the ring solver can
reduce it. Addressing that is a separate deeper change.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:14:10 +00:00
Kim Morrison
334d9bd4f3 feat: add markMeta parameter to addAndCompile (#13311)
This PR adds an optional `markMeta : Bool := false` parameter to
`addAndCompile`, so that callers can propagate the `meta` marking
without manually splitting into `addDecl` + `markMeta` + `compileDecl`.

Also updates `ParserCompiler` to use the new parameter.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:09:01 +00:00
Kim Morrison
f7f5fc5ecd feat: add PowIdentity typeclass for grind ring solver (#13086)
This PR adds a `Lean.Grind.PowIdentity` typeclass stating that `x ^ p =
x` for all elements of a commutative semiring, with `p` as an
`outParam`.

The primary source of instances is Fermat's little theorem: for a finite
field with `q` elements, `x ^ q = x`. Since `p` is an `outParam`,
instance synthesis discovers the correct exponent automatically — the
solver does not need to know the characteristic or cardinality in
advance.

A concrete instance for `Fin 2` is provided. Mathlib can provide
instances for general finite fields via `FiniteField.pow_card`.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:05:12 +00:00
Joachim Breitner
659db85510 fix: suggest (rfl) not id rfl in linter (#13319)
This PR amends #13317 to suggest `:= (rfl)` as the recommended way to
avoid a theorem to be automatically marked `[defeq]`, for consistency
with existing documentation. Rationale: the special treatment of `:=
rfl` is based on syntax, not the proof term, so it’s appropriate to use
different syntax. And also I like the way it reads like a “muted whisper
of `rfl`”.
2026-04-08 08:21:23 +00:00
Wojciech Różowski
91dd99165a feat: add warning when applying global attribute using in (#13223)
This PR adds a warning preventing a user from applying global attribute
using `... in ...`, e.g.
```lean4
theorem a : True := trivial
attribute [simp] a in
def b : True := a
```
2026-04-08 06:20:34 +00:00
Lean stage0 autoupdater
e44351add9 chore: update stage0 2026-04-08 05:43:47 +00:00
Leonardo de Moura
fd2723d9c0 feat: add linter for rfl simp theorems at restricted transparency (#13317)
This PR adds an opt-in linter (`set_option simp.rfl.checkTransparency
true`) that warns when a `rfl` simp theorem's LHS and RHS are not
definitionally equal at `.instances` transparency. Bad rfl-simp theorems
— those that only hold at higher transparency — create problems
throughout the system because `simp` and `dsimp` operate at restricted
transparency. The linter suggests two fixes: use `id rfl` as the proof
(to remove the `rfl` status), or mark relevant constants as
`[implicit_reducible]`.

This is part of a broader effort to ensure `isDefEq` respects
transparency levels. The linter helps systematically identify
problematic rfl-simp theorems so they can be fixed incrementally.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 04:49:07 +00:00
Kim Morrison
ad2105dc94 feat: add List.prod, Array.prod, and Vector.prod (#13200)
This PR adds `prod` (multiplicative fold) for `List`, `Array`, and
`Vector`, mirroring the existing `sum` API. Includes basic simp lemmas
(`prod_nil`, `prod_cons`, `prod_append`, `prod_singleton`,
`prod_reverse`, `prod_push`, `prod_eq_foldl`), Nat-specialized lemmas
(`prod_pos_iff_forall_pos_nat`, `prod_eq_zero_iff_exists_zero_nat`,
`prod_replicate_nat`), Int-specialized lemmas (`prod_replicate_int`),
cross-type lemmas (`prod_toArray`, `prod_toList`), and `Perm.prod_nat`
with grind patterns.

The min/max pigeonhole-style bounds from the `sum` Nat/Int files are
omitted as they don't have natural multiplicative analogues.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 04:01:49 +00:00
Mac Malone
235aedfaf7 chore: use --wfail for core CI build (#13294)
This PR introduces the `WFAIL` CMake setting that uses `--wfail` for
Lake builds of core.

This also, by extension, fixes a mismatch in the trace of core CI builds
vs local core builds.
2026-04-08 02:17:39 +00:00
Kim Morrison
30dca7b545 fix: make delta-derived Prop-valued instances theorems (#13304)
This PR makes the delta-deriving handler create `theorem` declarations
instead of `def` declarations when the instance type is a `Prop`.
Previously, `deriving instance Nonempty for Foo` would always create a
`def`, which is inconsistent with the behavior of a handwritten
`instance` declaration.

For example, given:
```lean
def Foo (α : Type u) := List α
deriving instance Nonempty for Foo
```

Before: `@[implicit_reducible] def instNonemptyFoo ...`
After: `@[implicit_reducible] theorem instNonemptyFoo ...`

The implementation checks `isProp result.type` after constructing the
instance closure, and uses `mkThmOrUnsafeDef` for the Prop case (which
also handles the unsafe fallback correctly). The noncomputable check is
skipped for Prop-typed instances since theorems can freely reference
noncomputable constants.

Closes #13295

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 01:19:48 +00:00
Kim Morrison
7e04970c58 fix: skip nightly-testing merge when branch does not exist (#13308)
This PR makes `release_steps.py` robust to release repos that have no
`nightly-testing` branch. Previously, `git merge origin/nightly-testing`
would fail with "not something we can merge" and the error handler
misinterpreted this as a merge conflict, then crashed trying to commit
with nothing to commit. Now we check for the branch with
`git ls-remote --heads` before attempting the merge.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 00:31:22 +00:00
Kim Morrison
0a6ee838df fix: update lean-toolchain in verso test-projects during release (#13309)
This PR updates `release_steps.py` to sync all `lean-toolchain` files in
verso's `test-projects/` subdirectories to match the root toolchain
during release bumps. The verso CI "SubVerso version consistency" check
requires these to match, and the script was only syncing
`lake-manifest.json` sub-manifests but not toolchain files.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 00:29:38 +00:00
Sebastian Graf
ec72785927 chore: git ignore .claude/worktrees (#13299)
This PR ignores `.claude/worktrees` after I have had one instance too
many where I nuked all my worktrees due to a brain-dead `git clean
-fxd`.
2026-04-07 09:51:39 +00:00
Keith Seim
ba33c3daa4 fix: match stub signature of lean_uv_dns_get_info to real implementation (#13234)
This PR fixes a build issue when Lean is not linked against libuv.

## Problem

In `src/runtime/uv/dns.cpp`, the non-libuv stub of
`lean_uv_dns_get_info` (in the `#else` branch, compiled when building
without libuv) has a **4-parameter** signature:

```cpp
lean_uv_dns_get_info(b_obj_arg name, b_obj_arg service, uint8_t family, int8_t protocol)
```

But the real implementation above the `#else` has only **3 parameters**:

```cpp
lean_uv_dns_get_info(b_obj_arg name, b_obj_arg service, uint8_t family)
```

The Lean `@[extern]` declaration also expects 3 parameters. The stub has
an extra `int8_t protocol` parameter that the real function and the Lean
FFI caller do not use.

## Fix

Remove the extra `protocol` parameter from the stub so both branches
have the same signature.

## Evidence

Discovered while building Lean4 to WASM via Emscripten for a production
project ([specify-lean](https://github.com/kjsdesigns/specify)) since
v4.27.0. The stub branch is compiled in this configuration, and the
signature mismatch was caught at link time. The fix has been stable in
production across multiple Lean version bumps.

Related: [Zulip thread on WASM build
fixes](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/WASM.20build.20fixes.3A.20libuv.20symbol.20leakage.20.28.236817.29.20and.20unique_lo/with/580836892)
(2026-03-21).

Co-authored-by: Keith Seim <keith@MacBook-Pro.local>
2026-04-07 09:39:53 +00:00
Keith Seim
db1e2ac34c fix: add missing release() and adopt_lock_t to single-threaded unique_lock stub (#13233)
This PR fixes runtime build issues when `LEAN_MULTI_THREAD` is not set.

## Problem

When building with `LEAN_MULTI_THREAD` undefined (required for
Emscripten/WASM targets), the stub `unique_lock<T>` in
`src/runtime/thread.h` is missing two members that the real
`std::unique_lock` provides:

1. **`release()`** — called by runtime code paths, causes a compile
error when the stub is active
2. **`unique_lock(T const &, std::adopt_lock_t)`** — required by code
that acquires a lock before constructing the `unique_lock`

The other stubs in this file (`mutex`, `lock_guard`,
`condition_variable`) are complete; only `unique_lock` is missing API
surface.

## Fix

Add the two missing members to the single-threaded `unique_lock` stub:

```cpp
unique_lock(T const &, std::adopt_lock_t) {}
T * release() { return nullptr; }
```

Both are no-ops, matching the semantics of a single-threaded
environment. `release()` returns `nullptr` (no mutex to release). The
`adopt_lock_t` constructor is a no-op (no lock to adopt).

## Evidence

I've been using this fix in a production project
([specify-lean](https://github.com/kjsdesigns/specify)) since v4.27.0 to
build the Lean4 runtime to WASM via Emscripten. The fix has been stable
across multiple Lean version bumps.

I posted about this on
[Zulip](https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/WASM.20build.20fixes.3A.20libuv.20symbol.20leakage.20.28.236817.29.20and.20unique_lo/with/580836892)
on 2026-03-21.

Co-authored-by: Keith Seim <keith@MacBook-Pro.local>
2026-04-07 09:30:13 +00:00
Jason Yuen
cb06946972 chore: fix typo in annotatedBorrows (#13276)
Fixes a public-facing typo from #13274.
2026-04-07 09:17:26 +00:00
Sebastian Ullrich
4f6bcc5ada chore: avoid segfault in stage2 (#13296)
Lake.Load must not define metaprograms under the current `--plugin`
setup
2026-04-07 09:08:04 +00:00
Jason Yuen
0650cbe0fa chore: fix typo in isInvalidContinuationByte (#13275)
Fixes a public-facing typo from #13274.
2026-04-07 09:01:19 +00:00
Kim Morrison
8bb07f336d chore: add leansqlite to release repos (#13293)
This PR adds `leansqlite` to `release_repos.yml` and lists it as a
dependency of `doc-gen4`, which requires it.

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:54:42 +00:00
Mac Malone
c16e88644c feat: Runtime.hold (#13270)
This PR adds `Runtime.hold`, which ensures its argument remains alive
until the callsite by holding a reference to it. This can be useful for
unsafe code (such as an FFI) that relies on a Lean object not being
freed until after some point in the program.

It is implemented via a `static inline` function in `lean.h` so that C
will optimize away the call.

---------

Co-authored-by: Henrik Böving <hargonix@gmail.com>
2026-04-07 08:44:35 +00:00
Mac Malone
96d502bd11 refactor: introduce LakefileConfig & well-formed workspaces (#13282)
This PR introduces `LakefileConfig`, which can be constructed from a
Lake configuration file without all the information required to
construct a full `Package`. Also, workspaces now have a well-formedness
property attached which ensures the workspace indices of its packages
match their index in the workspace. Finally, the facet configuration map
now has its own type: `FacetConfigMap`.

Created by splitting off the major self-contained (but overlapping)
refactors from #11662.
2026-04-06 23:34:48 +00:00
Joachim Breitner
d48863fc2b fix: add checkInterrupted to Core.withIncRecDepth (#13290)
This PR adds a `checkInterrupted` call to `Core.withIncRecDepth`, so
that all recursive CoreM-based operations (inferType, whnf, isDefEq,
simp, …) check for cancellation on each recursion step. Previously,
these operations could run for seconds without responding to IDE
cancellation requests.

This is the single highest-impact change for IDE cancellation
responsiveness. It covers all recursive MetaM/TacticM operations at
once, eliminating the vast majority of multi-second gaps identified by
the `LEAN_CHECK_SYSTEM_INTERVAL_MS` monitoring in #13212.

Gap measurements pending — will be added after CI benchmarks and
instrumented measurement.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 13:45:30 +00:00
Joachim Breitner
c4a664eb5d fix: add checkSystem to LCNF compiler passes (#13231)
This PR adds checkSystem calls to the LCNF compiler to improve IDE
cancellation responsiveness during compilation of large declarations.

Two changes:
1. `Pass.mkPerDeclaration` now calls `checkSystem` before processing
each declaration, allowing interruption between declarations.
2. The LCNF `simp` pass calls `checkSystem` every 128 recursive visits,
allowing interruption within large single-declaration simplifications.

Note: LCNF simp has its own custom `withIncRecDepth` (in `SimpM.lean`)
that does **not** call `checkInterrupted`, unlike
`Core.withIncRecDepth`. So this `checkSystem` call is the only
cancellation check on this code path.

Performance: unconditional `checkSystem` added +0.44% instructions on
`big_do.lean` per CI benchmarks. Amortizing to every 128 visits brings
overhead to noise level while keeping the max simp gap under ~80M
instructions (~14ms at 6 Ginstr/s).

**Before** (measured with `LEAN_CHECK_SYSTEM_INTERVAL_INSN` on the
instrumentation branch):

| Test | Largest LCNF gap | Time at 6 Ginstr/s |
|------|------------------|-------------------|
| `big_do.lean` | 12,623M insn (simp) | 2.1s |
| `riscv-ast.lean` | 1,162M insn (simp) | 194ms |
| `riscv-ast.lean` | 37 gaps total | — |

**After:**

| Test | Largest LCNF gap | Time at 6 Ginstr/s |
|------|------------------|-------------------|
| `big_do.lean` | 869M insn (resetReuse) | 145ms |
| `riscv-ast.lean` | 232M insn (simp) | 39ms |
| `riscv-ast.lean` | reduced gap count | — |

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 09:35:58 +00:00
Leonardo de Moura
0cd6dbaad2 feat: add Sym.Arith infrastructure for arithmetic normalization (#13289)
This PR adds the shared infrastructure for arithmetic normalization in
`Sym.Arith/`,
laying the groundwork for both `Sym.simp`'s arith pre-simproc and the
eventual
unification of grind's `CommRing` module.

The key components:

- **`Types.lean`**: Classification structures (`Semiring`, `Ring`,
`CommRing`,
`CommSemiring`) stored in a `SymExtension`. These are the
classification-only
subset of grind's ring types — no solver state. Includes
`withExpThreshold` for
  controlling exponent evaluation limits.

- **`EvalNum.lean`**: `evalNat?`/`evalInt?` for evaluating ground
Nat/Int
expressions in type classes (e.g., `IsCharP`), adapted from grind to
`SymM`.

- **`Classify.lean`**: Algebraic structure detection (CommRing > Ring >
CommSemiring > Semiring) with a single `typeClassify : PHashMap ExprPtr
ClassifyResult` cache.
  Detects `IsCharP`, `NoNatZeroDivisors`, and `Field` instances.

- **Type classes**: `MonadCanon`, `MonadRing`/`MonadCommRing`,
`MonadSemiring`/`MonadCommSemiring`, `MonadGetVar`/`MonadMkVar` —
abstract over
the monad so the same code works in both `SymM` and grind's
`RingM`/`SemiringM`.
Grind's `MonadCanon` is deleted; grind's monads inherit it from `SymM`
via
  `MonadLift`.

- **`Functions.lean`**: Cached function getters (`getAddFn`, `getMulFn`,
etc.)
generic over the type classes. Synthesizes instances, validates via
`isDefEqI`,
  canonicalizes via `canonExpr`.

- **`Reify.lean`**: Converts Lean expressions into
`RingExpr`/`SemiringExpr` for
reflection-based normalization. Variable creation abstracted via
`MonadMkVar`.

- **`DenoteExpr.lean`**: Converts reified expressions back to Lean
`Expr`s.
Roundtrip tests confirm reify→denote produces definitionally equal
results.

- **`ToExpr.lean`**, **`VarRename.lean`**, **`Poly.lean`**: Moved from
`Grind.Arith.CommRing/` — pure utilities on `Grind.CommRing` types with
no
  solver dependencies.

- **Tests**: Unit tests for classification (`Int` → commRing, `Nat` →
commSemiring,
`Rat` → commRing), `evalNat?`/`evalInt?`, exp threshold, and
reify-denote roundtrips.
  
  
**TODO**: use abstractions to implement `grind` ring module, and delete
code duplication.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 05:21:09 +00:00
Joachim Breitner
34d00cb50d feat: add checkSystem calls to bv_decide for cancellation responsiveness (#13284)
This PR adds `checkSystem` calls to the `bv_decide` tactic's main loops
to improve IDE cancellation responsiveness. Previously, `bv_decide` had
zero `checkSystem` calls in its entire codebase, meaning long-running
invocations could not be cancelled promptly.

Insertion points:
- `fixpointPipeline` (normalization fixpoint loop)
- `ReifiedBVExpr.of.go` (recursive BitVec expression reification)
- `ReifiedBVLogical.of.go` (recursive boolean expression reification)
- `reflectBV` (hypothesis loop)

These cover the MetaM-based phases of `bv_decide`. The pure computation
phases (bitblasting via `IO.lazyPure`, AIG→CNF conversion) remain
without checks and would require restructuring to address.

Gap measurements (at 6.0 Ginstr/s, using
`LEAN_CHECK_SYSTEM_INTERVAL_INSN` from #13212):
- `bv_decide_large_aig.lean`: before ~3.3s max gap (19,899M insn),
after: pure bitblast/AIG gaps remain but reification and normalization
phases are now interruptible
- Other bv_decide bench files: normalization and reification loops are
now responsive to cancellation

Note: the largest remaining gaps in bv_decide benchmarks are in the
kernel type checker (proof term checking, up to 10.5s on
`bv_decide_mod.lean`) and in pure bitblasting — both out of scope for
this PR.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 13:11:39 +00:00
Tom Levy
a73be70607 doc: fix typo in doc of Functor.mapConst (#13285)
This PR fixes a typo.

---

Additional comment:

I found the description hard to read (in part because the term "constant
function" is not one I encounter frequently enough, and because there is
no explicit constant function in the signature). Would you consider
changing the first sentence from "Mapping a constant function" to
"Replaces the value in a functor with a constant, retaining the
functor's structure" (based on Functor.discard)?

Also, I would write `(fun _ => a)` rather than `Function.const _ a`.

---------

Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2026-04-05 09:16:49 +00:00
Lean stage0 autoupdater
3d49476058 chore: update stage0 2026-04-05 00:39:54 +00:00
Leonardo de Moura
adc45d7c7b feat: mark exposed match auxiliary declarations as implicit_reducible (#13281)
This PR marks any exposed (non-private) auxiliary match declaration as
`[implicit_reducible]`. This is essential when the outer declaration is
marked as `instance_reducible` — without it, reduction is blocked at the
match auxiliary. We do not inherit the attribute from the parent
declaration because match auxiliary declarations are reused across
definitions, and the reducibility setting of the parent can change
independently. This change prepares for implementing the TODO at
`ExprDefEq.lean:465`, which would otherwise cause too many failures
requiring manual `[implicit_reducible]` annotations on match
declarations whose names are not necessarily derived from the outer
function.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 23:55:47 +00:00
Jason Yuen
9efba691e7 chore: fix typo in checkArtifactsExist (#13277)
This PR fixes a public-facing typo in a function name:
`Module.checkArtifactsExsist` -> `Module.checkArtifactsExist`.
2026-04-04 18:56:10 +00:00
Lean stage0 autoupdater
681856324f chore: update stage0 2026-04-04 18:50:11 +00:00
Leonardo de Moura
9f49ea63e2 feat: add backward.isDefEq.respectTransparency.types option (#13280)
This PR adds a new option `backward.isDefEq.respectTransparency.types`
that controls the transparency used when checking whether the type of a
metavariable matches the type of the term being assigned to it during
`checkTypesAndAssign`. Previously, this check always bumped transparency
to `.default` (via `withInferTypeConfig`), which is overly permissive.
The new option uses `.instances` transparency instead (via
`withImplicitConfig`), matching the behavior already used for implicit
arguments.

The option defaults to `false` (preserving old behavior) until stage0 is
updated and breakage is assessed. If
`backward.isDefEq.respectTransparency` (already in v4.29) is set to
`false`, then `backward.isDefEq.respectTransparency.types` is
automatically treated as `false` too.

When `diagnostics` is enabled, a trace message is emitted if the
stricter transparency fails but `.default` would have succeeded, helping
identify affected code. To investigate failures when enabling
`backward.isDefEq.respectTransparency.types`, use:

```
set_option diagnostics true
set_option trace.diagnostics true
```

Also renames `withInstanceConfig` to `withImplicitConfig` since it now
serves implicit argument and type checking, not just instances.
Registers the `diagnostics` trace class in `CoreM`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 18:05:33 +00:00
Jason Yuen
3770b3dcb8 chore: fix spelling errors (#13274)
This PR fixed typos:

```
pip install codespell --upgrade
codespell --summary --ignore-words-list enew,forin,fro,happend,hge,ihs,iterm,spred --skip stage0 --check-filenames
codespell --summary --ignore-words-list enew,forin,fro,happend,hge,ihs,iterm,spred --skip stage0 --check-filenames --regex '[A-Z][a-z]*'
codespell --summary --ignore-words-list enew,forin,fro,happend,hge,ihs,iterm,spred --skip stage0 --check-filenames --regex "\b[a-z']*"
```
2026-04-04 07:34:34 +00:00
Leonardo de Moura
3c6ea49d0e feat: add mkAppNS, mkAppRevS, betaRevS, betaS, and related Sym functions (#13273)
This PR adds a comprehensive public API for constructing maximally
shared
expression applications and performing beta reduction in the `Sym`
framework.
These functions were previously defined locally in the VC generator and
cbv
tactic, and are needed by downstream `SymM`-based tools.

New functions in `Lean.Meta.Sym.Internal` (generic over
`MonadShareCommon`):
- `mkAppS₆` through `mkAppS₁₁` (higher-arity application builders)
- `mkAppRangeS`, `mkAppNS` (forward application over arrays/ranges)
- `mkAppRevRangeS`, `mkAppRevS` (reversed application over
arrays/ranges)

New public functions in `Lean.Meta.Sym` (`SymM`):
- `betaRevS` and `betaS` (beta reduction with max sharing)
- `mkForallFVarsS` (forall abstraction with max sharing)

The `AlphaShareBuilderM`-specific `mkAppRevRangeS` in
`InstantiateS.lean` is
replaced by the generic version from `Internal`, and the internal
`betaRevS`
is renamed to `betaRevS'`. The `Cbv.mkAppNS` now delegates to
`Internal.mkAppNS`.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 02:31:16 +00:00
Leonardo de Moura
608e0d06a8 fix: extend sym canonicalizer reductions to value positions (#13272)
This PR extends the sym canonicalizer to apply reductions (projection,
match/ite/cond, Nat
arithmetic) in all positions, not just inside types. Previously, a value
`v` appearing in a
type `T(v)` could remain unreduced while `T(v)` was normalized, breaking
the invariant that
definitionally equal types are structurally identical after
canonicalization.

Changes:
- Remove `insideType` guards from `canonApp` and `canonProj`, so
reductions apply unconditionally
(eta reduction remains type-only, to preserve lambda structure for
`grind`)
- Add `canonInstDecCore` to handle `Decidable` instances in
`if-then-else` expressions, dispatching
`Grind.nestedDecidable` to `canonInstDec` and falling back silently for
other instances
- Add `report` parameter to `canonInstCore`/`canonInst'`/`canonInst` to
allow suppressing issue
reporting for propositional and decidable instances that cannot be
resynthesized (common with
`haveI`-provided instances that propagate into types through forward
dependencies)
- Update module documentation to reflect the new reduction scope and the
`haveI` reporting tradeoff

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 01:52:24 +00:00
Leonardo de Moura
5fdeaf0d5a fix: handle propositional and decidable instances in sym canonicalizer (#13271)
This PR refactors instance canonicalization in the sym canonicalizer to
properly handle
\`Grind.nestedProof\` and \`Grind.nestedDecidable\` markers. Previously,
the canonicalizer
would report an issue when it failed to resynthesize propositional
instances that were
provided by \`grind\` itself or by the user via \`haveI\`. Now,
resynthesis failure gracefully
falls back to the original instance in value positions, while remaining
strict inside types.

Changes:
- Extract \`canonInstCore\` as the shared resynthesis + defEq-check
logic
- Add \`canonInstProp\` for \`Grind.nestedProof\`: canonicalize the
proposition, attempt resynthesis, fall back silently (proof irrelevance
means no defEq check needed)
- Add \`canonInstDec\`/\`canonInstDec'\` for \`Grind.nestedDecidable\`:
canonicalize the proposition, attempt resynthesis with defEq guard, fall
back silently
- Remove the separate \`cacheInsts\` cache in favor of the existing
type/value caches via \`withCaching\`
- Update module-level documentation

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 00:40:39 +00:00
Julia Markus Himmel
da91aed2e2 feat: verification of String.dropWhile and String.takeWhile (#13155)
This PR verifies the `String.dropWhile` and `String.takeWhile`
functions.

It also includes a refactor of the `PatternModel` class so that the
`not_matches_empty` condition is moved into a separate typeclass
`StrictPatternModel`. This allows string patterns to implement
`LawfulForwardPatternModel` unconditionally, which means that more of
the general theory about patterns directly applies to string patterns
without having to do a case distinction for empty strings.

This PR also includes a study of the `PatternModel` machinery given to
slices `s` and `t` such that `s.copy = t.copy`. From these results, we
deduce statements like `s.copy.startsWith pat = s.startsWith pat` (which
is far from obvious!).
2026-04-03 14:02:21 +00:00
Joachim Breitner
e57d84bba0 fix: show missing match cases in declaration order (#13266)
This PR changes the counter-example accumulator in the match compiler
from
a `List` (built with cons, producing reverse order) to an `Array` (built
with push, preserving declaration order). Missing cases are now reported
in
the order constructors appear in the inductive type definition.

For example, given `inductive Enum | a | b | c | d`, missing cases `c`
and
`d` were previously shown as `d, c` and are now shown as `c, d`.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 13:33:54 +00:00
Sebastian Ullrich
772b5663d2 perf: correct over-allocated capacity for imported constant hashmaps (#13238)
privateConstantMap capacity was inflated by IR extraConstNames that are
only inserted into const2ModIdx. const2ModIdx capacity included
numPublicConsts even though public constants are never inserted into it.
2026-04-03 08:58:42 +00:00
Joachim Breitner
c7983a8c65 perf: limit counter-example generation in match compiler (#13222)
This PR adds a `match.maxCounterExamples` option (default 5) to bound
the
number of "missing cases" counter-examples the match compiler generates.

When the match compiler runs out of alternatives for a variable, it
case-splits to explore missing cases. Previously, this would recursively
split all inductive-typed fields of each constructor, leading to
O(K^fields)
counter-examples for types with K constructors per field. For nested
incomplete matches on types like `Op w` (20 constructors with `Operand
w`
fields having 8 constructors each), this produced thousands of
counter-examples and took several seconds.

The fix checks the counter-example count in `isConstructorTransition`:
once
the limit is reached and there are no remaining alternatives, the match
compiler stops exploring further case splits. The error message notes
when
output has been truncated and names the option. The existing protection
against infinite recursion on recursive types is preserved.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:37:20 +00:00
Joachim Breitner
d3b04871f5 perf: add checkInterrupted to inferType cache miss path (#13258)
This PR adds a `Core.checkInterrupted` call in `checkInferTypeCache` on
cache miss, allowing cancellation to be detected during large type
inference traversals. Previously, `inferTypeImp` could run for >100ms
without any interruption check when processing large expressions (e.g.
BVDecide proof terms), making IDE cancellation unresponsive.

The check is only on cache miss (actual computation), so cache hits have
zero overhead. `checkInterrupted` is used instead of the heavier
`checkSystem` to minimize performance impact — local benchmarks show no
measurable regression on `tests/elab/5664.lean`.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 06:03:55 +00:00
Kyle Miller
acae2b44fd feat: no default values for structure instance notation patterns (#13243)
This PR changes elaboration of structure instance notation when used in
patterns (e.g. `s matches { x := 1, y := [] }`) so that the structure's
default values are not used to elaborate the pattern. The motivation is
that default values frequently lead to surprisingly over-specific
patterns. It will now report "field missing" errors. The error can be
suppressed using `{ x := 1, .. }` ellipsis notation, which has the same
behavior as before. The pretty printer is also modified to stay in sync
with this feature. **Breaking change:** patterns using structure
instance notation may need missing fields or a `..` added, as
appropriate.

The rationale for the previous behavior is that with `..` you could
opt-in to not using default values, and now with this PR's behavior you
cannot opt-in. However, default values in structure instance patterns
are very likely to silently cause bugs. There are a couple examples in
this PR of unintentional default values in patterns in core Lean
(luckily these were not triggering bugs). With the new behavior, you can
now tell for sure whether every explicit field in a structure is being
matched explicitly or not, by the absence or presence of `..`.

Closes #10753
2026-04-03 03:25:23 +00:00
Paul Reichert
fcc070f18f chore: getElemV tests (#13249)
This PR adds a test file containing `V` operations and examples for
papercuts using them.
2026-04-02 21:24:04 +00:00
Wojciech Różowski
9aad86a576 feat: allow deprecating options (#13195)
This PR adds support for marking options as deprecated. When a
deprecated option is used via `set_option`, a warning is emitted
(controlled by `linter.deprecated.options`).

An `OptionDeprecation` structure with a required `since` field and an
optional `text?` field is added to `OptionDecl`. Each `set_option`
elaborator (command, term, tactic, grind) calls `checkDeprecatedOption`
to emit warnings. The C++ `register_option` is updated to account for
the new field.

As a first use, `backward.eqns.nonrecursive` and
`backward.eqns.deepRecursiveSplit` are marked deprecated. Continues
earlier work done in #11096.
2026-04-02 14:44:11 +00:00
Garmelon
2bcbb676f5 chore: disable flaky tests (#13253)
Discovered while doing the v4.30.0-rc release.

- `async_select_channel.lean`: @hargoniX @algebraic-dev 
- `sync_mutex.lean`: @hargoniX @datokrat
2026-04-02 12:59:59 +00:00
Joachim Breitner
f7ec39d6a1 test: add empty-by completion tests and column-0 test marker (#13257)
This PR adds test infrastructure and tests for tactic completion in
empty `by` blocks.

**Test runner improvements (`src/Lean/Server/Test/Runner.lean`):**
- Add `--⬑` marker variant that targets the column of `--` itself,
enabling column 0 tests (which `--^` cannot reach since `^` is always at
column 2+).

**New test file (`tests/server_interactive/completionEmptyBy.lean`):**
- Tests tactic completion in empty `by` blocks for both top-level `by`
and nested `by` (inside `id <| have := by`).
- Tests at various column positions on the line below `by`: indented
past `by`, at column 2, and at column 0.
- Tests on the `by` token itself (no completions expected).
- All positions below `by` currently offer tactic completions.


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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 12:40:38 +00:00
Sebastian Graf
aaf0f6e7f5 feat: add letConfig support to do block let/have (#13255)
This PR adds support for let configuration options (`(eq := h)`,
`+nondep`, `+usedOnly`, `+zeta`) in `do` block `let` and `have`
declarations, matching the behavior available in term-level
`let`/`have`. Configuration options are rejected with `let mut` since
they are incompatible with mutable bindings. `+postponeValue` and
`+generalize` are also rejected in `do` blocks.

Follow-up to #13250 which added the parser support. Now that stage0 is
updated, this PR replaces the backward-compat index helpers with proper
quotation patterns and implements the actual `letConfig` elaboration.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 12:36:20 +00:00
Sebastian Ullrich
5bf590e710 chore: fixes from #13103 "enable separate codegen" (#13241) 2026-04-02 11:13:22 +00:00
Lean stage0 autoupdater
159f069863 chore: update stage0 2026-04-02 10:20:07 +00:00
Sebastian Graf
aa1144602b feat: add letConfig syntax to do block let/have parsers (#13250)
This PR extends the `doLet`, `doLetElse`, `doLetArrow`, and `doHave`
parsers to accept `letConfig` (e.g. `(eq := h)`, `+nondep`, `+usedOnly`,
`+zeta`), matching the syntax of term-level `let`/`have`. The
elaborators are adjusted to handle the shifted syntax indices but do not
yet process the configuration; that will be done in a follow-up PR after
stage0 is updated, allowing the use of proper quotation patterns.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 09:40:44 +00:00
Sebastian Graf
ffc2c0ab1a chore: remove hardcoded maxSteps limit in Sym mvcgen' (#13252)
This PR removes a FIXME in Sym-based mvcgen' concerning a hardcoded step
limit for grind simplification. I tested that this is no longer
necessary even at highest setting for the GetThrowSetGrind benchmark.
2026-04-02 09:16:43 +00:00
Sebastian Ullrich
8dc4c16fce fix: correct String cases codegen to use String.toByteArray (#13242)
This PR fixes the compiler handling of pattern matching on the `String`
constructor to conform to the new `String` representation.
2026-04-02 08:17:20 +00:00
Kyle Miller
861bc19e0c feat: allow dotted function notation to use @ and explicit universes (#13245)
This PR extends Lean syntax for dotted function notation (`.f`) to add
support for explicit mode (`@.f`), explicit universes (`.f.{u,v}`), and
both simultaneously (`@.f.{u,v}`). This also includes a fix for a bug
involving overloaded functions, where it used to give erroneous
deprecation warnings about declarations that the function did not
elaborate to.

Closes #10984
2026-04-02 03:12:54 +00:00
Kyle Miller
8f1c18d9f4 feat: pretty print level metavariables using index (#13030)
This PR improves pretty printing of level metavariables: they now print
with a per-definition index rather than their per-module internal
identifiers. Furthermore, `+` is printed uniformly in level expressions
with surrounding spaces. **Breaking metaprogramming change:** level
pretty printing should use `delabLevel` or `MessageData.ofLevel`;
functions such as `format` or `toString` do not have access to the
indices, since they are stored in the current metacontext. Absent index
information, metavariables print with the raw internal identifier as
`?_mvar.nnn`. **Note:** The heartbeat counter also increases quicker due
to counting allocations that record level metavariable indices. In some
tests we needed to increase `maxHeartbeats` by 20–50% to compensate,
without a corresponding slowdown.
2026-04-01 22:34:29 +00:00
Henrik Böving
097f3ebdbc perf: use memcmp for ByteArray equality (#13235)
This PR uses `std::memcmp` for `ByteArray` `BEq` and `DecidableEq`.

Implementation is done in the same way as `String` but adapted to scalar
arrays.
2026-04-01 15:30:03 +00:00
Joachim Breitner
861f722844 fix: handle multi-discriminant casesOn in WF unfold equation generation (#13232)
This PR fixes a panic when compiling mutually recursive definitions that
use `casesOn` on indexed inductive types (e.g. `Vect`). The
`splitMatchOrCasesOn` function in `WF.Unfold` asserted
`matcherInfo.numDiscrs = 1`, but for indexed types the casesOn recursor
has multiple discriminants (indices + major premise). The fix uses the
last discriminant (the major premise) and lets the `cases` tactic handle
index discriminants automatically.

Closes #13015

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 15:23:13 +00:00
Wojciech Różowski
eac9315962 feat: add deprecated_module (#13002)
This PR adds a `deprecated_module` command that marks the current module
as deprecated. When another module imports a deprecated module, a
warning is emitted during elaboration suggesting replacement imports.

Example usage:
```lean
deprecated_module "use NewModule instead" (since := "2026-03-30")
```

The warning message is optional but recommended. The `since` parameter
is required. Warnings can be disabled, by setting
`linter.deprecated.module` option to false in the command line. Because
the check happens when importing , using `set_option
linter.deprecated.module` in the source file won't affect the warnings.
Instead, a whole file can be marked not to display depreciation
warnings, by putting a comment `deprecated_module: ignore` next to
`module` keyword. Similarly, individual keywords can be silenced.

A `#show_deprecated_modules` command is also provided for inspecting
which modules in the current environment are deprecated.
`linter.deprecated.module` has no effect on this command, and hence one
can view deprecated modules, even when having warnings silenced.
2026-04-01 14:40:43 +00:00
Robin Arnez
8b52f4e8f7 fix: make FirstTokens.seq (.optTokens _) .unknown return .unkown (#13205)
This PR fixes `FirstTokens.seq (.optTokens s) .unknown` to return
`.unknown`. This occurs e.g. when an optional (with first tokens
`.optTokens s`) is followed by a parser category (with first tokens
`.unknown`). Previously `FirstTokens.seq` returned `.optTokens s`,
ignoring the fact that the optional may be empty and then the parser
category may have any first token. The correct behavior here is to
return `.unknown`, which indicates that the first token may be anything.

Closes #13203
2026-04-01 13:21:26 +00:00
Joachim Breitner
402a6096b9 fix: add checkSystem calls to long-running elaboration paths (#13220)
This PR adds `checkSystem` calls to several code paths that can run for
extended periods without checking for cancellation, heartbeat limits, or
stack overflow. This improves responsiveness of the cancellation
mechanism
in the language server.

Affected paths:
- `simpLoop` step loop (`Simp/Main.lean`)
- `simp` rewrite candidate loops (`Rewrite.lean`)
- `simpAppUsingCongr` argument traversal (`Types.lean`)
- `synthesizeSyntheticMVarsStep` mvar loop (`SyntheticMVars.lean`)
- `abstractNestedProofs` visitor (`AbstractNestedProofs.lean`)
- `transform`/`transformWithCache` visitors (`Transform.lean`)
- LCNF compiler pass runner loop (`LCNF/Main.lean`)
- LCNF checker recursive traversal (`LCNF/Check.lean`)
- `whnfImp` top-level reduction (`WHNF.lean`)

Intentionally *not* instrumented (too hot, measurable regression):
- `whnfCore.go` inner recursion
- `simpImpl` entry point (redundant with `simpLoop`)
- LCNF `simp` inner recursion (0.4% regression on `big_do`)

Also adds a docstring to `checkInterrupted` clarifying its relationship
to
`checkSystem`.

Found using `LEAN_CHECK_SYSTEM_INTERVAL_MS` monitoring from #13218.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 12:54:57 +00:00
Lean stage0 autoupdater
978bde4a0f chore: update stage0 2026-04-01 12:56:25 +00:00
Garmelon
8aa0c21bf8 chore: begin development cycle for v4.31.0 (#13230) 2026-04-01 12:23:00 +00:00
Sebastian Ullrich
1aa860af33 fix: avoid heartbeat timeout in symbolFrequencyExt export (#13202)
This PR fixes a heartbeat timeout from an environment extension at the
end of the file that cannot be avoided by raising the limit.

Fixes #12989
2026-04-01 10:54:13 +00:00
Wojciech Różowski
cdd982a030 feat: add deprecated_syntax (#13108)
This PR adds a `deprecated_syntax` command that marks syntax kinds as
deprecated. When deprecated syntax is elaborated (in terms, tactics, or
commands), a linter warning is emitted. The warning is also emitted
during quotation precheck when a macro definition uses deprecated syntax
in its expansion.

The `deprecated_syntax` command takes a syntax node kind, an optional
message, and a `(since := "...")` clause. Deprecation warnings correctly
attribute the warning to macro call sites when the deprecated syntax is
produced by macro expansion, including through nested macro chains.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-04-01 10:51:59 +00:00
Sebastian Ullrich
f11d137a30 feat: add unlock_limits command to disable all resource limits (#13211)
This PR adds an `unlock_limits` command that sets `maxHeartbeats`,
`maxRecDepth`, and `synthInstance.maxHeartbeats` to 0, disabling all
core resource limits. Also makes `maxRecDepth 0` mean "no limit"
(matching the existing behavior of `maxHeartbeats 0`).
2026-04-01 09:26:13 +00:00
Kim Morrison
fc0cf68539 fix: make -DLEAN_VERSION_* CMake overrides actually work (#13226)
This PR updates `release_checklist.py` to handle the `CACHE STRING ""`
suffix on CMake version variables. The `CACHE STRING` format was
introduced in the `releases/v4.30.0` branch, but the script's parsing
wasn't updated to match, causing false failures.

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 05:31:23 +00:00
Kim Morrison
46a0a0eb59 chore: add safety notes to release command (#13225)
This PR adds two safety notes to the Claude Code release command:
- Mathlib bump branches live on `mathlib4-nightly-testing`, not the main
`mathlib4` repo
- Never force-update remote refs without explicit user confirmation

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 05:01:00 +00:00
Joachim Breitner
916004bd3c fix: add checkSystem calls to hasAssignableMVar (#13219)
This PR moves `hasAssignableMVar`, `hasAssignableLevelMVar`, and
`isLevelMVarAssignable` from `MetavarContext.lean` to a new
`Lean.Meta.HasAssignableMVar` module, changing them from generic `[Monad
m] [MonadMCtx m]` functions to `MetaM` functions. This enables adding
`checkSystem` calls in the recursive traversal, which ensures
cancellation and heartbeat checks happen during what can be a very
expensive computation.

All callers of these functions were already in `MetaM`, so this change
is safe. The motivating case is the `4595_slowdown.lean` test, where
`hasAssignableMVar` (with `PersistentHashMap.find?` lookups on
`mctx.lDepth`) was the dominant CPU cost during elaboration of category
theory definitions. Without `checkSystem` calls, cancellation requests
could be delayed by over 2 seconds.

The test file `4595_slowdown.lean` gets a slightly increased
`maxHeartbeats` limit because `checkSystem` now detects heartbeat
exhaustion mid-traversal rather than after the function returns.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 19:27:15 +00:00
Nadja Yang
a145b9c11a chore: use FetchContent for mimalloc source acquisition (#13196)
FetchContent provides configure-time source acquisition with
`FETCHCONTENT_SOURCE_DIR_MIMALLOC` for sandboxed builds, replacing the
empty-command ExternalProject pattern.

Closes https://github.com/leanprover/lean4/issues/13176

---------

Co-authored-by: Joscha <joscha@plugh.de>
2026-03-31 18:00:39 +00:00
Garmelon
67b6e815b9 chore: strip binaries only in release builds (#13208)
This commit ensures binaries are only stripped in the `release` build
preset, not in any of the other presets.

Since `release` is used for development, the commit adds a non-stripping
copy called `dev` that can be used via `cmake --preset dev`.
2026-03-31 17:18:43 +00:00
Kim Morrison
33c3604b87 fix: use correct shared library directory for Lake plugin on Windows (#13128)
This PR fixes the Windows dev build by using
`CMAKE_RELATIVE_LIBRARY_OUTPUT_DIRECTORY` instead of the hardcoded
`lib/lean` path for the Lake plugin. On Windows, DLLs must be placed
next to executables in `bin/`, but the plugin path was hardcoded to
`lib/lean`, causing stage0 DLLs to not be found.

The `CMAKE_RELATIVE_LIBRARY_OUTPUT_DIRECTORY` variable was introduced
precisely for this purpose — it resolves to `bin` on Windows and
`lib/lean` elsewhere.

Closes #13126

🤖 Prepared with Claude Code

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-03-31 16:33:58 +00:00
Sebastian Graf
504e099c5d test: lazy let-binding unfolding in sym mvcgen (#13210)
This PR replaces eager let-expression zeta-reduction in the sym-based
mvcgen with on-demand unfolding that mirrors the production mvcgen's
behavior.

Previously, all let-expressions in the program head were immediately
zeta-reduced. Now, let-expressions are hoisted to the top of the goal
target, and the value is only inlined if it is duplicable (literals,
fvars, consts, `OfNat.ofNat`). Complex values are introduced into the
local context via `introsSimp`, preserving SymM's maximal sharing
invariants, and unfolded on demand when the fvar later appears as the
program head.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 15:29:11 +00:00
Sofia Rodrigues
17795b02ee fix: missing borrow annotations in Std.Internal.UV.System (#13172)
This PR adds borrow annotations in `Std.Internal.UV.System`.
2026-03-31 15:10:23 +00:00
Julia Markus Himmel
48800e438c fix: assorted fixes in the string library (#13201)
This PR fixes several issues in `Init.Data.String`, most of them typos.

We also move the remaining material out of `Init.Data.String.Lemmas` to
`Init.Data.String.Lemmas.StringOrder`, which shortens the pole.
2026-03-31 06:28:20 +00:00
Wojciech Różowski
f395593ffc feat: missingDocs linter warns about empty doc strings (#13188)
This PR extends the `missingDocs` linter to detect and warn about empty
doc strings (e.g. `/---/` or `/-- -/`), in addition to missing doc
strings. Previously, an empty doc comment would silence the linter even
though it provides no documentation value. Now empty doc strings produce
a distinct "empty doc string for ..." warning, while `@[inherit_doc]`
still suppresses warnings as before.
2026-03-30 19:48:25 +00:00
Sebastian Graf
a88f81bc28 test: use DFS ordering for subgoals in mvcgen (#13193)
This PR switches the mvcgen worklist from BFS (queue) to DFS (stack)
ordering for subgoal processing.

With the new do elaborator, `if`-without-`else` generates asymmetric
bind depth between branches (`pure () >>= cont` is optimized to just
`cont` in the else branch). This caused BFS-based VC numbering to depend
on elaborator internals, swapping vc10/vc11 in test cases. DFS ordering
follows the syntactic program structure more naturally and is robust to
such bind-depth asymmetries.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 17:11:13 +00:00
Sebastian Graf
313abdb49f fix: if _ : p ... syntax in new do elaborator (#13192)
This PR fixes the handling of anonymous dependent `if` (`if _ : cond
then ... else ...`) inside `do` blocks when using the new do elaborator.

The `_%$tk` binder pattern was incorrectly quoted as `$(⟨tk⟩):hole` in
the generated `dite` syntax, causing "elaboration function for
`termDepIfThenElse` has not been implemented" errors. The fix quotes it
correctly as `_%$tk`.

A test case is added to verify both anonymous (`if _ : ...`) and named
(`if h : ...`) dependent `if` work in do blocks.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 16:58:48 +00:00
Sebastian Ullrich
f08983bf01 chore: support jj workspaces in cmake git hash detection (#13190)
The stage0 tree hash used by Lake to invalidate stage 1 oleans was
computed via `git ls-tree HEAD`, which fails in jj workspaces (no .git
directory). Fall back to discovering the backing git repo via `jj git
root` and resolving the current workspace's commit via `jj log -r @`
(since git's HEAD points to the root jj workspace, not the current one).
2026-03-30 16:32:07 +00:00
Sebastian Ullrich
22308dbaaa chore: CLAUDE.md individual module build instructions (#13189) 2026-03-30 14:30:16 +00:00
Wojciech Różowski
51e87865c5 feat: add deprecated_arg attribute (#13011)
This PR adds a `@[deprecated_arg]` attribute that marks individual
function parameters as deprecated. When a caller uses the old parameter
name, the elaborator emits a deprecation warning with a code action hint
to rename or delete the argument, and silently forwards the value to the
correct binder.

Supported forms:
- `@[deprecated_arg old new (since := "...")]` — renamed parameter,
warns and forwards
- `@[deprecated_arg old new "reason" (since := "...")]` — with custom
message
- `@[deprecated_arg removed (since := "...")]` — removed parameter,
errors with delete hint
- `@[deprecated_arg removed "reason" (since := "...")]` — removed with
custom message

A warning is emitted if `(since := "...")` is omitted.

When a parameter is deprecated without a replacement, the elaborator
treats it as no longer present: using it as a named argument produces an
error. Note that positional uses of deprecated arguments are not checked
— if a function's arity changed, the caller will simply get a "function
expected" error.

The `linter.deprecated.arg` option (default `true`) controls behavior:
when enabled, renamed args produce warnings and removed args produce
specific deprecation errors with code action hints; when disabled, both
fall through to the standard "invalid argument name" error. This lets
library authors phase out old parameter names without breaking
downstream code immediately.

Example (renamed parameter):
```lean
@[deprecated_arg old new (since := "2026-03-18")]
def f (new : Nat) : Nat := new

/--
warning: parameter `old` of `f` has been deprecated, use `new` instead

Hint: Rename this argument:
  o̵l̵d̵n̲e̲w̲
---
info: f 42 : Nat
-/
#guard_msgs in
#check f (old := 42)
```

Example (removed parameter):
```lean
@[deprecated_arg removed (since := "2026-03-18")]
def g (x : Nat) : Nat := x

/--
error: parameter `removed` of `g` has been deprecated

Hint: Delete this argument:
  (̵r̵e̵m̵o̵v̵e̵d̵ ̵:̵=̵ ̵4̵2̵)̵
-/
#guard_msgs in
#check g (removed := 42)
```
2026-03-30 10:20:44 +00:00
Sebastian Graf
75ec8e42c8 test: simplify assumptions in mvcgen' with grind benchmark (#13186)
This PR adds `simplifying_assumptions [Nat.add_assoc]` to the
`vcgen_get_throw_set_grind` benchmark, recovering hypothesis
simplification lost in a54eafb ("refactor: decouple solve from grind").
That refactor introduced `PreTac.processHypotheses` which wraps
`simpNewHyps`, but the call sites in `work` and `main` call
`Grind.processHypotheses` directly, leaving `simpNewHyps` as dead code.
Without it, long `s + 1 + … + 1` chains are never collapsed, causing an
asymptotic slowdown visible by a factor of 2 at n=150 (largest radar
input size).

Benchmark results (VCGen time in ms):

| n | Before | After | Speedup |
|---|--------|-------|---------|
| 50 | 222 | 186 | 1.2× |
| 100 | 391 | 251 | 1.6× |
| 150 | 647 | 329 | 2.0× |
| 200 | 995 | 415 | 2.4× |
| 300 | 1894 | 589 | 3.2× |

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 10:03:43 +00:00
Sebastian Ullrich
9fc62b7042 chore: clean up old test artifacts (#13179) 2026-03-30 08:02:52 +00:00
Yang Song
583c223b16 style: correct typos in comments and documentation (#13181)
This PR fixes 15 spelling errors across 8 files in source comments,
docstrings,
and Lake CLI help text. No functional code changes.

Typos corrected: `auxillary` → `auxiliary`, `occurence`/`occuring` →
`occurrence`/`occurring`, `paramaters` → `parameters`, `precendence` →
`precedence`, `similiar` → `similar`, `contianing` → `containing`,
`specifed` → `specified`, `sythesized` → `synthesized`, `enviroment` →
`environment`. Also fixes grammar in `lake cache put` help text
(`used via be specified` → `used can be specified`, `Lake will used` →
`Lake will use`).

Files changed:
- `src/Lean/Elab/Coinductive.lean` — auxillary, occurence (×2),
paramaters
- `src/Lean/Server/FileWorker/SemanticHighlighting.lean` — precendence
(×2)
- `src/Lean/Compiler/LCNF/CoalesceRC.lean` — similiar
- `src/Lean/Compiler/LCNF/ExpandResetReuse.lean` — occuring, contianing
- `src/Lean/LibrarySuggestions/Basic.lean` — occuring (×2)
- `src/Lean/Meta/Tactic/Simp/Rewrite.lean` — sythesized
- `src/lake/Lake/CLI/Help.lean` — specifed (×2), grammar fixes
- `src/lake/Lake/CLI/Main.lean` — enviroment

Closes #13182
2026-03-30 07:00:18 +00:00
Sebastian Ullrich
ccc7157c08 fix: expose grind gadgets abstractFn and simpMatchDiscrsOnly (#13177)
This PR adds `@[expose]` to `Lean.Grind.abstractFn` and
`Lean.Grind.simpMatchDiscrsOnly` so that the kernel can unfold them when
type-checking `grind`-produced proofs inside `module` blocks. Other
similar gadgets (`nestedDecidable`, `PreMatchCond`, `alreadyNorm`) were
already exposed; these two were simply missed.

Closes https://github.com/leanprover/lean4/issues/13167
2026-03-29 10:37:54 +00:00
Lean stage0 autoupdater
05046dc3d7 chore: update stage0 2026-03-29 01:00:22 +00:00
Mac Malone
43f18fd502 fix: lake: large cache get bulk fetch (#13173)
This PR fixes a bug in #13164 where the bulk request would hang if the
response was large.
2026-03-28 22:58:56 +00:00
Sofia Rodrigues
b06eb981a3 fix: remove non-deterministic http-body test (#13175)
This PR fixes the wrong behavior of a stream in http_body.
2026-03-28 20:36:35 +00:00
Sofia Rodrigues
f72137f53a feat: introduce Body type class and some Body types for HTTP (#12144)
This PR introduces the `Body` type class, the `ChunkStream` and `Full`
types that are used to represent streaming bodies of Requests and
Responses.

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-28 17:14:53 +00:00
Lean stage0 autoupdater
96dbc324f3 chore: update stage0 2026-03-28 05:24:36 +00:00
Mac Malone
d6e69649b6 refactor: lake: fetch artifact URLs in a single Reservoir request (#13164)
This PR changes `lake cache get` to fetch artifact cloud storage URLs
from Reservoir in a single bulk POST request rather than relying on
per-artifact HTTP redirects. When downloading many artifacts, the
redirect-based approach sends one request per artifact to the Reservoir
web host (Netlify), which can be slow and risks hitting rate limits. The
bulk endpoint returns all URLs at once, so curl only talks to the CDN
after that.

Non-Reservoir cache services are unaffected and continue using direct
URLs as before.

🤖 Prepared with Claude Code
2026-03-28 04:46:43 +00:00
Lean stage0 autoupdater
337f1c455b chore: update stage0 2026-03-28 03:21:53 +00:00
Leonardo de Moura
6871abaa44 refactor: replace grind canonicalizer with type-directed normalizer (#13166)
This PR replaces the `grind` canonicalizer with a new type-directed
normalizer (`Sym.canon`) that goes inside binders and applies targeted
reductions in type positions, eliminating the O(n^2) `isDefEq`-based
approach.

The old canonicalizer maintained a map from `(function,
argument_position)` to previously seen arguments, iterating the list and
calling `isDefEq` for each new argument. This produced performance
problems in some goal. For example, for a goal containing `n` numeric
literals, it would produce O(n^2) `isDefEq` comparisons.

The new canonicalizer normalizes types directly:
- **Instances**: re-synthesized via `synthInstance` with the type
normalized first, so `OfNat (Fin (2+1)) 0` and `OfNat (Fin 3) 0` produce
the same instance.
- **Types**: normalized with targeted reductions — eta, projection,
match/ite/cond, and Nat arithmetic (`n.succ + 1` → `n + 2`, `2 + 1` →
`3`).
- **Values**: traversed but not reduced, preserving lambdas for grind's
beta module.

The canonicalizer enters binders (the old one did not), using separate
caches for type-level and value-level contexts. Propositions are not
normalized to avoid interfering with grind's proposition handling.

Move `SynthInstance` from `Grind` to `Sym` since the canonicalizer now
lives in `Sym` and needs instance synthesis. The `Grind` namespace
re-exports the key functions.

Add `no_index` annotations to `val_addNat` and `val_castAdd` patterns in
`Fin/Lemmas.lean` — arithmetic in type positions is now normalized, so
patterns must not rely on the un-normalized form for e-matching
indexing.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 02:43:22 +00:00
Henrik Böving
8c0bb68ee5 perf: prevent unnecessary allocations of EST.Out.ok (#13163) 2026-03-27 22:10:24 +00:00
Lean stage0 autoupdater
ae19b3e248 chore: update stage0 2026-03-27 21:21:38 +00:00
Mac Malone
d0d135dbe2 refactor: lake: log process output as info on errors (#13151)
This PR changes `Lake.proc` to always log process output as `info` if
the process exits with a nonzero return code. This way it behaves the
same as `captureProc` on errors.
2026-03-27 16:49:14 +00:00
Lean stage0 autoupdater
088b299343 chore: update stage0 2026-03-27 16:47:06 +00:00
Sebastian Graf
82c35eb517 refactor: rename goalDotAlt/goalCaseAlt to invariantDotAlt/invariantCaseAlt (#13160)
This PR renames `goalDotAlt` to `invariantDotAlt` and `goalCaseAlt` to
`invariantCaseAlt` to better reflect that these syntax nodes are
specific
to invariant alternatives in `mvcgen`, not general goal alternatives.

Part 2 of #13137, which made `elabInvariants` resilient to this rename
by using positional dispatch instead of quotation pattern matching.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 16:01:32 +00:00
Henrik Böving
abcf400e90 perf: tagged values can be interpreted as borrowed (#13152)
This PR informs the RC optimizer that tagged values can also be
considered as "borrowed" in the sense that we do not need to consider
them as owned values for the borrow analysis (they do of course not have
an allocation they actually borrow from).

Implementation note: For the derived borrows analysis we instead just
disregard parents that are tagged. Note that we cannot match on types in
passes before boxing as the IR might still be type incorrect at that
point.
2026-03-27 15:55:57 +00:00
Sebastian Graf
42854412c3 refactor: use simpTelescope in mvcgen' simplifying_assumptions (#13159)
This PR replaces the manual `simpForallDomains` / `implies_congr`
machinery in `introsSimp` with `Sym.Simp.simpTelescope` as the
pre-combinator, which already handles simplifying forall telescope
domains.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 15:48:35 +00:00
Lean stage0 autoupdater
c84aa086c7 chore: update stage0 2026-03-27 15:17:24 +00:00
Sebastian Graf
7168289c57 refactor: make elabInvariants resilient to goalDotAlt/goalCaseAlt renames (#13137)
This PR replaces quotation pattern matches on `goalDotAlt`/`goalCaseAlt`
in `elabInvariants` with positional/structural dispatch based on
`getNumArgs`. This is part 1 of renaming `goalDotAlt` to
`invariantDotAlt` and `goalCaseAlt` to `invariantCaseAlt`; the
elaborator change lands first so that the subsequent rename does not
require a stage0 update.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 14:30:19 +00:00
Sebastian Ullrich
febd1caf36 doc: fix inferInstanceAs docstring (#13158) 2026-03-27 14:03:38 +00:00
Lean stage0 autoupdater
79ac2d93b0 chore: update stage0 2026-03-27 14:06:36 +00:00
Sebastian Graf
210d4d00fa test: add simplifying_assumptions clause to mvcgen' tactic (#13156)
This PR adds a `simplifying_assumptions` clause to the `mvcgen'` tactic
that allows users to specify Sym.simp rewrite theorems for simplifying
hypotheses during VC generation. The syntax is `mvcgen'
simplifying_assumptions [thm₁, thm₂, ...]`. This replaces the previous
approach of hardcoding `reassocNatAdd` in `mvcgen' with grind` mode,
making hypothesis simplification user-extensible and independent of
grind.

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

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:16:23 +00:00
Sebastian Graf
938c19aace chore: rename mvcgen_invariant_type attribute to spec_invariant_type, part 2 (#13157)
This PR switches all usages from `@[mvcgen_invariant_type]` to
`@[spec_invariant_type]` and removes the old attribute registration.
Concludes the work of #13153.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:06:17 +00:00
Lean stage0 autoupdater
e06fc0b5e8 chore: update stage0 2026-03-27 12:26:23 +00:00
Sebastian Graf
f2d36227cf chore: rename mvcgen_invariant_type attribute to spec_invariant_type, part 1 (#13153)
This PR registers the new `spec_invariant_type` attribute alongside the
old
`mvcgen_invariant_type`, renames internal identifiers, and replaces the
hardcoded `Invariant` check in `Spec.lean` with `isSpecInvariantType`.

A follow-up PR will switch all usages to `spec_invariant_type` and
remove
the old attribute after stage0 is updated.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 11:29:26 +00:00
Sebastian Ullrich
0b401cd17c refactor: compile @[extern] via standard pipeline (#13102) 2026-03-27 10:31:22 +00:00
Sebastian Ullrich
fda4793215 test: copy ver_clash test data to temp dir before modifying (#13134)
Avoids git/(especially) jj thinking the files have vanished from the
root repo
2026-03-27 10:25:58 +00:00
Lean stage0 autoupdater
215aa4010b chore: update stage0 2026-03-27 11:03:27 +00:00
Joachim Breitner
142ca24192 feat: support #print axioms under the module system (#13117)
This PR re-enables `#print axioms` under the module system by computing
axiom dependencies at olean serialization time. It reverts #8174 and
replaces it with a proper fix.

Depends on #13142, which refactors `exportEntriesFnEx` to return all
three olean levels at once via a new `OLeanEntries` structure, allowing
extensions to share expensive computation.

The axiom extension uses `exportEntriesFnEx` to walk bodies of all
public declarations in the current module, collecting axiom dependencies
in a single batch with a shared cache across declarations. The results
are stored sorted for binary search and exported uniformly to all olean
levels. Downstream modules look up pre-computed axiom data from imported
oleans, so axiom collection never crosses module boundaries. During
elaboration of the current module, `collectAxioms` walks bodies directly
since they are always available locally.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 10:15:49 +00:00
Henrik Böving
c71a0ea9a5 perf: coalescing of RC ops within one basic block (#13136)
This PR introduces coalescing of RC operations to the RC optimizer.
Whenever we perform multiple `inc`s for a single value within one basic
block it is legal to instead perform all of these `inc`s at once at the
first `inc` side. This is the case because the value will stay alive
until at least the last `inc` and was thus never observable with `RC=1`.
Hence, this change of `inc` location never destroys reuse opportunities.
2026-03-27 10:13:04 +00:00
Joachim Breitner
439c3c5544 refactor: make exportEntriesFnEx return all olean levels at once (#13142)
This PR replaces the per-level `OLeanLevel → Array α` return type of
`exportEntriesFnEx` with a new `OLeanEntries (Array α)` structure that
bundles exported, server, and private entries together. This allows
extensions to share expensive computation across all three olean levels
instead of being called three separate times.

A new `computeExtEntries` function in `Environment.lean` calls each
extension's export function once and distributes results across levels.
`mkModuleData` accepts optional pre-computed entries, and `writeModule`
uses `computeExtEntries` to compute once for all three olean parts.

Extensions that previously relied on `env.setExporting` being
pre-applied by `mkModuleData` now call `env.setExporting true`
internally for their exported/server-level filtering, since the export
function is called once rather than per-level.

Extracted from #13117 to be reviewed independently.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 08:57:45 +00:00
Mac Malone
2bc7a77806 fix: lake: lake cache help put-staged (#13150)
This PR fixes a typo in #13144 where `lake cache help put-staged` was
incorrectly `lake cache help putStaged`.
2026-03-27 05:11:43 +00:00
Leonardo de Moura
e55f69acd0 refactor: simplify grind canonicalizer and fix preprocessing issues (#13149)
This PR simplifies the `grind` canonicalizer by removing dead state and
unnecessary
complexity, and fixes two bugs discovered during the cleanup.

## Changes

**Canonicalizer cleanup:**
- Remove dead `Canon.State.canon` field — values were inserted but never
read.
The canonicalizer uses a transient `HashMap` local to each `canonImpl`
invocation.
- Remove `proofCanon` — it deduplicated `Grind.nestedProof` terms by
mapping
canonicalized propositions to a single representative, but different
proofs may
reference different hypotheses, making the result context-dependent and
preventing
  cache sharing across goals.
- Remove `isDefEqBounded` — a fallback that retried `isDefEq` at default
transparency
with a heartbeat budget. The one test that depended on it was actually
masking a
  transparency bug in `propagateCtorHomo`.

**Bug fixes:**
- Use `withDefault` for `mkAppOptM` in `propagateCtorHomo` (`Ctor.lean`)
— the
injectivity proof construction needs default transparency to unify
implicit
  arguments of indexed inductive types like `Vector`.
- Add `Grind.abstractFn` gadget to protect lambda abstractions created
by
`abstractGroundMismatches?` from beta reduction during preprocessing.
Without
this, `Core.betaReduce` in `preprocessLight` collapses `(fun x => body)
arg`
back to `body[arg/x]`, undoing the abstraction that congruence closure
needs.

**Eta reduction infrastructure:**
- Lower `etaReduceAll` from `MetaM` to `CoreM` — it only performs
structural
  operations, no `MetaM` needed.
- Add `etaReduceWithCache` that takes and returns an explicit `HashMap`
cache,
  enabling callers to thread a single cache across multiple expressions.

The net effect on `Canon.State` is removing 3 fields (`canon`,
`proofCanon`)
and the `isDefEqBounded` function, along with the `useIsDefEqBounded`
and
`parent` parameters from `canonElemCore`.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 05:00:01 +00:00
Mac Malone
50785098d8 feat: lake: cache staging (#13144)
This PR adds three new `lake cache` subcommands for staged cache
uploads: `stage`, `unstage`, and `put-staged`. These are designed to
function as parallels for the commands of the same name in Mathlib's
`lake exe cache`.

- `lake cache stage`: Copies the build outputs of a mappings file from
the Lake cache to a staging directory.
- `lake cache unstage`: Copies the build outputs from a staging
directory back into the Lake cache.
- `lake cache put-staged`: Uploads build outputs from a staging
directory to a remote cache service. Unlike `lake cache put`, this
command does not load the workspace configuration. As a result, platform
and toolchain settings must be supplied manually via `--platform` and
`--toolchain` if needed.

This PR also removes deprecation warnings when using environment
variables to configure the cache service for `lake cache put` (and `lake
cache put-staged`).

🤖 Prepared with Claude Code

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 03:16:09 +00:00
Henrik Böving
fee2d7a6e8 fix: potential Array.get!Internal leaks part 2 (#13148)
Part 2 for #13147, adding the necessary constant semantics to the
interpreter.
2026-03-27 02:51:39 +00:00
Lean stage0 autoupdater
bc5210d52a chore: update stage0 2026-03-27 01:15:51 +00:00
Lean stage0 autoupdater
12c547122f chore: update stage0 2026-03-27 01:04:33 +00:00
Henrik Böving
f9c8b5e93d fix: potential Array.get!Internal leaks part 1 (#13147)
This PR fixes theoretical leaks in the handling of `Array.get!Internal`
in the code generator.
Currently, the code generator assumes that the value returned by
`get!Internal` is derived from the
`Array` argument. However, this does not generally hold up as we might
also return the `Inhabited`
value in case of an out of bounds access (recall that we continue
execution after panics by
default). This means that we sometimes convert an `Array.get!Internal`
to
`Array.get!InternalBorrowed` when we are not allowed to do so because in
the panic case the
`Inhabited` instance can be returned and if it is an owned value it is
going to leak.

The fix consists of adapting several components to this change:
1. `PropagateBorrow` will only mark the derived value as forcibly
borrowed if both the `Inhabited`
   and `Array` argument are forcibly borrowed.
2. `InferBorrow` will do the same for its data flow analysis
3. The derived value analysis of `ExplicitRC` is extended from a derived
value tree to a derived
value graph where a value may have more than one parent. We only
consider a value borrowed if all
of its parents are still accessible. Then `get!Internal` is equipped
with both its `Inhabited`
   and its `Array` parent.

These changes are sufficient for correctness on their own. However, they
are going to break
`get!Internal` to `get!InternalBorrowed` conversion in most places. This
happens because almost all
`Inhabited` instances are going to be constants. Currently reads from
constants yield semantically
owned values and thus block the `get!InternalBorrowed` conversion. We
would thus prefer for these
constants to be treated as borrows instead.

The owned return is implemented in two ways at the moment:
1. In the C code emitter we do not need to do anything as constants are
marked persistent to begin
   with
2. In the interpreter whenever a constant is pulled from the constant
cache it is `inc`-ed and then
later `dec`-ed somewhere (potentially using a `dec[persistent]` which is
a no-op in C)

This PR changes the semantics of constant reads to instead be borrows
from the constant (they can be
cutely interpreted as "being borrowed from the world"). This enables
many `get!Internal` to have
both their arguments be marked as borrowed and thus still converted to
`get!InternalBorrowed`. Note
that this PR does not yet change the semantics of the interpreter to
account for this
(it will be done in a part 2) and thus introduces (very minor) leaks
temporarily.

Furthermore, we observed code with signatures such as the following:
```lean
@[specialize]
def foo {a : Type} [inst : Inhabited a] (xs : Array a) (f : a -> a -> Bool) ... :=
  ...
  let x := Array.get!Internal inst xs i
  ...
```
being instantiated with `a := UInt32`. This poses a challenge because
`Inhabited` is currently
marked as `nospecialize`, meaning that we are sometimes going to end up
with code such as:
```
def foo._spec (inst : UInt32) (xs : @&Array UInt32) ... :=
  ...
  let inst := box inst
  let x := Array.get!Internal inst xs i
  dec inst
  ...
```
Here `xs` itself was inferred as borrowed, however, the `UInt32`
`Inhabited` instance was not
specialized for (as `Inhabited` is marked `nospecialize`) and thus needs
to be boxed. This causes
the `inst` parameter to `get!Internal` to be owned and thus
`get!InternalBorrowed` conversion fails.
This PR marks `Inhabited` as `weak_specialize` which will make it get
specialized for in this case,
yielding code such as:

```
def foo._spec (xs : @&Array UInt32) ... :=
  ...
  let inst := instInhabitedUInt32
  let inst := box inst
  let x := Array.get!Internal inst xs i
  dec inst
  ...
```
Fortunately the closed term extractor has support for precisely this
feature and thus produces:

```
def inst.boxed_const :=
  let inst := instInhabitedUInt32
  let inst := box inst
  return inst

def foo._spec (xs : @&Array UInt32) ... :=
  ...
  let inst := inst.boxed_const
  let x := Array.get!Internal inst xs i
  ...
```
As described above reads from constants are now interpreted as borrows
and thus the conversion to
`get!InternalBorrowed` becomes legal again.
2026-03-27 00:13:17 +00:00
Mac Malone
f8f12fdbc8 fix: lake: run git clean -xf when updating packages (#13141)
This PR changes Lake's materialization process to run remove untracked
files in tracked directories (via `git clean -xf`) when updating
dependency repositories. This ensures stale leftovers in the source tree
are removed.

In particular, if a `.hash` ends up in the source tree and the package
is updated, that `.hash` file will be stale but nonetheless trusted by a
Lake build. This will cause incorrect trace computation and break
builds. This happened with ProofWidgets in Mathlib (see [this Zulip
discussion](https://leanprover.zulipchat.com/#narrow/channel/113488-general/topic/ProofWidgets.20not.20up-to-date)).

This PR serves as alternative to #13130 (by @kim-em) and instead
provides a more generic solution to the problem. Nonetheless, thank them
for diagnosing this issue in the first place!
2026-03-26 22:12:54 +00:00
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
2089 changed files with 19284 additions and 9385 deletions

View File

@@ -7,6 +7,11 @@ 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.
To rebuild individual modules without a full build, use Lake directly:
```
cd src && lake build Init.Prelude
```
## Running Tests
See `tests/README.md` for full documentation. Quick reference:
@@ -56,6 +61,11 @@ make -C build/release/stage2 clean-stdlib
```
must be run manually before building.
To rebuild individual stage 2 modules without a full `make stage2`, use Lake directly:
```
cd build/release/stage2 && lake build Init.Prelude
```
## New features
When asked to implement new features:

View File

@@ -157,6 +157,16 @@ Note: `gh pr checks --watch` exits as soon as ALL checks complete (pass or fail)
fail while others are still running, `--watch` will continue until everything settles, then exit
with a non-zero code. So a background `--watch` finishing = all checks done; check which failed.
## Mathlib Bump Branches
Mathlib `bump/v4.X.0` branches live on the **fork** `leanprover-community/mathlib4-nightly-testing`,
NOT on `leanprover-community/mathlib4`.
## Never Force-Update Remote Refs Without Confirmation
Never force-update an existing remote branch or tag via `git push --force` or the GitHub API
without explicit user confirmation.
## Error Handling
**CRITICAL**: If something goes wrong or a command fails:

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,13 +125,13 @@ jobs:
else
echo "TARGET_STAGE=stage1" >> $GITHUB_ENV
fi
- name: Build
- name: Configure Build
run: |
ulimit -c unlimited # coredumps
[ -d build ] || mkdir build
cd build
# arguments passed to `cmake`
OPTIONS=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
OPTIONS=(-DWFAIL=ON)
if [[ -n '${{ matrix.release }}' ]]; then
# this also enables githash embedding into stage 1 library, which prohibits reusing
# `.olean`s across commits, so we don't do it in the fast non-release CI
@@ -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

@@ -76,9 +76,20 @@ jobs:
fi
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
else
# Scheduled: do nothing if commit already has a different tag
# Scheduled: do nothing if commit already has a different tag (e.g. a release tag)
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
HEAD_TAG="$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || true)"
if [[ -n "$HEAD_TAG" && "$HEAD_TAG" != "$LEAN_VERSION_STRING" ]]; then
echo "HEAD already tagged as ${HEAD_TAG}, skipping nightly"
elif git rev-parse "refs/tags/${LEAN_VERSION_STRING}" >/dev/null 2>&1; then
# Today's nightly already exists (e.g. from a manual release), create a revision
REV=1
while git rev-parse "refs/tags/${LEAN_VERSION_STRING}-rev${REV}" >/dev/null 2>&1; do
REV=$((REV + 1))
done
LEAN_VERSION_STRING="${LEAN_VERSION_STRING}-rev${REV}"
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
else
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
fi
fi
@@ -132,7 +143,7 @@ jobs:
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | sed -nE 's/^set\(LEAN_VERSION_IS_RELEASE ([0-9]+)\).*/\1/p')
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | grep -oE '[0-9]+' | head -1)
# Expected values from tag parsing
TAG_MAJOR="${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}"
@@ -244,7 +255,7 @@ jobs:
// portable release build: use channel with older glibc (2.26)
"name": "Linux release",
// usually not a bottleneck so make exclusive to `fast-ci`
"os": large && fast ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
"os": large && fast ? "nscloud-ubuntu-24.04-amd64-8x16-with-cache" : "ubuntu-latest",
"release": true,
// Special handling for release jobs. We want:
// 1. To run it in PRs so developers get PR toolchains (so secondary without tests is sufficient)
@@ -265,7 +276,7 @@ jobs:
},
{
"name": "Linux Lake",
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
"os": large ? "nscloud-ubuntu-24.04-amd64-8x16-with-cache" : "ubuntu-latest",
"enabled": true,
"check-rebootstrap": level >= 1,
"check-stage3": level >= 2,
@@ -273,7 +284,19 @@ jobs:
// NOTE: `test-bench` currently seems to be broken on `ubuntu-latest`
"test-bench": large && level >= 2,
// We are not warning-free yet on all platforms, start here
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror",
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror -DUSE_LAKE_CACHE=ON",
},
{
"name": "Linux Lake (Cached)",
"os": large ? "nscloud-ubuntu-24.04-amd64-8x16-with-cache" : "ubuntu-latest",
"enabled": true,
"check-rebootstrap": level >= 1,
"check-stage3": level >= 2,
"test": true,
"secondary": true,
// NOTE: `test-bench` currently seems to be broken on `ubuntu-latest`
"test-bench": large && level >= 2,
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror -DUSE_LAKE_CACHE=ON",
},
{
"name": "Linux Reldebug",
@@ -287,7 +310,7 @@ jobs:
{
"name": "Linux fsanitize",
// Always run on large if available, more reliable regarding timeouts
"os": large ? "nscloud-ubuntu-22.04-amd64-16x32-with-cache" : "ubuntu-latest",
"os": large ? "nscloud-ubuntu-24.04-amd64-16x32-with-cache" : "ubuntu-latest",
"enabled": level >= 2,
// do not fail nightlies on this for now
"secondary": level <= 2,

View File

@@ -77,7 +77,7 @@ jobs:
# sync options with `Linux Lake` to ensure cache reuse
run: |
mkdir -p build
cmake --preset release -B build -DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true
cmake --preset release -B build -DWFAIL=ON
shell: 'nix develop -c bash -euxo pipefail {0}'
- if: env.should_update_stage0 == 'yes'
run: |

1
.gitignore vendored
View File

@@ -34,3 +34,4 @@ wdErr.txt
wdIn.txt
wdOut.txt
downstream_releases/
.claude/worktrees/

View File

@@ -6,6 +6,6 @@ vscode:
- leanprover.lean4
tasks:
- name: Release build
init: cmake --preset release
- name: Build
init: cmake --preset dev
command: make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)

9
.vscode/tasks.json vendored
View File

@@ -11,6 +11,15 @@
"isDefault": true
}
},
{
"label": "build stage2",
"type": "shell",
"command": "make -C build/release stage2 -j$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)",
"problemMatcher": [],
"group": {
"kind": "build"
}
},
{
"label": "build-old",
"type": "shell",

View File

@@ -1,4 +1,6 @@
cmake_minimum_required(VERSION 3.21)
include(ExternalProject)
include(FetchContent)
if(NOT CMAKE_GENERATOR MATCHES "Makefiles")
message(FATAL_ERROR "Only makefile generators are supported")
@@ -34,7 +36,6 @@ foreach(var ${vars})
endif()
endforeach()
include(ExternalProject)
project(LEAN CXX C)
if(NOT (DEFINED STAGE0_CMAKE_EXECUTABLE_SUFFIX))
@@ -119,17 +120,17 @@ if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
endif()
if(USE_MIMALLOC)
ExternalProject_Add(
FetchContent_Declare(
mimalloc
PREFIX mimalloc
GIT_REPOSITORY https://github.com/microsoft/mimalloc
GIT_TAG v2.2.3
# just download, we compile it as part of each stage as it is small
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
# Unnecessarily deep directory structure, but it saves us from a complicated
# stage0 update for now. If we ever update the other dependencies like
# cadical, it might be worth reorganizing the directory structure.
SOURCE_DIR
"${CMAKE_BINARY_DIR}/mimalloc/src/mimalloc"
)
list(APPEND EXTRA_DEPENDS mimalloc)
FetchContent_MakeAvailable(mimalloc)
endif()
if(NOT STAGE1_PREV_STAGE)

View File

@@ -8,16 +8,26 @@
"configurePresets": [
{
"name": "release",
"displayName": "Default development optimized build config",
"displayName": "Release build config",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/release"
},
{
"name": "dev",
"displayName": "Default development optimized build config",
"cacheVariables": {
"STRIP_BINARIES": "OFF"
},
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/dev"
},
{
"name": "debug",
"displayName": "Debug build config",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"LEAN_EXTRA_CXX_FLAGS": "-DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
"CMAKE_BUILD_TYPE": "Debug"
"STRIP_BINARIES": "OFF"
},
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/debug"
@@ -26,7 +36,8 @@
"name": "reldebug",
"displayName": "Release with assertions enabled",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithAssert"
"CMAKE_BUILD_TYPE": "RelWithAssert",
"STRIP_BINARIES": "OFF"
},
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/reldebug"
@@ -38,6 +49,7 @@
"LEAN_EXTRA_CXX_FLAGS": "-fsanitize=address,undefined -DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
"LEANC_EXTRA_CC_FLAGS": "-fsanitize=address,undefined",
"LEAN_EXTRA_LINKER_FLAGS": "-fsanitize=address,undefined -fsanitize-link-c++-runtime",
"STRIP_BINARIES": "OFF",
"SMALL_ALLOCATOR": "OFF",
"USE_MIMALLOC": "OFF",
"BSYMBOLIC": "OFF",
@@ -58,6 +70,10 @@
"name": "release",
"configurePreset": "release"
},
{
"name": "dev",
"configurePreset": "dev"
},
{
"name": "debug",
"configurePreset": "debug"
@@ -81,6 +97,11 @@
"configurePreset": "release",
"output": {"outputOnFailure": true, "shortProgress": true}
},
{
"name": "dev",
"configurePreset": "dev",
"output": {"outputOnFailure": true, "shortProgress": true}
},
{
"name": "debug",
"configurePreset": "debug",

View File

@@ -30,6 +30,9 @@ cd lean4
cmake --preset release
make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)
```
For development, `cmake --preset dev` is recommended instead.
You can replace `$(nproc || sysctl -n hw.logicalcpu)` with the desired parallelism amount.
The above commands will compile the Lean library and binaries into the

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)
@@ -311,16 +311,16 @@ def check_cmake_version(repo_url, branch, version_major, version_minor, github_t
print(f" ❌ Could not retrieve {cmake_file_path} from {branch}")
return False
expected_lines = [
f"set(LEAN_VERSION_MAJOR {version_major})",
f"set(LEAN_VERSION_MINOR {version_minor})",
f"set(LEAN_VERSION_PATCH 0)",
f"set(LEAN_VERSION_IS_RELEASE 1)"
expected_patterns = [
(f"LEAN_VERSION_MAJOR", rf"^set\(LEAN_VERSION_MAJOR\s+{version_major}[\s)]", f"set(LEAN_VERSION_MAJOR {version_major} ...)"),
(f"LEAN_VERSION_MINOR", rf"^set\(LEAN_VERSION_MINOR\s+{version_minor}[\s)]", f"set(LEAN_VERSION_MINOR {version_minor} ...)"),
(f"LEAN_VERSION_PATCH", rf"^set\(LEAN_VERSION_PATCH\s+0[\s)]", f"set(LEAN_VERSION_PATCH 0 ...)"),
(f"LEAN_VERSION_IS_RELEASE", rf"^set\(LEAN_VERSION_IS_RELEASE\s+1[\s)]", f"set(LEAN_VERSION_IS_RELEASE 1 ...)"),
]
for line in expected_lines:
if not any(l.strip().startswith(line) for l in content.splitlines()):
print(f" ❌ Missing or incorrect line in {cmake_file_path}: {line}")
for name, pattern, display in expected_patterns:
if not any(re.match(pattern, l.strip()) for l in content.splitlines()):
print(f" ❌ Missing or incorrect line in {cmake_file_path}: {display}")
return False
print(f" ✅ CMake version settings are correct in {cmake_file_path}")
@@ -343,11 +343,11 @@ def check_stage0_version(repo_url, branch, version_major, version_minor, github_
for line in content.splitlines():
stripped = line.strip()
if stripped.startswith("set(LEAN_VERSION_MAJOR "):
actual = stripped.split()[-1].rstrip(")")
actual = stripped.split()[1].rstrip(")")
if actual != str(version_major):
errors.append(f"LEAN_VERSION_MAJOR: expected {version_major}, found {actual}")
elif stripped.startswith("set(LEAN_VERSION_MINOR "):
actual = stripped.split()[-1].rstrip(")")
actual = stripped.split()[1].rstrip(")")
if actual != str(version_minor):
errors.append(f"LEAN_VERSION_MINOR: expected {version_minor}, found {actual}")

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
@@ -35,6 +28,14 @@ repositories:
branch: main
dependencies: []
- name: leansqlite
url: https://github.com/leanprover/leansqlite
toolchain-tag: true
stable-branch: false
branch: main
dependencies:
- plausible
- name: verso
url: https://github.com/leanprover/verso
toolchain-tag: true
@@ -107,7 +108,7 @@ repositories:
toolchain-tag: true
stable-branch: false
branch: main
dependencies: [lean4-cli, BibtexQuery, mathlib4]
dependencies: [lean4-cli, BibtexQuery, mathlib4, leansqlite]
- name: cslib
url: https://github.com/leanprover/cslib

View File

@@ -481,11 +481,9 @@ def execute_release_steps(repo, version, config):
run_command("lake update", cwd=repo_path, stream_output=True)
elif repo_name == "verso":
# verso has nested Lake projects in test-projects/ that each have their own
# lake-manifest.json with a subverso pin. After updating the root manifest via
# `lake update`, sync the de-modulized subverso rev into all sub-manifests.
# The sub-projects use an old toolchain (v4.21.0) that doesn't support module/prelude
# syntax, so they need the de-modulized version (tagged no-modules/<root-rev>).
# The "SubVerso version consistency" CI check accepts either the root or de-modulized rev.
# lake-manifest.json with a subverso pin and their own lean-toolchain.
# After updating the root manifest via `lake update`, sync the de-modulized
# subverso rev into all sub-manifests, and update their lean-toolchain files.
run_command("lake update", cwd=repo_path, stream_output=True)
print(blue("Syncing de-modulized subverso rev to test-project sub-manifests..."))
sync_script = (
@@ -498,6 +496,15 @@ def execute_release_steps(repo, version, config):
)
run_command(sync_script, cwd=repo_path)
print(green("Synced de-modulized subverso rev to all test-project sub-manifests"))
# Update all lean-toolchain files in test-projects/ to match the root
print(blue("Updating lean-toolchain files in test-projects/..."))
find_result = run_command("find test-projects -name lean-toolchain", cwd=repo_path)
for tc_path in find_result.stdout.strip().splitlines():
if tc_path:
tc_file = repo_path / tc_path
with open(tc_file, "w") as f:
f.write(f"leanprover/lean4:{version}\n")
print(green(f" Updated {tc_path}"))
elif dependencies:
run_command(f'perl -pi -e \'s/"v4\\.[0-9]+(\\.[0-9]+)?(-rc[0-9]+)?"/"' + version + '"/g\' lakefile.*', cwd=repo_path)
run_command("lake update", cwd=repo_path, stream_output=True)
@@ -659,56 +666,61 @@ def execute_release_steps(repo, version, config):
# Fetch latest changes to ensure we have the most up-to-date nightly-testing branch
print(blue("Fetching latest changes from origin..."))
run_command("git fetch origin", cwd=repo_path)
try:
print(blue("Merging origin/nightly-testing..."))
run_command("git merge origin/nightly-testing", cwd=repo_path)
print(green("Merge completed successfully"))
except subprocess.CalledProcessError:
# Merge failed due to conflicts - check which files are conflicted
print(blue("Merge conflicts detected, checking which files are affected..."))
# Get conflicted files using git status
status_result = run_command("git status --porcelain", cwd=repo_path)
conflicted_files = []
for line in status_result.stdout.splitlines():
if len(line) >= 2 and line[:2] in ['UU', 'AA', 'DD', 'AU', 'UA', 'DU', 'UD']:
# Extract filename (skip the first 3 characters which are status codes)
conflicted_files.append(line[3:])
# Filter out allowed files
allowed_patterns = ['lean-toolchain', 'lake-manifest.json']
problematic_files = []
for file in conflicted_files:
is_allowed = any(pattern in file for pattern in allowed_patterns)
if not is_allowed:
problematic_files.append(file)
if problematic_files:
# There are conflicts in non-allowed files - fail
print(red("❌ Merge failed!"))
print(red(f"Merging nightly-testing resulted in conflicts in:"))
for file in problematic_files:
print(red(f" - {file}"))
print(red("Please resolve these conflicts manually."))
return
else:
# Only allowed files are conflicted - resolve them automatically
print(green(f"✅ Only allowed files conflicted: {', '.join(conflicted_files)}"))
print(blue("Resolving conflicts automatically..."))
# For lean-toolchain and lake-manifest.json, keep our versions
# Check if nightly-testing branch exists on origin (use local ref after fetch for exact match)
nightly_check = run_command("git show-ref --verify --quiet refs/remotes/origin/nightly-testing", cwd=repo_path, check=False)
if nightly_check.returncode != 0:
print(yellow("No nightly-testing branch found on origin, skipping merge"))
else:
try:
print(blue("Merging origin/nightly-testing..."))
run_command("git merge origin/nightly-testing", cwd=repo_path)
print(green("Merge completed successfully"))
except subprocess.CalledProcessError:
# Merge failed due to conflicts - check which files are conflicted
print(blue("Merge conflicts detected, checking which files are affected..."))
# Get conflicted files using git status
status_result = run_command("git status --porcelain", cwd=repo_path)
conflicted_files = []
for line in status_result.stdout.splitlines():
if len(line) >= 2 and line[:2] in ['UU', 'AA', 'DD', 'AU', 'UA', 'DU', 'UD']:
# Extract filename (skip the first 3 characters which are status codes)
conflicted_files.append(line[3:])
# Filter out allowed files
allowed_patterns = ['lean-toolchain', 'lake-manifest.json']
problematic_files = []
for file in conflicted_files:
print(blue(f"Keeping our version of {file}"))
run_command(f"git checkout --ours {file}", cwd=repo_path)
# Complete the merge
run_command("git add .", cwd=repo_path)
run_command("git commit --no-edit", cwd=repo_path)
print(green("Merge completed successfully with automatic conflict resolution"))
is_allowed = any(pattern in file for pattern in allowed_patterns)
if not is_allowed:
problematic_files.append(file)
if problematic_files:
# There are conflicts in non-allowed files - fail
print(red("❌ Merge failed!"))
print(red(f"Merging nightly-testing resulted in conflicts in:"))
for file in problematic_files:
print(red(f" - {file}"))
print(red("Please resolve these conflicts manually."))
return
else:
# Only allowed files are conflicted - resolve them automatically
print(green(f"✅ Only allowed files conflicted: {', '.join(conflicted_files)}"))
print(blue("Resolving conflicts automatically..."))
# For lean-toolchain and lake-manifest.json, keep our versions
for file in conflicted_files:
print(blue(f"Keeping our version of {file}"))
run_command(f"git checkout --ours {file}", cwd=repo_path)
# Complete the merge
run_command("git add .", cwd=repo_path)
run_command("git commit --no-edit", cwd=repo_path)
print(green("✅ Merge completed successfully with automatic conflict resolution"))
# Build and test (skip for Mathlib)
if repo_name not in ["mathlib4"]:

View File

@@ -8,7 +8,7 @@ endif()
include(ExternalProject)
project(LEAN CXX C)
set(LEAN_VERSION_MAJOR 4 CACHE STRING "")
set(LEAN_VERSION_MINOR 30 CACHE STRING "")
set(LEAN_VERSION_MINOR 31 CACHE STRING "")
set(LEAN_VERSION_PATCH 0 CACHE STRING "")
set(LEAN_VERSION_IS_RELEASE 0 CACHE STRING "") # This number is 1 in the release revision, and 0 otherwise.
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
@@ -80,6 +80,7 @@ option(CCACHE "use ccache" ON)
option(SPLIT_STACK "SPLIT_STACK" OFF)
# When OFF we disable LLVM support
option(LLVM "LLVM" OFF)
option(STRIP_BINARIES "Strip produced binaries" ON)
# When ON we include githash in the version string
option(USE_GITHASH "GIT_HASH" ON)
@@ -115,11 +116,19 @@ option(CHECK_OLEAN_VERSION "Only load .olean files compiled with the current ver
option(USE_LAKE "Use Lake instead of lean.mk for building core libs from language server" ON)
option(USE_LAKE_CACHE "Use the Lake artifact cache for stage 1 builds (requires USE_LAKE)" OFF)
set(LEAN_EXTRA_MAKE_OPTS "" CACHE STRING "extra options to lean --make")
set(LEAN_EXTRA_OPTS "" CACHE STRING "extra options to lean (via lake or make)")
set(LEAN_EXTRA_MAKE_OPTS "" CACHE STRING "extra options to leanmake")
set(LEANC_CC ${CMAKE_C_COMPILER} CACHE STRING "C compiler to use in `leanc`")
# Temporary, core-only flags. Must be synced with stdlib_flags.h.
string(APPEND LEAN_EXTRA_MAKE_OPTS " -Dbackward.do.legacy=false")
string(APPEND LEAN_EXTRA_OPTS " -Dbackward.do.legacy=false")
# option used by the CI to fail on warnings
option(WFAIL "Fail build if warnings are emitted by Lean" ON)
if(WFAIL MATCHES "ON")
string(APPEND LAKE_EXTRA_ARGS " --wfail")
string(APPEND LEAN_EXTRA_MAKE_OPTS " -DwarningAsError=true")
endif()
if(LAZY_RC MATCHES "ON")
set(LEAN_LAZY_RC "#define LEAN_LAZY_RC")
@@ -197,7 +206,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/lean")
# OSX default thread stack size is very small. Moreover, in Debug mode, each new stack frame consumes a lot of extra memory.
if((MULTI_THREAD MATCHES "ON") AND (CMAKE_SYSTEM_NAME MATCHES "Darwin"))
string(APPEND LEAN_EXTRA_MAKE_OPTS " -s40000")
string(APPEND LEAN_EXTRA_OPTS " -s40000")
endif()
# We want explicit stack probes in huge Lean stack frames for robust stack overflow detection
@@ -614,6 +623,38 @@ else()
OUTPUT_VARIABLE GIT_SHA1
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Fallback for jj workspaces where git cannot find .git directly.
# Use `jj git root` to find the backing git repo, then `jj log` to
# resolve the current workspace's commit (git HEAD points to the root
# workspace, not the current one).
if("${GIT_SHA1}" STREQUAL "")
find_program(JJ_EXECUTABLE jj)
if(JJ_EXECUTABLE)
execute_process(
COMMAND "${JJ_EXECUTABLE}" git root
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE _jj_git_dir
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _jj_git_root_result
)
execute_process(
COMMAND "${JJ_EXECUTABLE}" log -r @ --no-graph -T "commit_id"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
OUTPUT_VARIABLE _jj_commit
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
RESULT_VARIABLE _jj_rev_result
)
if(_jj_git_root_result EQUAL 0 AND _jj_rev_result EQUAL 0)
execute_process(
COMMAND git --git-dir "${_jj_git_dir}" ls-tree "${_jj_commit}" stage0 --object-only
OUTPUT_VARIABLE GIT_SHA1
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
endif()
endif()
message(STATUS "stage0 sha1: ${GIT_SHA1}")
# Now that we've prepared the information for the next stage, we can forget that we will use
# Lake in the future as we won't use it in this stage
@@ -637,6 +678,9 @@ else()
set(LEAN_PATH_SEPARATOR ":")
endif()
# inherit genral options for lean.mk.in and stdlib.make.in
string(APPEND LEAN_EXTRA_MAKE_OPTS " ${LEAN_EXTRA_OPTS}")
# Version
configure_file("${LEAN_SOURCE_DIR}/version.h.in" "${LEAN_BINARY_DIR}/include/lean/version.h")
if(STAGE EQUAL 0)
@@ -797,7 +841,14 @@ if(LLVM AND STAGE GREATER 0)
set(EXTRA_LEANMAKE_OPTS "LLVM=1")
endif()
set(STDLIBS Init Std Lean Leanc LeanIR)
set(
STDLIBS
Init
Std
Lean
Leanc
LeanIR
)
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
list(APPEND STDLIBS Lake LeanChecker)
endif()
@@ -905,10 +956,7 @@ if(PREV_STAGE)
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)
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
@@ -1017,7 +1065,7 @@ string(REPLACE "ROOT" "${CMAKE_BINARY_DIR}" LEANC_CC "${LEANC_CC}")
string(REPLACE "ROOT" "${CMAKE_BINARY_DIR}" LEANC_INTERNAL_FLAGS "${LEANC_INTERNAL_FLAGS}")
string(REPLACE "ROOT" "${CMAKE_BINARY_DIR}" LEANC_INTERNAL_LINKER_FLAGS "${LEANC_INTERNAL_LINKER_FLAGS}")
toml_escape("${LEAN_EXTRA_MAKE_OPTS}" LEAN_EXTRA_OPTS_TOML)
toml_escape("${LEAN_EXTRA_OPTS}" LEAN_EXTRA_OPTS_TOML)
if(CMAKE_BUILD_TYPE MATCHES "Debug|Release|RelWithDebInfo|MinSizeRel")
set(CMAKE_BUILD_TYPE_TOML "${CMAKE_BUILD_TYPE}")

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

@@ -1085,6 +1085,17 @@ Examples:
def sum {α} [Add α] [Zero α] : Array α α :=
foldr (· + ·) 0
/--
Computes the product of the elements of an array.
Examples:
* `#[a, b, c].prod = a * (b * (c * 1))`
* `#[1, 2, 5].prod = 10`
-/
@[inline, expose]
def prod {α} [Mul α] [One α] : Array α α :=
foldr (· * ·) 1
/--
Counts the number of elements in the array `as` that satisfy the Boolean predicate `p`.

View File

@@ -7,6 +7,7 @@ module
prelude
public import Init.Data.List.Int.Sum
public import Init.Data.List.Int.Prod
public import Init.Data.Array.MinMax
import Init.Data.Int.Lemmas
@@ -74,4 +75,17 @@ theorem sum_div_length_le_max_of_max?_eq_some_int {xs : Array Int} (h : xs.max?
simpa [List.max?_toArray, List.sum_toArray] using
List.sum_div_length_le_max_of_max?_eq_some_int (by simpa using h)
@[simp] theorem prod_replicate_int {n : Nat} {a : Int} : (replicate n a).prod = a ^ n := by
rw [ List.toArray_replicate, List.prod_toArray]
simp
theorem prod_append_int {as₁ as₂ : Array Int} : (as₁ ++ as₂).prod = as₁.prod * as₂.prod := by
simp [prod_append]
theorem prod_reverse_int (xs : Array Int) : xs.reverse.prod = xs.prod := by
simp [prod_reverse]
theorem prod_eq_foldl_int {xs : Array Int} : xs.prod = xs.foldl (init := 1) (· * ·) := by
simp only [foldl_eq_foldr_reverse, Int.mul_comm, prod_eq_foldr, prod_reverse_int]
end Array

View File

@@ -4380,6 +4380,47 @@ theorem sum_eq_foldl [Zero α] [Add α] [Std.Associative (α := α) (· + ·)]
xs.sum = xs.foldl (init := 0) (· + ·) := by
simp [ sum_toList, List.sum_eq_foldl]
/-! ### prod -/
@[simp, grind =] theorem prod_empty [Mul α] [One α] : (#[] : Array α).prod = 1 := rfl
theorem prod_eq_foldr [Mul α] [One α] {xs : Array α} :
xs.prod = xs.foldr (init := 1) (· * ·) :=
rfl
@[simp, grind =]
theorem prod_toList [Mul α] [One α] {as : Array α} : as.toList.prod = as.prod := by
cases as
simp [Array.prod, List.prod]
@[simp, grind =]
theorem prod_append [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.LawfulLeftIdentity (α := α) (· * ·) 1]
{as₁ as₂ : Array α} : (as₁ ++ as₂).prod = as₁.prod * as₂.prod := by
simp [ prod_toList, List.prod_append]
@[simp, grind =]
theorem prod_singleton [Mul α] [One α] [Std.LawfulRightIdentity (· * ·) (1 : α)] {x : α} :
#[x].prod = x := by
simp [Array.prod_eq_foldr, Std.LawfulRightIdentity.right_id x]
@[simp, grind =]
theorem prod_push [Mul α] [One α] [Std.Associative (α := α) (· * ·)]
[Std.LawfulIdentity (· * ·) (1 : α)] {xs : Array α} {x : α} :
(xs.push x).prod = xs.prod * x := by
simp [Array.prod_eq_foldr, Std.LawfulRightIdentity.right_id, Std.LawfulLeftIdentity.left_id,
Array.foldr_assoc]
@[simp, grind =]
theorem prod_reverse [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.Commutative (α := α) (· * ·)]
[Std.LawfulLeftIdentity (α := α) (· * ·) 1] (xs : Array α) : xs.reverse.prod = xs.prod := by
simp [ prod_toList, List.prod_reverse]
theorem prod_eq_foldl [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.LawfulIdentity (· * ·) (1 : α)] {xs : Array α} :
xs.prod = xs.foldl (init := 1) (· * ·) := by
simp [ prod_toList, List.prod_eq_foldl]
theorem foldl_toList_eq_flatMap {l : List α} {acc : Array β}
{F : Array β α Array β} {G : α List β}
(H : acc a, (F acc a).toList = acc.toList ++ G a) :

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

@@ -8,6 +8,7 @@ module
prelude
public import Init.Data.Array.MinMax
import Init.Data.List.Nat.Sum
import Init.Data.List.Nat.Prod
import Init.Data.Array.Lemmas
public section
@@ -81,4 +82,24 @@ theorem sum_div_length_le_max_of_max?_eq_some_nat {xs : Array Nat} (h : xs.max?
simpa [List.max?_toArray, List.sum_toArray] using
List.sum_div_length_le_max_of_max?_eq_some_nat (by simpa using h)
protected theorem prod_pos_iff_forall_pos_nat {xs : Array Nat} : 0 < xs.prod x xs, 0 < x := by
simp [ prod_toList, List.prod_pos_iff_forall_pos_nat]
protected theorem prod_eq_zero_iff_exists_zero_nat {xs : Array Nat} :
xs.prod = 0 x xs, x = 0 := by
simp [ prod_toList, List.prod_eq_zero_iff_exists_zero_nat]
@[simp] theorem prod_replicate_nat {n : Nat} {a : Nat} : (replicate n a).prod = a ^ n := by
rw [ List.toArray_replicate, List.prod_toArray]
simp
theorem prod_append_nat {as₁ as₂ : Array Nat} : (as₁ ++ as₂).prod = as₁.prod * as₂.prod := by
simp [prod_append]
theorem prod_reverse_nat (xs : Array Nat) : xs.reverse.prod = xs.prod := by
simp [prod_reverse]
theorem prod_eq_foldl_nat {xs : Array Nat} : xs.prod = xs.foldl (init := 1) (· * ·) := by
simp only [foldl_eq_foldr_reverse, Nat.mul_comm, prod_eq_foldr, prod_reverse_nat]
end Array

View File

@@ -66,3 +66,8 @@ theorem BEq.neq_of_beq_of_neq [BEq α] [PartialEquivBEq α] {a b c : α} :
instance (priority := low) [BEq α] [LawfulBEq α] : EquivBEq α where
symm h := beq_iff_eq.2 <| Eq.symm <| beq_iff_eq.1 h
trans hab hbc := beq_iff_eq.2 <| (beq_iff_eq.1 hab).trans <| beq_iff_eq.1 hbc
theorem equivBEq_of_iff_apply_eq [BEq α] (f : α β) (hf : a b, a == b f a = f b) : EquivBEq α where
rfl := by simp [hf]
symm := by simp [hf, eq_comm]
trans hab hbc := (hf _ _).2 (Eq.trans ((hf _ _).1 hab) ((hf _ _).1 hbc))

View File

@@ -20,12 +20,20 @@ universe u
namespace ByteArray
deriving instance BEq for ByteArray
@[extern "lean_sarray_dec_eq"]
def beq (lhs rhs : @& ByteArray) : Bool :=
lhs.data == rhs.data
instance : BEq ByteArray where
beq := beq
attribute [ext] ByteArray
instance : DecidableEq ByteArray :=
fun _ _ => decidable_of_decidable_of_iff ByteArray.ext_iff.symm
@[extern "lean_sarray_dec_eq"]
def decEq (lhs rhs : @& ByteArray) : Decidable (lhs = rhs) :=
decidable_of_decidable_of_iff ByteArray.ext_iff.symm
instance : DecidableEq ByteArray := decEq
instance : Inhabited ByteArray where
default := empty

View File

@@ -98,4 +98,8 @@ theorem toNat_inj {c d : Char} : c.toNat = d.toNat ↔ c = d := by
theorem isDigit_iff_toNat {c : Char} : c.isDigit '0'.toNat c.toNat c.toNat '9'.toNat := by
simp [isDigit, UInt32.le_iff_toNat_le]
@[simp]
theorem toNat_mk {val : UInt32} {h} : (Char.mk val h).toNat = val.toNat := by
simp [ toNat_val]
end Char

View File

@@ -527,6 +527,14 @@ theorem castLE_of_eq {m n : Nat} (h : m = n) {h' : m ≤ n} : castLE h' = Fin.ca
@[simp, grind =] theorem val_castAdd (m : Nat) (i : Fin n) : (castAdd m i : Nat) = i := rfl
/-
**Note**
The current pattern inference heuristic includes the implicit term `n + m` as pattern of the pattern,
but arithmetic is problematic in patterns because it is an interpreted symbol. For example,
we will fail to match `@val n (castNat 0 i)`. Thus, we mark the implicit subterm with `no_index`
-/
grind_pattern val_castAdd => @val (no_index _) (castAdd m i)
@[deprecated val_castAdd (since := "2025-11-21")]
theorem coe_castAdd (m : Nat) (i : Fin n) : (castAdd m i : Nat) = i := rfl
@@ -637,7 +645,15 @@ theorem exists_castSucc_eq {n : Nat} {i : Fin (n + 1)} : (∃ j, castSucc j = i)
theorem succ_castSucc {n : Nat} (i : Fin n) : i.castSucc.succ = i.succ.castSucc := rfl
@[simp, grind =] theorem val_addNat (m : Nat) (i : Fin n) : (addNat i m : Nat) = i + m := rfl
@[simp] theorem val_addNat (m : Nat) (i : Fin n) : (addNat i m : Nat) = i + m := rfl
/-
**Note**
The current pattern inference heuristic includes the implicit term `n + m` as pattern of the pattern,
but arithmetic is problematic in patterns because it is an interpreted symbol. For example,
we will fail to match `@val n (addNat i 0)`. Thus, we mark the implicit subterm with `no_index`
-/
grind_pattern val_addNat => @val (no_index _) (addNat i m)
@[deprecated val_addNat (since := "2025-11-21")]
theorem coe_addNat (m : Nat) (i : Fin n) : (addNat i m : Nat) = i + m := rfl

View File

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

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

@@ -271,7 +271,7 @@ private def optionPelim' {α : Type u_1} (t : Option α) {β : Sort u_2}
/--
Inserts an `Option` case distinction after the first computation of a call to `MonadAttach.pbind`.
This lemma is useful for simplifying the second computation, which often involes `match` expressions
This lemma is useful for simplifying the second computation, which often involves `match` expressions
that use `pbind`'s proof term.
-/
private theorem pbind_eq_pbind_if_isSome [Monad m] [MonadAttach m] (x : m (Option α)) (f : (_ : _) _ m β) :

View File

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

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

@@ -2056,6 +2056,20 @@ def sum {α} [Add α] [Zero α] : List αα :=
@[simp, grind =] theorem sum_cons [Add α] [Zero α] {a : α} {l : List α} : (a::l).sum = a + l.sum := rfl
theorem sum_eq_foldr [Add α] [Zero α] {l : List α} : l.sum = l.foldr (· + ·) 0 := rfl
/--
Computes the product of the elements of a list.
Examples:
* `[a, b, c].prod = a * (b * (c * 1))`
* `[1, 2, 5].prod = 10`
-/
def prod {α} [Mul α] [One α] : List α α :=
foldr (· * ·) 1
@[simp, grind =] theorem prod_nil [Mul α] [One α] : ([] : List α).prod = 1 := rfl
@[simp, grind =] theorem prod_cons [Mul α] [One α] {a : α} {l : List α} : (a::l).prod = a * l.prod := rfl
theorem prod_eq_foldr [Mul α] [One α] {l : List α} : l.prod = l.foldr (· * ·) 1 := rfl
/-! ### range -/
/--

View File

@@ -7,3 +7,4 @@ module
prelude
public import Init.Data.List.Int.Sum
public import Init.Data.List.Int.Prod

View File

@@ -0,0 +1,31 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
module
prelude
import Init.Data.List.Lemmas
import Init.Data.Int.Lemmas
public import Init.Data.Int.Pow
public import Init.Data.List.Basic
public section
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
namespace List
@[simp]
theorem prod_replicate_int {n : Nat} {a : Int} : (replicate n a).prod = a ^ n := by
induction n <;> simp_all [replicate_succ, Int.pow_succ, Int.mul_comm]
theorem prod_append_int {l₁ l₂ : List Int} : (l₁ ++ l₂).prod = l₁.prod * l₂.prod := by
simp [prod_append]
theorem prod_reverse_int (xs : List Int) : xs.reverse.prod = xs.prod := by
simp [prod_reverse]
end List

View File

@@ -1878,6 +1878,24 @@ theorem sum_reverse [Zero α] [Add α] [Std.Associative (α := α) (· + ·)]
simp_all [sum_append, Std.Commutative.comm (α := α) _ 0,
Std.LawfulLeftIdentity.left_id, Std.Commutative.comm]
@[simp, grind =]
theorem prod_append [Mul α] [One α] [Std.LawfulLeftIdentity (α := α) (· * ·) 1]
[Std.Associative (α := α) (· * ·)] {l₁ l₂ : List α} : (l₁ ++ l₂).prod = l₁.prod * l₂.prod := by
induction l₁ generalizing l₂ <;> simp_all [Std.Associative.assoc, Std.LawfulLeftIdentity.left_id]
@[simp, grind =]
theorem prod_singleton [Mul α] [One α] [Std.LawfulRightIdentity (· * ·) (1 : α)] {x : α} :
[x].prod = x := by
simp [List.prod_eq_foldr, Std.LawfulRightIdentity.right_id x]
@[simp, grind =]
theorem prod_reverse [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.Commutative (α := α) (· * ·)]
[Std.LawfulLeftIdentity (α := α) (· * ·) 1] (xs : List α) : xs.reverse.prod = xs.prod := by
induction xs <;>
simp_all [prod_append, Std.Commutative.comm (α := α) _ 1,
Std.LawfulLeftIdentity.left_id, Std.Commutative.comm]
/-! ### concat
Note that `concat_eq_append` is a `@[simp]` lemma, so `concat` should usually not appear in goals.
@@ -2784,6 +2802,11 @@ theorem sum_eq_foldl [Zero α] [Add α] [Std.Associative (α := α) (· + ·)]
xs.sum = xs.foldl (init := 0) (· + ·) := by
simp [sum_eq_foldr, foldl_eq_apply_foldr, Std.LawfulLeftIdentity.left_id]
theorem prod_eq_foldl [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.LawfulIdentity (· * ·) (1 : α)] {xs : List α} :
xs.prod = xs.foldl (init := 1) (· * ·) := by
simp [prod_eq_foldr, foldl_eq_apply_foldr, Std.LawfulLeftIdentity.left_id]
-- The argument `f : α₁ → α₂` is intentionally explicit, as it is sometimes not found by unification.
theorem foldl_hom (f : α₁ α₂) {g₁ : α₁ β α₁} {g₂ : α₂ β α₂} {l : List β} {init : α₁}
(H : x y, g₂ (f x) y = f (g₁ x y)) : l.foldl g₂ (f init) = f (l.foldl g₁ init) := by

View File

@@ -13,6 +13,7 @@ public import Init.Data.List.Nat.Sublist
public import Init.Data.List.Nat.TakeDrop
public import Init.Data.List.Nat.Count
public import Init.Data.List.Nat.Sum
public import Init.Data.List.Nat.Prod
public import Init.Data.List.Nat.Erase
public import Init.Data.List.Nat.Find
public import Init.Data.List.Nat.BEq

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: Kim Morrison
-/
module
prelude
import Init.Data.List.Lemmas
public import Init.BinderPredicates
public import Init.NotationExtra
import Init.Data.Nat.Lemmas
public section
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
namespace List
protected theorem prod_eq_zero_iff_exists_zero_nat {l : List Nat} : l.prod = 0 x l, x = 0 := by
induction l with
| nil => simp
| cons x xs ih =>
simp [Nat.mul_eq_zero, ih, eq_comm (a := (0 : Nat))]
protected theorem prod_pos_iff_forall_pos_nat {l : List Nat} : 0 < l.prod x l, 0 < x := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [prod_cons, mem_cons, forall_eq_or_imp, ih]
constructor
· intro h
exact Nat.pos_of_mul_pos_right h, Nat.pos_of_mul_pos_left h
· exact fun hx, hxs => Nat.mul_pos hx hxs
@[simp]
theorem prod_replicate_nat {n : Nat} {a : Nat} : (replicate n a).prod = a ^ n := by
induction n <;> simp_all [replicate_succ, Nat.pow_succ, Nat.mul_comm]
theorem prod_append_nat {l₁ l₂ : List Nat} : (l₁ ++ l₂).prod = l₁.prod * l₂.prod := by
simp [prod_append]
theorem prod_reverse_nat (xs : List Nat) : xs.reverse.prod = xs.prod := by
simp [prod_reverse]
theorem prod_eq_foldl_nat {xs : List Nat} : xs.prod = xs.foldl (init := 1) (· * ·) := by
simp only [foldl_eq_foldr_reverse, Nat.mul_comm, prod_eq_foldr, prod_reverse_nat]
end List

View File

@@ -606,6 +606,13 @@ theorem sum_nat {l₁ l₂ : List Nat} (h : l₁ ~ l₂) : l₁.sum = l₂.sum :
| swap => simpa [List.sum_cons] using Nat.add_left_comm ..
| trans _ _ ih₁ ih₂ => simp [ih₁, ih₂]
theorem prod_nat {l₁ l₂ : List Nat} (h : l₁ ~ l₂) : l₁.prod = l₂.prod := by
induction h with
| nil => simp
| cons _ _ ih => simp [ih]
| swap => simpa [List.prod_cons] using Nat.mul_left_comm ..
| trans _ _ ih₁ ih₂ => simp [ih₁, ih₂]
theorem all_eq {l₁ l₂ : List α} {f : α Bool} (hp : l₁.Perm l₂) : l₁.all f = l₂.all f := by
rw [Bool.eq_iff_iff]; simp [hp.mem_iff]
@@ -615,6 +622,9 @@ theorem any_eq {l₁ l₂ : List α} {f : α → Bool} (hp : l₁.Perm l₂) : l
grind_pattern Perm.sum_nat => l₁ ~ l₂, l₁.sum
grind_pattern Perm.sum_nat => l₁ ~ l₂, l₂.sum
grind_pattern Perm.prod_nat => l₁ ~ l₂, l₁.prod
grind_pattern Perm.prod_nat => l₁ ~ l₂, l₂.prod
end Perm
end List

View File

@@ -213,6 +213,9 @@ theorem forM_toArray [Monad m] (l : List α) (f : α → m PUnit) :
@[simp, grind =] theorem sum_toArray [Add α] [Zero α] (l : List α) : l.toArray.sum = l.sum := by
simp [Array.sum, List.sum]
@[simp, grind =] theorem prod_toArray [Mul α] [One α] (l : List α) : l.toArray.prod = l.prod := by
simp [Array.prod, List.prod]
@[simp, grind =] theorem append_toArray (l₁ l₂ : List α) :
l₁.toArray ++ l₂.toArray = (l₁ ++ l₂).toArray := by
apply ext'

View File

@@ -60,7 +60,7 @@ theorem gcd_def (x y : Nat) : gcd x y = if x = 0 then y else gcd (y % x) x := by
cases n with
| zero => simp
| succ n =>
-- `simp [gcd_succ]` produces an invalid term unless `gcd_succ` is proved with `id rfl` instead
-- `simp [gcd_succ]` produces an invalid term unless `gcd_succ` is proved with `(rfl)` instead
rw [gcd_succ]
exact gcd_zero_left _
instance : Std.LawfulIdentity gcd 0 where

View File

@@ -298,7 +298,7 @@ theorem ofDigitChars_cons {c : Char} {cs : List Char} {init : Nat} :
simp [ofDigitChars]
theorem ofDigitChars_cons_digitChar_of_lt_ten {n : Nat} (hn : n < 10) {cs : List Char} {init : Nat} :
ofDigitChars 10 (n.digitChar :: cs) init = ofDigitChars 10 cs (10 * init + n) := by
ofDigitChars b (n.digitChar :: cs) init = ofDigitChars b cs (b * init + n) := by
simp [ofDigitChars_cons, Nat.toNat_digitChar_sub_48_of_lt_ten hn]
theorem ofDigitChars_eq_ofDigitChars_zero {l : List Char} {init : Nat} :
@@ -320,15 +320,17 @@ theorem ofDigitChars_replicate_zero {n : Nat} : ofDigitChars b (List.replicate n
| zero => simp
| succ n ih => simp [List.replicate_succ, ofDigitChars_cons, ih, Nat.pow_succ, Nat.mul_assoc]
@[simp]
theorem ofDigitChars_toDigits {n : Nat} : ofDigitChars 10 (toDigits 10 n) 0 = n := by
have : 1 < 10 := by decide
induction n using base_induction 10 this with
theorem ofDigitChars_toDigits {b n : Nat} (hb' : 1 < b) (hb : b 10) : ofDigitChars b (toDigits b n) 0 = n := by
induction n using base_induction b hb' with
| single m hm =>
simp [Nat.toDigits_of_lt_base hm, ofDigitChars_cons_digitChar_of_lt_ten hm]
simp [Nat.toDigits_of_lt_base hm, ofDigitChars_cons_digitChar_of_lt_ten (by omega : m < 10)]
| digit m k hk hm ih =>
rw [ Nat.toDigits_append_toDigits this hm hk,
rw [ Nat.toDigits_append_toDigits hb' hm hk,
ofDigitChars_append, ih, Nat.toDigits_of_lt_base hk,
ofDigitChars_cons_digitChar_of_lt_ten hk, ofDigitChars_nil]
ofDigitChars_cons_digitChar_of_lt_ten (Nat.lt_of_lt_of_le hk hb), ofDigitChars_nil]
@[simp]
theorem ofDigitChars_ten_toDigits {n : Nat} : ofDigitChars 10 (toDigits 10 n) 0 = n :=
ofDigitChars_toDigits (by decide) (by decide)
end Nat

View File

@@ -9,7 +9,7 @@ prelude
public import Init.Data.Order.Ord
public import Init.Data.String.Basic
import Init.Data.Char.Lemmas
import Init.Data.String.Lemmas
import Init.Data.String.Lemmas.StringOrder
public section

View File

@@ -243,6 +243,10 @@ public theorem lt_iff_le_and_ne [LE α] [LT α] [LawfulOrderLT α] [IsPartialOrd
a < b a b a b := by
simpa [le_iff_lt_or_eq, or_and_right] using Std.ne_of_lt
public theorem lt_trichotomy [LT α] [Std.Trichotomous (α := α) (· < ·)] (a b : α) :
a < b a = b b < a :=
Trichotomous.rel_or_eq_or_rel_swap
end LT
end Std

View File

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

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]
@@ -1382,6 +1386,11 @@ theorem Slice.copy_eq_copy_sliceTo {s : Slice} {pos : s.Pos} :
rw [Nat.max_eq_right]
exact pos.offset_str_le_offset_endExclusive
@[simp]
theorem Slice.sliceTo_append_sliceFrom {s : Slice} {pos : s.Pos} :
(s.sliceTo pos).copy ++ (s.sliceFrom pos).copy = s.copy :=
copy_eq_copy_sliceTo.symm
/-- Given a slice `s` and a position on `s.copy`, obtain the corresponding position on `s`. -/
@[inline]
def Pos.ofCopy {s : Slice} (pos : s.copy.Pos) : s.Pos where
@@ -1741,6 +1750,31 @@ theorem Slice.Pos.offset_cast {s t : Slice} {pos : s.Pos} {h : s.copy = t.copy}
theorem Slice.Pos.cast_rfl {s : Slice} {pos : s.Pos} : pos.cast rfl = pos :=
Slice.Pos.ext (by simp)
@[simp]
theorem Slice.Pos.cast_cast {s t u : Slice} {hst : s.copy = t.copy} {htu : t.copy = u.copy}
{pos : s.Pos} : (pos.cast hst).cast htu = pos.cast (hst.trans htu) :=
Slice.Pos.ext (by simp)
@[simp]
theorem Slice.Pos.cast_inj {s t : Slice} {hst : s.copy = t.copy} {p q : s.Pos} : p.cast hst = q.cast hst p = q := by
simp [Slice.Pos.ext_iff]
@[simp]
theorem Slice.Pos.cast_startPos {s t : Slice} {hst : s.copy = t.copy} : s.startPos.cast hst = t.startPos :=
Slice.Pos.ext (by simp)
@[simp]
theorem Slice.Pos.cast_eq_startPos {s t : Slice} {p : s.Pos} {hst : s.copy = t.copy} : p.cast hst = t.startPos p = s.startPos := by
rw [ cast_startPos (hst := hst), Pos.cast_inj]
@[simp]
theorem Slice.Pos.cast_endPos {s t : Slice} {hst : s.copy = t.copy} : s.endPos.cast hst = t.endPos :=
Slice.Pos.ext (by simp [ rawEndPos_copy, hst])
@[simp]
theorem Slice.Pos.cast_eq_endPos {s t : Slice} {p : s.Pos} {hst : s.copy = t.copy} : p.cast hst = t.endPos p = s.endPos := by
rw [ cast_endPos (hst := hst), Pos.cast_inj]
@[simp]
theorem Slice.Pos.cast_le_cast_iff {s t : Slice} {pos pos' : s.Pos} {h : s.copy = t.copy} :
pos.cast h pos'.cast h pos pos' := by
@@ -1751,6 +1785,22 @@ theorem Slice.Pos.cast_lt_cast_iff {s t : Slice} {pos pos' : s.Pos} {h : s.copy
pos.cast h < pos'.cast h pos < pos' := by
simp [Slice.Pos.lt_iff]
theorem Slice.Pos.cast_le_iff {s t : Slice} {pos : s.Pos} {pos' : t.Pos} {h : s.copy = t.copy} :
pos.cast h pos' pos pos'.cast h.symm := by
simp [Slice.Pos.le_iff]
theorem Slice.Pos.le_cast_iff {s t : Slice} {pos : t.Pos} {pos' : s.Pos} {h : s.copy = t.copy} :
pos pos'.cast h pos.cast h.symm pos' := by
simp [Slice.Pos.le_iff]
theorem Slice.Pos.cast_lt_iff {s t : Slice} {pos : s.Pos} {pos' : t.Pos} {h : s.copy = t.copy} :
pos.cast h < pos' pos < pos'.cast h.symm := by
simp [Slice.Pos.lt_iff]
theorem Slice.Pos.lt_cast_iff {s t : Slice} {pos : t.Pos} {pos' : s.Pos} {h : s.copy = t.copy} :
pos < pos'.cast h pos.cast h.symm < pos' := by
simp [Slice.Pos.lt_iff]
/-- Constructs a valid position on `t` from a valid position on `s` and a proof that `s = t`. -/
@[inline]
def Pos.cast {s t : String} (pos : s.Pos) (h : s = t) : t.Pos where
@@ -1765,6 +1815,31 @@ theorem Pos.offset_cast {s t : String} {pos : s.Pos} {h : s = t} :
theorem Pos.cast_rfl {s : String} {pos : s.Pos} : pos.cast rfl = pos :=
Pos.ext (by simp)
@[simp]
theorem Pos.cast_cast {s t u : String} {hst : s = t} {htu : t = u}
{pos : s.Pos} : (pos.cast hst).cast htu = pos.cast (hst.trans htu) :=
Pos.ext (by simp)
@[simp]
theorem Pos.cast_inj {s t : String} {hst : s = t} {p q : s.Pos} : p.cast hst = q.cast hst p = q := by
simp [Pos.ext_iff]
@[simp]
theorem Pos.cast_startPos {s t : String} {hst : s = t} : s.startPos.cast hst = t.startPos := by
subst hst; simp
@[simp]
theorem Pos.cast_eq_startPos {s t : String} {hst : s = t} {p : s.Pos} : p.cast hst = t.startPos p = s.startPos := by
rw [ Pos.cast_startPos (hst := hst), Pos.cast_inj]
@[simp]
theorem Pos.cast_endPos {s t : String} {hst : s = t} : s.endPos.cast hst = t.endPos := by
subst hst; simp
@[simp]
theorem Pos.cast_eq_endPos {s t : String} {hst : s = t} {p : s.Pos} : p.cast hst = t.endPos p = s.endPos := by
rw [ Pos.cast_endPos (hst := hst), Pos.cast_inj]
@[simp]
theorem Pos.cast_le_cast_iff {s t : String} {pos pos' : s.Pos} {h : s = t} :
pos.cast h pos'.cast h pos pos' := by
@@ -1775,6 +1850,22 @@ theorem Pos.cast_lt_cast_iff {s t : String} {pos pos' : s.Pos} {h : s = t} :
pos.cast h < pos'.cast h pos < pos' := by
cases h; simp
theorem Pos.cast_le_iff {s t : String} {pos : s.Pos} {pos' : t.Pos} {h : s = t} :
pos.cast h pos' pos pos'.cast h.symm := by
simp [Pos.le_iff]
theorem Pos.le_cast_iff {s t : String} {pos : t.Pos} {pos' : s.Pos} {h : s = t} :
pos pos'.cast h pos.cast h.symm pos' := by
simp [Pos.le_iff]
theorem Pos.cast_lt_iff {s t : String} {pos : s.Pos} {pos' : t.Pos} {h : s = t} :
pos.cast h < pos' pos < pos'.cast h.symm := by
simp [Pos.lt_iff]
theorem Pos.lt_cast_iff {s t : String} {pos : t.Pos} {pos' : s.Pos} {h : s = t} :
pos < pos'.cast h pos.cast h.symm < pos' := by
simp [Pos.lt_iff]
theorem Pos.copy_toSlice_eq_cast {s : String} (p : s.Pos) :
p.toSlice.copy = p.cast copy_toSlice.symm :=
Pos.ext (by simp)
@@ -2050,6 +2141,10 @@ theorem Pos.le_ofToSlice_iff {s : String} {p : s.Pos} {q : s.toSlice.Pos} :
theorem Pos.toSlice_lt_toSlice_iff {s : String} {p q : s.Pos} :
p.toSlice < q.toSlice p < q := Iff.rfl
@[simp]
theorem Pos.toSlice_le_toSlice_iff {s : String} {p q : s.Pos} :
p.toSlice q.toSlice p q := Iff.rfl
theorem Pos.next_le_of_lt {s : String} {p q : s.Pos} {h} : p < q p.next h q := by
rw [next, Pos.ofToSlice_le_iff, Pos.toSlice_lt_toSlice_iff]
exact Slice.Pos.next_le_of_lt

View File

@@ -363,7 +363,7 @@ theorem toBitVec_eq_of_parseFirstByte_eq_threeMore {b : UInt8} (h : parseFirstBy
public def isInvalidContinuationByte (b : UInt8) : Bool :=
b &&& 0xc0 != 0x80
theorem isInvalidContinutationByte_eq_false_iff {b : UInt8} :
theorem isInvalidContinuationByte_eq_false_iff {b : UInt8} :
isInvalidContinuationByte b = false b &&& 0xc0 = 0x80 := by
simp [isInvalidContinuationByte]

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,37 @@
/-
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.
-/
@[inline]
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

@@ -17,49 +17,7 @@ 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
import Init.Data.Order.Lemmas
public import Init.Data.String.Basic
import Init.Data.Char.Lemmas
import Init.Data.Char.Order
import Init.Data.List.Lex
public section
open Std
namespace String
@[deprecated toList_inj (since := "2025-10-30")]
protected theorem data_eq_of_eq {a b : String} (h : a = b) : a.toList = b.toList :=
h rfl
@[deprecated toList_inj (since := "2025-10-30")]
protected theorem ne_of_data_ne {a b : String} (h : a.toList b.toList) : a b := by
simpa [ toList_inj]
@[simp] protected theorem not_le {a b : String} : ¬ a b b < a := Decidable.not_not
@[simp] protected theorem not_lt {a b : String} : ¬ a < b b a := Iff.rfl
@[simp] protected theorem le_refl (a : String) : a a := List.le_refl _
@[simp] protected theorem lt_irrefl (a : String) : ¬ a < a := List.lt_irrefl _
attribute [local instance] Char.notLTTrans Char.ltTrichotomous Char.ltAsymm
protected theorem le_trans {a b c : String} : a b b c a c := List.le_trans
protected theorem lt_trans {a b c : String} : a < b b < c a < c := List.lt_trans
protected theorem le_total (a b : String) : a b b a := List.le_total _ _
protected theorem le_antisymm {a b : String} : a b b a a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.toList) (bs := b.toList) h₁ h₂)
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
protected theorem ne_of_lt {a b : String} (h : a < b) : a b := by
have := String.lt_irrefl a
intro h; subst h; contradiction
instance instIsLinearOrder : IsLinearOrder String := by
apply IsLinearOrder.of_le
case le_antisymm => constructor; apply String.le_antisymm
case le_trans => constructor; apply String.le_trans
case le_total => constructor; apply String.le_total
instance : LawfulOrderLT String where
lt_iff a b := by
simp [ String.not_le, Decidable.imp_iff_not_or, Std.Total.total]
end String
public import Init.Data.String.Lemmas.Iter
public import Init.Data.String.Lemmas.Hashable
public import Init.Data.String.Lemmas.TakeDrop
public import Init.Data.String.Lemmas.StringOrder

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
@@ -21,6 +22,10 @@ public section
namespace String
@[simp]
theorem singleton_inj {c d : Char} : singleton c = singleton d c = d := by
simp [ toList_inj]
@[simp]
theorem singleton_append_inj : singleton c ++ s = singleton d ++ t c = d s = t := by
simp [ toList_inj]
@@ -56,6 +61,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]
@@ -185,18 +195,74 @@ theorem sliceTo_slice {s : String} {p₁ p₂ h p} :
theorem Slice.sliceFrom_startPos {s : Slice} : s.sliceFrom s.startPos = s := by
ext <;> simp
@[simp]
theorem Slice.sliceFrom_eq_self_iff {s : Slice} {p : s.Pos} : s.sliceFrom p = s p = s.startPos := by
refine ?_, by rintro rfl; simp
rcases s with str, startInclusive, endExclusive, h
simp [sliceFrom, Slice.startPos, String.Pos.ext_iff, Pos.Raw.ext_iff, Slice.Pos.ext_iff]
@[simp]
theorem Slice.sliceTo_endPos {s : Slice} : s.sliceTo s.endPos = s := by
ext <;> simp
@[simp]
theorem Slice.sliceTo_eq_self_iff {s : Slice} {p : s.Pos} : s.sliceTo p = s p = s.endPos := by
refine ?_, by rintro rfl; simp
rcases s with str, startInclusive, endExclusive, h
simp [sliceTo, Slice.endPos, String.Pos.ext_iff, Pos.Raw.ext_iff, Slice.Pos.ext_iff,
utf8ByteSize_eq]
omega
@[simp]
theorem Slice.slice_startPos {s : Slice} {p : s.Pos} :
s.slice s.startPos p (Pos.startPos_le _) = s.sliceTo p := by
ext <;> simp
@[simp]
theorem Slice.slice_eq_self_iff {s : Slice} {p₁ p₂ : s.Pos} {h} :
s.slice p₁ p₂ h = s p₁ = s.startPos p₂ = s.endPos := by
refine ?_, by rintro rfl, rfl; simp
rcases s with str, startInclusive, endExclusive, h
simp [slice, Slice.endPos, String.Pos.ext_iff, Pos.Raw.ext_iff, Slice.Pos.ext_iff,
utf8ByteSize_eq]
omega
@[simp]
theorem Slice.slice_endPos {s : Slice} {p : s.Pos} :
s.slice p s.endPos (Pos.le_endPos _) = s.sliceFrom p := by
ext <;> simp
@[simp]
theorem sliceFrom_startPos {s : String} : s.sliceFrom s.startPos = s := by
ext <;> simp
@[simp]
theorem sliceFrom_eq_toSlice_iff {s : String} {p : s.Pos} : s.sliceFrom p = s.toSlice p = s.startPos := by
simp [ sliceFrom_toSlice]
@[simp]
theorem sliceTo_endPos {s : String} : s.sliceTo s.endPos = s := by
ext <;> simp
@[simp]
theorem sliceTo_eq_toSlice_iff {s : String} {p : s.Pos} : s.sliceTo p = s.toSlice p = s.endPos := by
simp [ sliceTo_toSlice]
@[simp]
theorem slice_startPos {s : String} {p : s.Pos} :
s.slice s.startPos p (Pos.startPos_le _) = s.sliceTo p := by
ext <;> simp
@[simp]
theorem slice_endPos {s : String} {p : s.Pos} :
s.slice p s.endPos (Pos.le_endPos _) = s.sliceFrom p := by
ext <;> simp
@[simp]
theorem slice_eq_toSlice_iff {s : String} {p₁ p₂ : s.Pos} {h} :
s.slice p₁ p₂ h = s.toSlice p₁ = s.startPos p₂ = s.endPos := by
simp [ slice_toSlice]
end Iterate
theorem Slice.copy_eq_copy_slice {s : Slice} {pos₁ pos₂ : s.Pos} {h} :
@@ -244,4 +310,81 @@ 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
@[simp]
theorem Slice.Pos.cast_toSlice_copy {s : Slice} {pos : s.Pos} :
pos.copy.toSlice.cast (by simp) = pos := by
ext; simp
@[simp]
theorem Slice.Pos.sliceFrom_eq_startPos {s : Slice} {p : s.Pos} :
(Pos.sliceFrom p p (Pos.le_refl _)) = Slice.startPos _ := by
simp [ Pos.ofSliceFrom_inj]
@[simp]
theorem Slice.Pos.sliceFrom_endPos {s : Slice} {p : s.Pos} :
(Pos.sliceFrom p s.endPos (Pos.le_endPos _)) = Slice.endPos _ := by
simp [ Pos.ofSliceFrom_inj]
@[simp]
theorem Slice.Pos.sliceTo_startPos {s : Slice} {p : s.Pos} :
(Pos.sliceTo p s.startPos (Pos.startPos_le _)) = Slice.startPos _ := by
simp [ Pos.ofSliceTo_inj]
@[simp]
theorem Slice.Pos.sliceTo_eq_endPos {s : Slice} {p : s.Pos} :
(Pos.sliceTo p p (Pos.le_refl _)) = Slice.endPos _ := by
simp [ Pos.ofSliceTo_inj]
@[simp]
theorem Slice.Pos.slice_eq_startPos {s : Slice} {p₀ p₁ : s.Pos} {h} :
(Pos.slice p₀ p₀ p₁ (Pos.le_refl _) h) = Slice.startPos _ := by
simp [ Pos.ofSlice_inj]
@[simp]
theorem Slice.Pos.slice_eq_endPos {s : Slice} {p₀ p₁ : s.Pos} {h} :
(Pos.slice p₁ p₀ p₁ h (Pos.le_refl _)) = Slice.endPos _ := by
simp [ Pos.ofSlice_inj]
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,32 @@ 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
@[simp]
theorem join_append {l m : List String} : String.join (l ++ m) = String.join l ++ String.join m := by
simp [ toList_inj]
@[simp]
theorem length_join {l : List String} : (String.join l).length = (l.map String.length).sum := by
simp only [ length_toList, toList_join, List.length_flatMap]
simp
namespace Slice
@[simp]
@@ -65,6 +102,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

@@ -31,7 +31,7 @@ namespace Slice
/--
A list of all positions starting at {name}`p`.
This function is not meant to be used in actual progams. Actual programs should use
This function is not meant to be used in actual programs. Actual programs should use
{name}`Slice.positionsFrom` or {name}`Slice.positions`.
-/
protected def Model.positionsFrom {s : Slice} (p : s.Pos) : List { p : s.Pos // p s.endPos } :=
@@ -206,7 +206,7 @@ end Slice
/--
A list of all positions starting at {name}`p`.
This function is not meant to be used in actual progams. Actual programs should use
This function is not meant to be used in actual programs. Actual programs should use
{name}`Slice.positionsFrom` or {name}`Slice.positions`.
-/
protected def Model.positionsFrom {s : String} (p : s.Pos) : List { p : s.Pos // p s.endPos } :=

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,41 @@ 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.ne_endPos_of_sliceTo_ne_endPos {s : Slice} {p p₀ : s.Pos} {h₀}
(h : Pos.sliceTo p₀ p h₀ Slice.endPos _) : p s.endPos := by
rw [ Pos.ofSliceTo_sliceTo (h := h₀)]
apply Pos.ofSliceTo_ne_endPos 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 Slice.Pos.ne_startPos_of_sliceFrom_ne_startPos {s : Slice} {p p₀ : s.Pos} {h₀}
(h : Pos.sliceFrom p₀ p h₀ Slice.startPos _) : p s.startPos := by
rw [ Pos.ofSliceFrom_sliceFrom (h := h₀)]
apply Pos.ofSliceFrom_ne_startPos 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.ne_endPos_of_sliceTo_ne_endPos {s : String} {p p₀ : s.Pos} {h₀}
(h : Pos.sliceTo p₀ p h₀ Slice.endPos _) : p s.endPos := by
rw [ Pos.ofSliceTo_sliceTo (h := h₀)]
apply Pos.ofSliceTo_ne_endPos 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 Pos.ne_startPos_of_sliceFrom_ne_startPos {s : String} {p p₀ : s.Pos} {h₀}
(h : Pos.sliceFrom p₀ p h₀ Slice.startPos _) : p s.startPos := by
rw [ Pos.ofSliceFrom_sliceFrom (h := h₀)]
apply Pos.ofSliceFrom_ne_startPos 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 +445,130 @@ 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.ne_endPos_of_slice_ne_endPos {s : Slice} {p p₀ p₁ : s.Pos} {h₁ h₂}
(h : Pos.slice p p₀ p₁ h₁ h₂ Slice.endPos _) : p s.endPos := by
rw [ Pos.ofSlice_slice (h₁ := h₁) (h₂ := h₂)]
apply Pos.ofSlice_ne_endPos 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 Slice.Pos.ne_startPos_of_slice_ne_startPos {s : Slice} {p p₀ p₁ : s.Pos} {h₁ h₂}
(h : Pos.slice p p₀ p₁ h₁ h₂ Slice.startPos _) : p s.startPos := by
rw [ Pos.ofSlice_slice (h₁ := h₁) (h₂ := h₂)]
apply Pos.ofSlice_ne_startPos 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.ne_endPos_of_slice_ne_endPos {s : String} {p p₀ p₁ : s.Pos} {h₁ h₂}
(h : Pos.slice p p₀ p₁ h₁ h₂ Slice.endPos _) : p s.endPos := by
rw [ Pos.ofSlice_slice (h₁ := h₁) (h₂ := h₂)]
apply Pos.ofSlice_ne_endPos 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
theorem Pos.ne_startPos_of_slice_ne_startPos {s : String} {p p₀ p₁ : s.Pos} {h₁ h₂}
(h : Pos.slice p p₀ p₁ h₁ h₂ Slice.startPos _) : p s.startPos := by
rw [ Pos.ofSlice_slice (h₁ := h₁) (h₂ := h₂)]
apply Pos.ofSlice_ne_startPos h
@[simp]
theorem Slice.Pos.offset_le_rawEndPos {s : Slice} {p : s.Pos} :
p.offset s.rawEndPos :=
@@ -468,21 +621,37 @@ theorem Slice.Pos.get_eq_get_ofSliceTo {s : Slice} {p₀ : s.Pos} {pos : (s.slic
pos.get h = (ofSliceTo pos).get (ofSliceTo_ne_endPos h) := by
simp [Slice.Pos.get]
theorem Slice.Pos.get_sliceTo {s : Slice} {p₀ p : s.Pos} {h h'} :
(Pos.sliceTo p₀ p h).get h' = p.get (ne_endPos_of_sliceTo_ne_endPos h') := by
simp [get_eq_get_ofSliceTo]
theorem Pos.get_eq_get_ofSliceTo {s : String} {p₀ : s.Pos}
{pos : (s.sliceTo p₀).Pos} {h} :
pos.get h = (ofSliceTo pos).get (ofSliceTo_ne_endPos h) := by
simp [Pos.get, Slice.Pos.get]
theorem Pos.get_sliceTo {s : String} {p₀ p : s.Pos} {h h'} :
(Pos.sliceTo p₀ p h).get h' = p.get (ne_endPos_of_sliceTo_ne_endPos h') := by
simp [get_eq_get_ofSliceTo]
theorem Slice.Pos.get_eq_get_ofSlice {s : Slice} {p₀ p₁ : s.Pos} {h}
{pos : (s.slice p₀ p₁ h).Pos} {h'} :
pos.get h' = (ofSlice pos).get (ofSlice_ne_endPos h') := by
simp [Slice.Pos.get, Nat.add_assoc]
theorem Slice.Pos.get_slice {s : Slice} {p p₀ p₁ : s.Pos} {h₁ h₂ h} :
(Pos.slice p p₀ p₁ h₁ h₂).get h = p.get (ne_endPos_of_slice_ne_endPos h) := by
simp [get_eq_get_ofSlice]
theorem Pos.get_eq_get_ofSlice {s : String} {p₀ p₁ : s.Pos} {h}
{pos : (s.slice p₀ p₁ h).Pos} {h'} :
pos.get h' = (ofSlice pos).get (ofSlice_ne_endPos h') := by
simp [Pos.get, Slice.Pos.get]
theorem Pos.get_slice {s : String} {p p₀ p₁ : s.Pos} {h₁ h₂ h} :
(Pos.slice p p₀ p₁ h₁ h₂).get h = p.get (ne_endPos_of_slice_ne_endPos h) := by
simp [get_eq_get_ofSlice]
theorem Slice.Pos.ofSlice_next {s : Slice} {p₀ p₁ : s.Pos} {h}
{p : (s.slice p₀ p₁ h).Pos} {h'} :
Pos.ofSlice (p.next h') = (Pos.ofSlice p).next (ofSlice_ne_endPos h') := by

File diff suppressed because it is too large Load Diff

View File

@@ -20,28 +20,44 @@ 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} : StrictPatternModel c where
not_matches_empty := by simp [PatternModel.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 +68,46 @@ theorem isLongestMatchAt_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
simp +contextual [Model.isLongestMatchAt_iff, isLongestMatch_iff, Pos.ofSliceFrom_inj,
Pos.get_eq_get_ofSliceFrom, Pos.ofSliceFrom_next]
theorem isLongestRevMatch_iff {c : Char} {s : Slice} {pos : s.Pos} :
IsLongestRevMatch c pos
(h : s.endPos s.startPos), pos = s.endPos.prev h (s.endPos.prev h).get (by simp) = c := by
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff]
theorem isLongestRevMatchAt_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAt c pos pos' h, pos = pos'.prev h (pos'.prev h).get (by simp) = c := by
simp +contextual [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff, Pos.ofSliceTo_inj,
Pos.get_eq_get_ofSliceTo, Pos.ofSliceTo_prev]
theorem isLongestMatchAt_of_get_eq {c : Char} {s : Slice} {pos : s.Pos} {h : pos s.endPos}
(hc : pos.get h = c) : IsLongestMatchAt c pos (pos.next h) :=
isLongestMatchAt_iff.2 h, by simp [hc]
theorem isLongestRevMatchAt_of_get_eq {c : Char} {s : Slice} {pos : s.Pos} {h : pos s.startPos}
(hc : (pos.prev h).get (by simp) = c) : IsLongestRevMatchAt c (pos.prev h) pos :=
isLongestRevMatchAt_iff.2 h, by simp [hc]
instance {c : Char} : LawfulForwardPatternModel c where
skipPrefix?_eq_some_iff {s} pos := by
simp [isLongestMatch_iff, ForwardPattern.skipPrefix?, and_comm, eq_comm (b := pos)]
instance {c : Char} : LawfulBackwardPatternModel c where
skipSuffix?_eq_some_iff {s} pos := by
simp [isLongestRevMatch_iff, BackwardPattern.skipSuffix?, and_comm, eq_comm (b := pos)]
theorem toSearcher_eq {c : Char} {s : Slice} :
ToForwardSearcher.toSearcher c s = ToForwardSearcher.toSearcher (· == c) s := (rfl)
theorem toBackwardSearcher_eq {c : Char} {s : Slice} :
ToBackwardSearcher.toSearcher c s = ToBackwardSearcher.toSearcher (· == c) s := (rfl)
theorem matchesAt_iff {c : Char} {s : Slice} {pos : s.Pos} :
MatchesAt c pos (h : pos s.endPos), pos.get h = c := by
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm]
theorem revMatchesAt_iff {c : Char} {s : Slice} {pos : s.Pos} :
RevMatchesAt c pos (h : pos s.startPos), (pos.prev h).get (by simp) = c := by
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm]
theorem matchesAt_iff_splits {c : Char} {s : Slice} {pos : s.Pos} :
MatchesAt c pos t₁ t₂, pos.Splits t₁ (singleton c ++ t₂) := by
rw [matchesAt_iff]
@@ -77,37 +118,131 @@ 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 isLongestMatchAtChain_iff_isLongestMatchAtChain_beq {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestMatchAtChain c pos pos' IsLongestMatchAtChain (· == c) pos pos' := by
refine fun h => ?_, fun h => ?_
· induction h with
| nil => simp
| cons p₁ p₂ p₃ h₁ h₂ ih => exact .cons _ _ _ (isLongestMatchAt_iff_isLongestMatchAt_beq.1 h₁) ih
· induction h with
| nil => simp
| cons p₁ p₂ p₃ h₁ h₂ ih => exact .cons _ _ _ (isLongestMatchAt_iff_isLongestMatchAt_beq.2 h₁) ih
theorem isLongestMatchAtChain_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestMatchAtChain c pos pos' pos pos' pos'', pos pos'' (h : pos'' < pos') pos''.get (Pos.ne_endPos_of_lt h) = c := by
simp [isLongestMatchAtChain_iff_isLongestMatchAtChain_beq, CharPred.isLongestMatchAtChain_iff]
theorem isLongestMatchAtChain_iff_toList {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestMatchAtChain c pos pos'
(h : pos pos'), (s.slice pos pos' h).copy.toList = List.replicate (s.slice pos pos' h).copy.length c := by
simp [isLongestMatchAtChain_iff_isLongestMatchAtChain_beq, CharPred.isLongestMatchAtChain_iff_toList,
List.eq_replicate_iff]
theorem isLongestMatchAtChain_startPos_endPos_iff_toList {c : Char} {s : Slice} :
IsLongestMatchAtChain c s.startPos s.endPos s.copy.toList = List.replicate s.copy.length c := by
simp [isLongestMatchAtChain_iff_isLongestMatchAtChain_beq,
CharPred.isLongestMatchAtChain_startPos_endPos_iff_toList, List.eq_replicate_iff]
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 isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_beq {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAtChain c pos pos' IsLongestRevMatchAtChain (· == c) pos pos' := by
refine fun h => ?_, fun h => ?_
· induction h with
| nil => simp
| cons p₂ p₃ _ hmatch ih => exact .cons _ _ _ ih (isLongestRevMatchAt_iff_isLongestRevMatchAt_beq.1 hmatch)
· induction h with
| nil => simp
| cons p₂ p₃ _ hmatch ih => exact .cons _ _ _ ih (isLongestRevMatchAt_iff_isLongestRevMatchAt_beq.2 hmatch)
theorem isLongestRevMatchAtChain_iff {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAtChain c pos pos' pos pos' pos'', pos pos'' (h : pos'' < pos') pos''.get (Pos.ne_endPos_of_lt h) = c := by
simp [isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_beq, CharPred.isLongestRevMatchAtChain_iff]
theorem isLongestRevMatchAtChain_iff_toList {c : Char} {s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAtChain c pos pos'
(h : pos pos'), (s.slice pos pos' h).copy.toList = List.replicate (s.slice pos pos' h).copy.length c := by
simp [isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_beq, CharPred.isLongestRevMatchAtChain_iff_toList,
List.eq_replicate_iff]
theorem isLongestRevMatchAtChain_startPos_endPos_iff_toList {c : Char} {s : Slice} :
IsLongestRevMatchAtChain c s.startPos s.endPos s.copy.toList = List.replicate s.copy.length c := by
simp [isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_beq,
CharPred.isLongestRevMatchAtChain_startPos_endPos_iff_toList, List.eq_replicate_iff]
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 +255,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} :
@@ -142,18 +294,21 @@ theorem skipPrefix?_char_eq_skipPrefix?_beq {c : Char} {s : Slice} :
theorem Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq {c : Char} {s : Slice} :
skipPrefix? c s = skipPrefix? (· == c) s := (rfl)
theorem Pos.skip?_char_eq_skip?_beq {c : Char} {s : Slice} {pos : s.Pos} :
pos.skip? c = pos.skip? (· == c) := (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 [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq, h₁, h₂, ih]
simp [ Pos.skip?_char_eq_skip?_beq, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq, h, ih]
simp [ Pos.skip?_char_eq_skip?_beq, h, ih]
| case3 pos h =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_char_eq_skipPrefix?_beq]
simp [ Pos.skip?_char_eq_skip?_beq, h]
theorem skipPrefixWhile_char_eq_skipPrefixWhile_beq {c : Char} {s : Slice} :
s.skipPrefixWhile c = s.skipPrefixWhile (· == c) :=
@@ -169,7 +324,7 @@ theorem takeWhile_char_eq_takeWhile_beq {c : Char} {s : Slice} :
theorem all_char_eq_all_beq {c : Char} {s : Slice} :
s.all c = s.all (· == c) := by
simp only [all, dropWhile_char_eq_dropWhile_beq]
simp only [all, skipPrefixWhile_char_eq_skipPrefixWhile_beq]
theorem find?_char_eq_find?_beq {c : Char} {s : Slice} :
s.find? c = s.find? (· == c) :=
@@ -198,18 +353,21 @@ theorem dropSuffix_char_eq_dropSuffix_beq {c : Char} {s : Slice} :
theorem Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq {c : Char} {s : Slice} :
skipSuffix? c s = skipSuffix? (· == c) s := (rfl)
theorem Pos.revSkip?_char_eq_revSkip?_beq {c : Char} {s : Slice} {pos : s.Pos} :
pos.revSkip? c = pos.revSkip? (· == c) := (rfl)
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 [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq, h₁, h₂, ih]
simp [ Pos.revSkip?_char_eq_revSkip?_beq, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq, h, ih]
simp [ Pos.revSkip?_char_eq_revSkip?_beq, h, ih]
| case3 pos h =>
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_char_eq_skipSuffix?_beq]
simp [ Pos.revSkip?_char_eq_revSkip?_beq, h]
theorem skipSuffixWhile_char_eq_skipSuffixWhile_beq {c : Char} {s : Slice} :
s.skipSuffixWhile c = s.skipSuffixWhile (· == c) :=
@@ -223,4 +381,16 @@ theorem takeEndWhile_char_eq_takeEndWhile_beq {c : Char} {s : Slice} :
s.takeEndWhile c = s.takeEndWhile (· == c) := by
simp only [takeEndWhile]; exact congrArg _ skipSuffixWhile_char_eq_skipSuffixWhile_beq
theorem revFind?_char_eq_revFind?_beq {c : Char} {s : Slice} :
s.revFind? c = s.revFind? (· == c) :=
(rfl)
theorem Pos.revFind?_char_eq_revFind?_beq {c : Char} {s : Slice} {p : s.Pos} :
p.revFind? c = p.revFind? (· == c) :=
(rfl)
theorem revAll_char_eq_revAll_beq {c : Char} {s : Slice} :
s.revAll c = s.revAll (· == c) := by
simp [revAll, skipSuffixWhile_char_eq_skipSuffixWhile_beq]
end String.Slice

View File

@@ -23,8 +23,8 @@ open Std String.Slice Pattern Pattern.Model
namespace String.Slice
theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {σ : Slice Type}
[ s, Iterator (σ s) Id (SearchStep s)] [ s, Iterators.Finite (σ s) Id]
theorem Pattern.Model.find?_eq_some_iff {ρ : Type} (pat : ρ) [PatternModel pat] [StrictPatternModel 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} :
s.find? pat = some pos MatchesAt pat pos ( pos', pos' < pos ¬ MatchesAt pat pos') := by
@@ -40,8 +40,8 @@ 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}
[ s, Iterator (σ s) Id (SearchStep s)] [ s, Iterators.Finite (σ s) Id]
theorem Pattern.Model.find?_eq_none_iff {ρ : Type} (pat : ρ) [PatternModel pat] [StrictPatternModel 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.find? pat = none (pos : s.Pos), ¬ MatchesAt pat pos := by
@@ -65,15 +65,15 @@ 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}
[ s, Iterator (σ s) Id (SearchStep s)] [ s, Iterators.Finite (σ s) Id]
theorem Pattern.Model.contains_eq_false_iff {ρ : Type} (pat : ρ) [PatternModel pat] [StrictPatternModel 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}
[ s, Iterator (σ s) Id (SearchStep s)] [ s, Iterators.Finite (σ s) Id]
theorem Pattern.Model.contains_eq_true_iff {ρ : Type} (pat : ρ) [PatternModel pat] [StrictPatternModel 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 (pos : s.Pos), MatchesAt pat pos := by
@@ -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] [StrictPatternModel 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,8 +100,8 @@ 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}
[ s, Iterator (σ s) Id (SearchStep s)] [ s, Iterators.Finite (σ s) Id]
theorem Pattern.Model.posFind?_eq_none_iff {ρ : Type} {pat : ρ} [PatternModel pat] [StrictPatternModel 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} :
pos.find? pat = none pos', pos pos' ¬ MatchesAt pat pos' := by

View File

@@ -49,9 +49,10 @@ theorem contains_slice_iff {t s : Slice} :
by_cases ht : t.isEmpty
· simp [contains_eq_true_of_isEmpty ht s, copy_eq_empty_iff.mpr ht, String.toList_empty]
· simp only [Bool.not_eq_true] at ht
have := Pattern.Model.ForwardSliceSearcher.strictPatternModel ht
have := Pattern.Model.ForwardSliceSearcher.lawfulToForwardSearcherModel ht
simp only [Pattern.Model.contains_eq_true_iff,
Pattern.Model.ForwardSliceSearcher.exists_matchesAt_iff_eq_append ht, isInfix_toList_iff]
Pattern.Model.ForwardSliceSearcher.exists_matchesAt_iff_eq_append, isInfix_toList_iff]
@[simp]
theorem contains_string_iff {t : String} {s : Slice} :

View File

@@ -18,125 +18,321 @@ import Init.Data.String.Lemmas.Basic
import Init.Data.String.Lemmas.Order
import Init.Data.Order.Lemmas
import Init.Data.String.OrderInstances
import Init.Data.String.Lemmas.Iterate
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} : StrictPatternModel p where
not_matches_empty := by simp [PatternModel.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 isLongestMatchAtChain_iff {p : Char Bool} {s : Slice} {pos pos' : s.Pos} :
IsLongestMatchAtChain p pos pos' pos pos' pos'', pos pos'' (h : pos'' < pos') p (pos''.get (Pos.ne_endPos_of_lt h)) := by
induction pos using WellFounded.induction Pos.wellFounded_gt with | h pos ih
obtain (h|rfl|h) := Std.lt_trichotomy pos pos'
· refine fun h => ?_, fun h₁, h₂ => ?_
· cases h with
| nil => exact (Std.lt_irrefl h).elim
| cons _ mid _ h₁ h₂ =>
obtain h₀, rfl, h₁' := isLongestMatchAt_iff.1 h₁
refine Std.le_of_lt h, fun pos'' hp₁ hp₂ => ?_
obtain (hh|rfl) := Std.le_iff_lt_or_eq.1 hp₁
· exact ((ih (pos.next (Pos.ne_endPos_of_lt h)) Pos.lt_next).1 h₂).2 _ (by simpa) hp₂
· exact h₁'
· refine .cons _ (pos.next (Pos.ne_endPos_of_lt h)) _ ?_ ((ih _ Pos.lt_next).2 ?_)
· exact isLongestMatchAt_iff.2 Pos.ne_endPos_of_lt h, rfl, h₂ _ (by simp) h
· exact by simpa, fun pos'' hp₁ hp₂ => h₂ _ (Std.le_trans Pos.le_next hp₁) hp₂
· simpa using fun _ h₁ h₂ => (Std.lt_irrefl (Std.lt_of_le_of_lt h₁ h₂)).elim
· simpa [Std.not_le.2 h] using fun h' => (Std.not_le.2 h h'.le).elim
theorem isLongestMatchAtChain_iff_toList {p : Char Bool} {s : Slice} {pos pos' : s.Pos} :
IsLongestMatchAtChain p pos pos' (h : pos pos'), c, c (s.slice pos pos' h).copy.toList p c := by
simp only [isLongestMatchAtChain_iff, mem_toList_copy_iff_exists_get, Pos.get_eq_get_ofSlice,
forall_exists_index]
refine fun h₁, h₂ => h₁, fun c p' hp => ?_, fun h₁, h₂ => h₁, fun p' hp₁ hp₂ => ?_
· rintro rfl
exact h₂ _ Pos.le_ofSlice (by simp [Pos.ofSlice_lt_iff, h₁, hp])
· refine h₂ _ (Pos.slice p' _ _ hp₁ (Std.le_of_lt hp₂)) ?_ (by simp)
rwa [ Pos.lt_endPos_iff, Pos.slice_eq_endPos (h := h₁), Pos.slice_lt_slice_iff]
theorem isLongestMatchAtChain_startPos_endPos_iff_toList {p : Char Bool} {s : Slice} :
IsLongestMatchAtChain p s.startPos s.endPos c, c s.copy.toList p c := by
simp [isLongestMatchAtChain_iff_toList]
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]
theorem isLongestRevMatchAtChain_iff {p : Char Bool} {s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAtChain p pos pos' pos pos' pos'', pos pos'' (h : pos'' < pos') p (pos''.get (Pos.ne_endPos_of_lt h)) := by
induction pos' using WellFounded.induction Pos.wellFounded_lt with | h pos' ih
obtain (h|rfl|h) := Std.lt_trichotomy pos pos'
· refine fun h => ?_, fun h₁, h₂ => ?_
· cases h with
| nil => exact (Std.lt_irrefl h).elim
| cons _ _ hchain hmatch =>
obtain hne, hmid, hp := isLongestRevMatchAt_iff.1 hmatch
refine Std.le_of_lt h, fun pos'' hp₁ hp₂ => ?_
rcases Std.le_iff_lt_or_eq.1 (Pos.le_prev_iff_lt.2 hp₂) with hh | heq
· exact ((ih _ Pos.prev_lt).1 (hmid hchain)).2 _ hp₁ hh
· exact heq hp
· have hne : pos' s.startPos := Slice.Pos.ne_startPos_of_lt h
refine .cons _ (pos'.prev hne) _ ((ih _ Pos.prev_lt).2 ?_)
(isLongestRevMatchAt_of_get (h₂ _ (Pos.le_prev_iff_lt.2 h) Pos.prev_lt))
exact Pos.le_prev_iff_lt.2 h, fun pos'' hp₁ hp₂ =>
h₂ _ hp₁ (Std.lt_trans hp₂ Pos.prev_lt)
· simpa using fun _ h₁ h₂ => (Std.lt_irrefl (Std.lt_of_le_of_lt h₁ h₂)).elim
· simpa [Std.not_le.2 h] using fun h' => (Std.not_le.2 h h'.le).elim
theorem isLongestRevMatchAtChain_iff_toList {p : Char Bool} {s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAtChain p pos pos' (h : pos pos'), c, c (s.slice pos pos' h).copy.toList p c :=
isLongestRevMatchAtChain_iff.trans (isLongestMatchAtChain_iff.symm.trans isLongestMatchAtChain_iff_toList)
theorem isLongestRevMatchAtChain_startPos_endPos_iff_toList {p : Char Bool} {s : Slice} :
IsLongestRevMatchAtChain p s.startPos s.endPos c, c s.copy.toList p c := by
simp [isLongestRevMatchAtChain_iff_toList]
instance {p : Char Bool} : LawfulForwardPatternModel p where
skipPrefix?_eq_some_iff {s} pos := by
simp [isLongestMatch_iff, ForwardPattern.skipPrefix?, and_comm, eq_comm (b := pos)]
instance {p : Char Bool} : LawfulBackwardPatternModel p where
skipSuffix?_eq_some_iff {s} pos := by
simp [isLongestRevMatch_iff, BackwardPattern.skipSuffix?, and_comm, eq_comm (b := pos)]
instance {p : Char Bool} : LawfulToForwardSearcherModel p :=
.defaultImplementation
instance {p : Char Bool} : LawfulToBackwardSearcherModel p :=
.defaultImplementation
theorem matchesAt_iff {p : Char Bool} {s : Slice} {pos : s.Pos} :
MatchesAt p pos (h : pos s.endPos), p (pos.get h) := by
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff, exists_comm]
theorem revMatchesAt_iff {p : Char Bool} {s : Slice} {pos : s.Pos} :
RevMatchesAt p pos (h : pos s.startPos), p ((pos.prev h).get (by simp)) := by
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff, exists_comm]
theorem not_matchesAt_of_get {p : Char Bool} {s : Slice} {pos : s.Pos} {h : pos s.endPos}
(hc : p (pos.get h) = false) : ¬ MatchesAt p pos := by
simp [matchesAt_iff, hc]
theorem not_revMatchesAt_of_get {p : Char Bool} {s : Slice} {pos : s.Pos} {h : pos s.startPos}
(hc : p ((pos.prev h).get (by simp)) = false) : ¬ RevMatchesAt p pos := by
simp [revMatchesAt_iff, hc]
theorem matchAt?_eq {s : Slice} {pos : s.Pos} {p : Char Bool} :
matchAt? p pos =
if h₀ : (h : pos s.endPos), p (pos.get h) then some (pos.next h₀.1) else none := by
split <;> simp_all [isLongestMatchAt_iff, matchesAt_iff]
theorem revMatchAt?_eq {s : Slice} {pos : s.Pos} {p : Char Bool} :
revMatchAt? p pos =
if h₀ : (h : pos s.startPos), p ((pos.prev h).get (by simp)) then some (pos.prev h₀.1) else none := by
split <;> simp_all [isLongestRevMatchAt_iff, revMatchesAt_iff]
namespace Decidable
instance {p : Char Prop} [DecidablePred p] : ForwardPatternModel p where
Matches := ForwardPatternModel.Matches (decide <| p ·)
not_matches_empty := ForwardPatternModel.not_matches_empty (pat := (decide <| p ·))
instance {p : Char Prop} [DecidablePred p] : PatternModel p where
Matches := PatternModel.Matches (decide <| p ·)
instance {p : Char Prop} [DecidablePred p] : NoPrefixForwardPatternModel p where
eq_empty := NoPrefixForwardPatternModel.eq_empty (pat := (decide <| p ·))
instance {p : Char Prop} [DecidablePred p] : StrictPatternModel p where
not_matches_empty := StrictPatternModel.not_matches_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 isLongestMatchAtChain_iff_isLongestMatchAtChain_decide {p : Char Prop} [DecidablePred p]
{s : Slice} {pos pos' : s.Pos} :
IsLongestMatchAtChain p pos pos' IsLongestMatchAtChain (decide <| p ·) pos pos' := by
constructor
· intro h; induction h with
| nil => exact .nil _
| cons _ mid _ hmatch hchain ih =>
exact .cons _ mid _ (isLongestMatchAt_iff_isLongestMatchAt_decide.1 hmatch) ih
· intro h; induction h with
| nil => exact .nil _
| cons _ mid _ hmatch hchain ih =>
exact .cons _ mid _ (isLongestMatchAt_iff_isLongestMatchAt_decide.2 hmatch) ih
theorem isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_decide {p : Char Prop} [DecidablePred p]
{s : Slice} {pos pos' : s.Pos} :
IsLongestRevMatchAtChain p pos pos' IsLongestRevMatchAtChain (decide <| p ·) pos pos' := by
constructor
· intro h; induction h with
| nil => exact .nil _
| cons _ _ hchain hmatch ih =>
exact .cons _ _ _ ih (isLongestRevMatchAt_iff_isLongestRevMatchAt_decide.1 hmatch)
· intro h; induction h with
| nil => exact .nil _
| cons _ _ hchain hmatch ih =>
exact .cons _ _ _ ih (isLongestRevMatchAt_iff_isLongestRevMatchAt_decide.2 hmatch)
theorem isLongestMatchAt_iff {p : Char Prop} [DecidablePred p] {s : Slice}
{pos pos' : s.Pos} :
IsLongestMatchAt p pos pos' h, pos' = pos.next h p (pos.get h) := by
simp [isLongestMatchAt_iff_isLongestMatchAt_decide, CharPred.isLongestMatchAt_iff]
theorem isLongestRevMatchAt_iff {p : Char Prop} [DecidablePred p] {s : Slice}
{pos pos' : s.Pos} :
IsLongestRevMatchAt p pos pos' h, pos = pos'.prev h p ((pos'.prev h).get (by simp)) := by
simp [isLongestRevMatchAt_iff_isLongestRevMatchAt_decide, CharPred.isLongestRevMatchAt_iff]
theorem isLongestMatchAt_of_get {p : Char Prop} [DecidablePred p] {s : Slice} {pos : s.Pos}
{h : pos s.endPos} (hc : p (pos.get h)) : IsLongestMatchAt p pos (pos.next h) :=
isLongestMatchAt_iff.2 h, by simp [hc]
theorem isLongestRevMatchAt_of_get {p : Char Prop} [DecidablePred p] {s : Slice} {pos : s.Pos}
{h : pos s.startPos} (hc : p ((pos.prev h).get (by simp))) :
IsLongestRevMatchAt p (pos.prev h) pos :=
isLongestRevMatchAt_iff.2 h, by simp [hc]
theorem matchesAt_iff_matchesAt_decide {p : Char Prop} [DecidablePred p] {s : Slice}
{pos : s.Pos} : MatchesAt p pos MatchesAt (decide <| p ·) pos := by
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_decide]
theorem revMatchesAt_iff_revMatchesAt_decide {p : Char Prop} [DecidablePred p] {s : Slice}
{pos : s.Pos} : RevMatchesAt p pos RevMatchesAt (decide <| p ·) pos := by
simp [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_isLongestRevMatchAt_decide]
theorem matchAt?_eq_matchAt?_decide {p : Char Prop} [DecidablePred p] {s : Slice}
{pos : s.Pos} : matchAt? p pos = matchAt? (decide <| p ·) pos := by
ext endPos
simp [isLongestMatchAt_iff_isLongestMatchAt_decide]
theorem revMatchAt?_eq_revMatchAt?_decide {p : Char Prop} [DecidablePred p] {s : Slice}
{pos : s.Pos} : revMatchAt? p pos = revMatchAt? (decide <| p ·) pos := by
ext startPos
simp [isLongestRevMatchAt_iff_isLongestRevMatchAt_decide]
theorem skipPrefix?_eq_skipPrefix?_decide {p : Char Prop} [DecidablePred p] :
ForwardPattern.skipPrefix? p = ForwardPattern.skipPrefix? (decide <| p ·) := rfl
theorem skipSuffix?_eq_skipSuffix?_decide {p : Char Prop} [DecidablePred p] :
BackwardPattern.skipSuffix? p = BackwardPattern.skipSuffix? (decide <| p ·) := rfl
instance {p : Char Prop} [DecidablePred p] : LawfulForwardPatternModel p where
skipPrefix?_eq_some_iff {s} pos := by
rw [skipPrefix?_eq_skipPrefix?_decide, isLongestMatch_iff_isLongestMatch_decide]
exact LawfulForwardPatternModel.skipPrefix?_eq_some_iff ..
instance {p : Char Prop} [DecidablePred p] : LawfulBackwardPatternModel p where
skipSuffix?_eq_some_iff {s} pos := by
rw [skipSuffix?_eq_skipSuffix?_decide, isLongestRevMatch_iff_isLongestRevMatch_decide]
exact LawfulBackwardPatternModel.skipSuffix?_eq_some_iff ..
theorem toSearcher_eq {p : Char Prop} [DecidablePred p] {s : Slice} :
ToForwardSearcher.toSearcher p s = ToForwardSearcher.toSearcher (decide <| p ·) s := (rfl)
theorem toBackwardSearcher_eq {p : Char Prop} [DecidablePred p] {s : Slice} :
ToBackwardSearcher.toSearcher p s = ToBackwardSearcher.toSearcher (decide <| p ·) s := (rfl)
theorem isValidSearchFrom_iff_isValidSearchFrom_decide {p : Char Prop} [DecidablePred p]
{s : Slice} {pos : s.Pos} {l : List (SearchStep s)} :
IsValidSearchFrom p pos l IsValidSearchFrom (decide <| p ·) pos l := by
@@ -150,24 +346,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
@@ -184,6 +411,9 @@ theorem dropPrefix_prop_eq_dropPrefix_decide {p : Char → Prop} [DecidablePred
theorem skipPrefix?_prop_eq_skipPrefix?_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.skipPrefix? p = s.skipPrefix? (decide <| p ·) := (rfl)
theorem Pos.skip?_prop_eq_skip?_decide {p : Char Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
pos.skip? p = pos.skip? (decide <| p ·) := (rfl)
theorem Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide
{p : Char Prop} [DecidablePred p] {s : Slice} :
skipPrefix? p s = skipPrefix? (decide <| p ·) s := (rfl)
@@ -194,13 +424,13 @@ theorem Pos.skipWhile_prop_eq_skipWhile_decide {p : Char → Prop} [DecidablePre
fun_induction Pos.skipWhile curr p with
| case1 pos nextCurr h₁ h₂ ih =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide, h₁, h₂, ih]
simp [ Pos.skip?_prop_eq_skip?_decide, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide, h, ih]
simp [ Pos.skip?_prop_eq_skip?_decide, h, ih]
| case3 pos h =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_prop_eq_skipPrefix?_decide]
simp [ Pos.skip?_prop_eq_skip?_decide, h]
theorem skipPrefixWhile_prop_eq_skipPrefixWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} :
@@ -217,7 +447,7 @@ theorem takeWhile_prop_eq_takeWhile_decide {p : Char → Prop} [DecidablePred p]
theorem all_prop_eq_all_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.all p = s.all (decide <| p ·) := by
simp only [all, dropWhile_prop_eq_dropWhile_decide]
simp only [all, skipPrefixWhile_prop_eq_skipPrefixWhile_decide]
theorem find?_prop_eq_find?_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.find? p = s.find? (decide <| p ·) :=
@@ -248,19 +478,22 @@ theorem Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide
{p : Char Prop} [DecidablePred p] {s : Slice} :
skipSuffix? p s = skipSuffix? (decide <| p ·) s := (rfl)
theorem Pos.revSkip?_prop_eq_revSkip?_decide {p : Char Prop} [DecidablePred p] {s : Slice} {pos : s.Pos} :
pos.revSkip? p = pos.revSkip? (decide <| p ·) := (rfl)
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 [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide, h₁, h₂, ih]
simp [ Pos.revSkip?_prop_eq_revSkip?_decide, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide, h, ih]
simp [ Pos.revSkip?_prop_eq_revSkip?_decide, h, ih]
| case3 pos h =>
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_prop_eq_skipSuffix?_decide]
simp [ Pos.revSkip?_prop_eq_revSkip?_decide, h]
theorem skipSuffixWhile_prop_eq_skipSuffixWhile_decide {p : Char Prop} [DecidablePred p]
{s : Slice} :
@@ -277,4 +510,8 @@ theorem takeEndWhile_prop_eq_takeEndWhile_decide {p : Char → Prop} [DecidableP
s.takeEndWhile p = s.takeEndWhile (decide <| p ·) := by
simp only [takeEndWhile]; exact congrArg _ skipSuffixWhile_prop_eq_skipSuffixWhile_decide
theorem revAll_prop_eq_revAll_decide {p : Char Prop} [DecidablePred p] {s : Slice} :
s.revAll p = s.revAll (decide <| p ·) := by
simp only [revAll, skipSuffixWhile_prop_eq_skipSuffixWhile_decide]
end String.Slice

View File

@@ -28,7 +28,7 @@ set_option doc.verso true
# Verification of {name}`String.Slice.splitToSubslice`
This PR verifies the {name}`String.Slice.splitToSubslice` function by relating it to a model
implementation based on the {name}`String.Slice.Pattern.Model.ForwardPatternModel` class.
implementation based on the {name}`String.Slice.Pattern.Model.PatternModel` class.
This gives a low-level correctness proof from which higher-level API lemmas can be derived.
-/
@@ -36,7 +36,7 @@ This gives a low-level correctness proof from which higher-level API lemmas can
namespace String.Slice.Pattern.Model
@[cbv_opaque]
public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternModel pat] {s : Slice}
public protected noncomputable def split {ρ : Type} (pat : ρ) [PatternModel pat] [StrictPatternModel pat] {s : Slice}
(firstRejected curr : s.Pos) (hle : firstRejected curr) : List s.Subslice :=
if h : curr = s.endPos then
[s.subslice _ _ hle]
@@ -49,12 +49,12 @@ public protected noncomputable def split {ρ : Type} (pat : ρ) [ForwardPatternM
termination_by curr
@[simp]
public theorem split_endPos {ρ : Type} {pat : ρ} [ForwardPatternModel pat] {s : Slice}
public theorem split_endPos {ρ : Type} {pat : ρ} [PatternModel pat] [StrictPatternModel 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] [StrictPatternModel pat]
{s : Slice} {firstRejected start stop : s.Pos} {hle} (h : IsLongestMatchAt pat start stop) :
Model.split pat firstRejected start hle =
s.subslice _ _ hle :: Model.split pat stop stop (by exact Std.le_refl _) := by
@@ -63,7 +63,7 @@ public theorem split_eq_of_isLongestMatchAt {ρ : Type} {pat : ρ} [ForwardPatte
· congr <;> exact (matchAt?_eq_some_iff.1 _).eq h
· simp [matchAt?_eq_some_iff.2 _] at *
public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [PatternModel pat] [StrictPatternModel pat]
{s : Slice} {firstRejected start} (stop : s.Pos) (h₀ : start stop) {hle}
(h : p, start p p < stop ¬ MatchesAt pat p) :
Model.split pat firstRejected start hle =
@@ -80,7 +80,7 @@ public theorem split_eq_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternM
· obtain rfl : start = stop := Std.le_antisymm h₀ (Std.not_lt.1 h')
simp
public theorem split_eq_next_of_not_matchesAt {ρ : Type} {pat : ρ} [ForwardPatternModel pat]
public theorem split_eq_next_of_not_matchesAt {ρ : Type} {pat : ρ} [PatternModel pat] [StrictPatternModel pat]
{s : Slice} {firstRejected start} {hle} (hs : start s.endPos) (h : ¬ MatchesAt pat start) :
Model.split pat firstRejected start hle =
Model.split pat firstRejected (start.next hs) (by exact Std.le_trans hle (by simp)) := by
@@ -103,7 +103,7 @@ def splitFromSteps {s : Slice} (currPos : s.Pos) (l : List (SearchStep s)) : Lis
| .matched p q :: l => s.subslice! currPos p :: splitFromSteps q l
theorem IsValidSearchFrom.splitFromSteps_eq_extend_split {ρ : Type} (pat : ρ)
[ForwardPatternModel pat] (l : List (SearchStep s)) (pos pos' : s.Pos) (h₀ : pos pos')
[PatternModel pat] [StrictPatternModel pat] (l : List (SearchStep s)) (pos pos' : s.Pos) (h₀ : pos pos')
(h' : p, pos p p < pos' ¬ MatchesAt pat p)
(h : IsValidSearchFrom pat pos' l) :
splitFromSteps pos l = Model.split pat pos pos' h₀ := by
@@ -155,7 +155,7 @@ end Model
open Model
@[cbv_eval]
public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [ForwardPatternModel pat]
public theorem toList_splitToSubslice_eq_modelSplit {ρ : Type} (pat : ρ) [PatternModel pat] [StrictPatternModel pat]
{σ : Slice Type} [ToForwardSearcher pat σ] [ s, Std.Iterator (σ s) Id (SearchStep s)]
[ s, Std.Iterators.Finite (σ s) Id] [LawfulToForwardSearcherModel pat] (s : Slice) :
(s.splitToSubslice pat).toList = Model.split pat s.startPos s.startPos (by exact Std.le_refl _) := by
@@ -168,7 +168,7 @@ end Pattern
open Pattern
public theorem toList_splitToSubslice_of_isEmpty {ρ : Type} (pat : ρ)
[Model.ForwardPatternModel pat] {σ : Slice Type}
[Model.PatternModel pat] [Model.StrictPatternModel pat] {σ : Slice Type}
[ToForwardSearcher pat σ] [ s, Std.Iterator (σ s) Id (SearchStep s)]
[ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] {s : Slice}
(h : s.isEmpty = true) :
@@ -182,7 +182,7 @@ public theorem toList_split_eq_splitToSubslice {ρ : Type} (pat : ρ) {σ : Slic
simp [split, Std.Iter.toList_map]
public theorem toList_split_of_isEmpty {ρ : Type} (pat : ρ)
[Model.ForwardPatternModel pat] {σ : Slice Type}
[Model.PatternModel pat] [Model.StrictPatternModel pat] {σ : Slice Type}
[ToForwardSearcher pat σ] [ s, Std.Iterator (σ s) Id (SearchStep s)]
[ s, Std.Iterators.Finite (σ s) Id] [Model.LawfulToForwardSearcherModel pat] {s : Slice}
(h : s.isEmpty = true) :
@@ -200,7 +200,7 @@ public theorem split_eq_split_toSlice {ρ : Type} {pat : ρ} {σ : Slice → Typ
@[simp]
public theorem toList_split_empty {ρ : Type} (pat : ρ)
[Model.ForwardPatternModel pat] {σ : Slice Type}
[Model.PatternModel pat] [Model.StrictPatternModel 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
@@ -70,6 +71,11 @@ theorem Slice.toList_split_intercalate {c : Char} {l : List Slice} (hl : ∀ s
· simp_all
· rw [List.splitOn_intercalate] <;> simp_all
theorem Slice.toList_split_intercalate_beq {c : Char} {l : List Slice} (hl : s l, c s.copy.toList) :
((Slice.intercalate (String.singleton c) l).split c).toList ==
if l = [] then ["".toSlice] else l := by
split <;> simp_all [toList_split_intercalate hl, beq_list_iff]
theorem toList_split_intercalate {c : Char} {l : List String} (hl : s l, c s.toList) :
((String.intercalate (String.singleton c) l).split c).toList.map (·.copy) =
if l = [] then [""] else l := by
@@ -78,4 +84,9 @@ theorem toList_split_intercalate {c : Char} {l : List String} (hl : ∀ s ∈ l,
· simp_all
· rw [List.splitOn_intercalate] <;> simp_all
theorem toList_split_intercalate_beq {c : Char} {l : List String} (hl : s l, c s.toList) :
((String.intercalate (String.singleton c) l).split c).toList ==
if l = [] then ["".toSlice] else l.map String.toSlice := by
split <;> simp_all [toList_split_intercalate hl, Slice.beq_list_iff]
end String

View File

@@ -10,6 +10,9 @@ public import Init.Data.String.Pattern.String
public import Init.Data.String.Lemmas.Pattern.Basic
import Init.Data.String.Lemmas.IsEmpty
import Init.Data.String.Lemmas.Basic
import Init.Data.String.Lemmas.Intercalate
import Init.Data.String.OrderInstances
import Init.Data.String.Lemmas.Splits
import Init.Data.ByteArray.Lemmas
import Init.Omega
@@ -19,52 +22,135 @@ namespace String.Slice.Pattern.Model
namespace ForwardSliceSearcher
instance {pat : Slice} : ForwardPatternModel pat where
/-
See the docstring of `ForwardPatternModel` for an explanation about why we disallow matching the
empty string.
instance {pat : Slice} : PatternModel pat where
Matches s := s = pat.copy
Requiring `s ≠ ""` is a trick that allows us to give a `ForwardPatternModel` 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`.
-/
Matches s := s "" s = pat.copy
not_matches_empty := by simp
theorem strictPatternModel {pat : Slice} (hpat : pat.isEmpty = false) : StrictPatternModel pat where
not_matches_empty := by simpa [PatternModel.Matches]
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])
theorem isMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
instance {pat : Slice} : NoSuffixPatternModel pat :=
.of_length_eq (by simp +contextual [PatternModel.Matches])
theorem isMatch_iff {pat s : Slice} {pos : s.Pos} :
IsMatch pat pos (s.sliceTo pos).copy = pat.copy := by
simp only [Model.isMatch_iff, ForwardPatternModel.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]
simp [Model.isMatch_iff, PatternModel.Matches]
theorem isLongestMatch_iff {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
theorem isRevMatch_iff {pat s : Slice} {pos : s.Pos} :
IsRevMatch pat pos (s.sliceFrom pos).copy = pat.copy := by
simp [Model.isRevMatch_iff, PatternModel.Matches]
theorem isLongestMatch_iff {pat s : Slice} {pos : s.Pos} :
IsLongestMatch pat pos (s.sliceTo pos).copy = pat.copy := by
rw [isLongestMatch_iff_isMatch, isMatch_iff h]
rw [isLongestMatch_iff_isMatch, isMatch_iff]
theorem isLongestMatchAt_iff {pat s : Slice} {pos pos₂ : s.Pos} (h : pat.isEmpty = false) :
theorem isLongestRevMatch_iff {pat s : Slice} {pos : s.Pos} :
IsLongestRevMatch pat pos (s.sliceFrom pos).copy = pat.copy := by
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff]
theorem isLongestMatchAt_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestMatchAt pat pos₁ pos₂ h, (s.slice pos₁ pos₂ h).copy = pat.copy := by
simp [Model.isLongestMatchAt_iff, isLongestMatch_iff h]
simp [Model.isLongestMatchAt_iff, isLongestMatch_iff]
theorem isLongestMatchAt_iff_splits {pat s : Slice} {pos₁ pos₂ : s.Pos} (h : pat.isEmpty = false) :
theorem isLongestMatchAtChain_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestMatchAtChain pat pos₁ pos₂
h n, (s.slice pos₁ pos₂ h).copy = String.join (List.replicate n pat.copy) := by
refine fun h => h.le, ?_, fun h, n, h' => ?_
· induction h with
| nil => simpa using 0, by simp
| cons p₁ p₂ p₃ h₁ h₂ ih =>
rw [isLongestMatchAt_iff] at h₁
obtain n, ih := ih
obtain h₀, h₁ := h₁
have : (s.slice p₁ p₃ (Std.le_trans h₀ h₂.le)).copy = (s.slice p₁ p₂ h₀).copy ++ (s.slice p₂ p₃ h₂.le).copy := by
simp [(Slice.Pos.slice p₂ _ _ h₀ h₂.le).splits.eq_append]
refine n + 1, ?_
rw [this, h₁, ih]
simp [ String.join_cons, List.replicate_succ]
· induction n generalizing pos₁ pos₂ with
| zero => simp_all
| succ n ih =>
rw [List.replicate_succ, String.join_cons] at h'
refine .cons _ (Pos.ofSlice (Pos.ofEqAppend h')) _ ?_ (ih ?_ Pos.ofSlice_le ?_)
· simpa [isLongestMatchAt_iff] using (Pos.splits_ofEqAppend h').copy_sliceTo_eq
· simpa [sliceFrom_slice (Pos.splits_ofEqAppend h').copy_sliceFrom_eq] using n, rfl
· simpa using (Pos.splits_ofEqAppend h').copy_sliceFrom_eq
theorem isLongestMatchAtChain_startPos_endPos_iff {pat s : Slice} :
IsLongestMatchAtChain pat s.startPos s.endPos
n, s.copy = String.join (List.replicate n pat.copy) := by
simp [isLongestMatchAtChain_iff]
theorem isLongestRevMatchAt_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestRevMatchAt pat pos₁ pos₂ h, (s.slice pos₁ pos₂ h).copy = pat.copy := by
simp [Model.isLongestRevMatchAt_iff, isLongestRevMatch_iff]
theorem isLongestRevMatchAtChain_iff {pat s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestRevMatchAtChain pat pos₁ pos₂
h n, (s.slice pos₁ pos₂ h).copy = String.join (List.replicate n pat.copy) := by
refine fun h => h.le, ?_, fun h, n, h' => ?_
· induction h with
| nil => simpa using 0, by simp
| cons p₂ p₃ h₁ h₂ ih =>
rw [isLongestRevMatchAt_iff] at h₂
obtain n, ih := ih
obtain h₀, h₂ := h₂
have : (s.slice pos₁ p₃ (Std.le_trans h₁.le h₀)).copy = (s.slice pos₁ p₂ h₁.le).copy ++ (s.slice p₂ p₃ h₀).copy := by
simp [(Slice.Pos.slice p₂ _ _ (IsLongestRevMatchAtChain.le _) h₀).splits.eq_append]
refine n + 1, ?_
rw [this, h₂, ih]
simp [ List.replicate_append_replicate]
· induction n generalizing pos₁ pos₂ with
| zero => simp_all
| succ n ih =>
have h'' : (s.slice pos₁ pos₂ h).copy = String.join (List.replicate n pat.copy) ++ pat.copy := by
rw [h', List.replicate_succ', String.join_append]; simp
refine .cons _ (Pos.ofSlice (Pos.ofEqAppend h'')) _ (ih ?_ Pos.le_ofSlice ?_) ?_
· simpa [sliceTo_slice (Pos.splits_ofEqAppend h'').copy_sliceTo_eq] using n, rfl
· simpa using (Pos.splits_ofEqAppend h'').copy_sliceTo_eq
· simpa [isLongestRevMatchAt_iff] using (Pos.splits_ofEqAppend h'').copy_sliceFrom_eq
theorem isLongestRevMatchAtChain_startPos_endPos_iff {pat s : Slice} :
IsLongestRevMatchAtChain pat s.startPos s.endPos
n, s.copy = String.join (List.replicate n pat.copy) := by
simp [isLongestRevMatchAtChain_iff]
theorem isLongestMatchAt_iff_splits {pat s : Slice} {pos₁ pos₂ : s.Pos} :
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]
simp only [isLongestMatchAt_iff, copy_slice_eq_iff_splits]
theorem isLongestMatch_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
theorem isLongestRevMatchAt_iff_splits {pat s : Slice} {pos pos₂ : s.Pos} :
IsLongestRevMatchAt pat pos₁ pos₂ t₁ t₂, pos₁.Splits t₁ (pat.copy ++ t₂)
pos₂.Splits (t₁ ++ pat.copy) t₂ := by
simp only [isLongestRevMatchAt_iff, copy_slice_eq_iff_splits]
theorem isLongestMatch_iff_splits {pat s : Slice} {pos : s.Pos} :
IsLongestMatch pat pos t, pos.Splits pat.copy t := by
simp only [ isLongestMatchAt_startPos_iff, isLongestMatchAt_iff_splits h, splits_startPos_iff,
and_assoc, exists_and_left, exists_eq_left, empty_append]
exact fun h, _, h' => h, h', fun h, h' => h, h'.eq_append.symm, h'
rw [isLongestMatch_iff, copy_sliceTo_eq_iff_exists_splits]
theorem isLongestRevMatch_iff_splits {pat s : Slice} {pos : s.Pos} :
IsLongestRevMatch pat pos t, pos.Splits t pat.copy := by
rw [isLongestRevMatch_iff, 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
rw [isLongestMatchAt_iff h]
rw [isLongestMatchAt_iff]
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 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]
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
@@ -81,15 +167,32 @@ 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 matchesAt_iff_splits {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
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} :
MatchesAt pat pos t₁ t₂, pos.Splits t₁ (pat.copy ++ t₂) := by
simp only [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_splits h]
simp only [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_splits]
exact fun e, t₁, t₂, ht₁, ht₂ => t₁, t₂, ht₁,
fun t₁, t₂, ht => ht.rotateRight, t₁, t₂, ht, ht.splits_rotateRight
theorem exists_matchesAt_iff_eq_append {pat s : Slice} (h : pat.isEmpty = false) :
theorem revMatchesAt_iff_splits {pat s : Slice} {pos : s.Pos} :
RevMatchesAt pat pos t₁ t₂, pos.Splits (t₁ ++ pat.copy) t₂ := by
simp only [revMatchesAt_iff_exists_isLongestRevMatchAt, isLongestRevMatchAt_iff_splits]
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} :
( (pos : s.Pos), MatchesAt pat pos) t₁ t₂, s.copy = t₁ ++ pat.copy ++ t₂ := by
simp only [matchesAt_iff_splits h]
simp only [matchesAt_iff_splits]
constructor
· rintro pos, t₁, t₂, hsplit
exact t₁, t₂, by rw [hsplit.eq_append, append_assoc]
@@ -99,6 +202,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} :
( (pos : s.Pos), RevMatchesAt pat pos) t₁ t₂, s.copy = t₁ ++ pat.copy ++ t₂ := by
simp only [revMatchesAt_iff_splits]
constructor
· rintro pos, t₁, t₂, hsplit
exact t₁, t₂, by rw [hsplit.eq_append, append_assoc]
· rintro t₁, t₂, heq
have hvalid : (t₁ ++ pat.copy).rawEndPos.IsValidForSlice s :=
Pos.Raw.isValidForSlice_iff_exists_append.mpr
t₁ ++ pat.copy, t₂, heq, rfl
exact s.pos _ hvalid, t₁, t₂, heq, by simp
theorem matchesAt_iff_isLongestMatchAt {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
MatchesAt pat pos (h : (pos.offset.increaseBy pat.utf8ByteSize).IsValidForSlice s),
IsLongestMatchAt pat pos (s.pos _ h) := by
@@ -108,6 +223,25 @@ theorem matchesAt_iff_isLongestMatchAt {pat s : Slice} {pos : s.Pos} (h : pat.is
obtain rfl : p = s.pos _ this := by simpa [Pos.ext_iff] using offset_of_isLongestMatchAt h h'
exact h'
theorem revMatchesAt_iff_isLongestRevMatchAt {pat s : Slice} {pos : s.Pos}
(h : pat.isEmpty = false) :
RevMatchesAt pat pos
(h : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s),
IsLongestRevMatchAt pat (s.pos _ h) pos := by
refine fun p, h' => ?_, fun _, h => _, h
have hoff := offset_of_isLongestRevMatchAt h h'
have hvalid : (pos.offset.decreaseBy pat.utf8ByteSize).IsValidForSlice s := by
rw [show pos.offset.decreaseBy pat.utf8ByteSize = p.offset from by
simp [Pos.Raw.ext_iff, Pos.Raw.byteIdx_decreaseBy, Pos.Raw.byteIdx_increaseBy] at hoff
omega]
exact p.isValidForSlice
refine hvalid, ?_
obtain rfl : p = s.pos _ hvalid := by
simp only [Pos.ext_iff, offset_pos]
simp [Pos.Raw.ext_iff, Pos.Raw.byteIdx_decreaseBy, Pos.Raw.byteIdx_increaseBy] at hoff
omega
exact h'
theorem matchesAt_iff_getElem {pat s : Slice} {pos : s.Pos} (h : pat.isEmpty = false) :
MatchesAt pat pos
(h : pos.offset.byteIdx + pat.copy.toByteArray.size s.copy.toByteArray.size),
@@ -146,81 +280,194 @@ end ForwardSliceSearcher
namespace ForwardStringSearcher
instance {pat : String} : ForwardPatternModel pat where
Matches s := s "" s = pat
not_matches_empty := by simp
instance {pat : String} : PatternModel pat where
Matches s := s = pat
instance {pat : String} : NoPrefixForwardPatternModel pat :=
.of_length_eq (by simp +contextual [ForwardPatternModel.Matches])
theorem strictPatternModel {pat : String} (h : pat "") : StrictPatternModel pat where
not_matches_empty := by simpa [PatternModel.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 isLongestMatchAtChain_iff_isLongestMatchAtChain_toSlice {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestMatchAtChain pat pos₁ pos₂
IsLongestMatchAtChain pat.toSlice pos₁ pos₂ := by
refine fun h => ?_, fun h => ?_
· induction h with
| nil => simp
| cons p₁ p₂ p₃ h₁ h₂ ih =>
exact .cons _ _ _ (isLongestMatchAt_iff_isLongestMatchAt_toSlice.1 h₁) ih
· induction h with
| nil => simp
| cons p₁ p₂ p₃ h₁ h₂ ih =>
exact .cons _ _ _ (isLongestMatchAt_iff_isLongestMatchAt_toSlice.2 h₁) ih
theorem isLongestMatchAtChain_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestMatchAtChain pat pos₁ pos₂
h n, (s.slice pos₁ pos₂ h).copy = String.join (List.replicate n pat) := by
simp [isLongestMatchAtChain_iff_isLongestMatchAtChain_toSlice,
ForwardSliceSearcher.isLongestMatchAtChain_iff]
theorem isLongestMatchAtChain_startPos_endPos_iff {pat : String} {s : Slice} :
IsLongestMatchAtChain pat s.startPos s.endPos
n, s.copy = String.join (List.replicate n pat) := by
simp [isLongestMatchAtChain_iff]
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 isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_toSlice {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestRevMatchAtChain pat pos₁ pos₂
IsLongestRevMatchAtChain pat.toSlice pos₁ pos₂ := by
refine fun h => ?_, fun h => ?_
· induction h with
| nil => simp
| cons p₂ p₃ _ hmatch ih =>
exact .cons _ _ _ ih (isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice.1 hmatch)
· induction h with
| nil => simp
| cons p₂ p₃ _ hmatch ih =>
exact .cons _ _ _ ih (isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice.2 hmatch)
theorem isLongestRevMatchAtChain_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestRevMatchAtChain pat pos₁ pos₂
h n, (s.slice pos₁ pos₂ h).copy = String.join (List.replicate n pat) := by
simp [isLongestRevMatchAtChain_iff_isLongestRevMatchAtChain_toSlice,
ForwardSliceSearcher.isLongestRevMatchAtChain_iff]
theorem isLongestRevMatchAtChain_startPos_endPos_iff {pat : String} {s : Slice} :
IsLongestRevMatchAtChain pat s.startPos s.endPos
n, s.copy = String.join (List.replicate n pat) := by
simp [isLongestRevMatchAtChain_iff]
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]
private theorem toSlice_isEmpty (h : pat "") : pat.toSlice.isEmpty = false := by
rwa [isEmpty_toSlice, isEmpty_eq_false_iff]
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]
theorem isMatch_iff {pat : String} {s : Slice} {pos : s.Pos} (h : pat "") :
theorem isMatch_iff {pat : String} {s : Slice} {pos : s.Pos} :
IsMatch pat pos (s.sliceTo pos).copy = pat := by
rw [isMatch_iff_slice, ForwardSliceSearcher.isMatch_iff (toSlice_isEmpty h)]
rw [isMatch_iff_slice, ForwardSliceSearcher.isMatch_iff]
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 isRevMatch_iff {pat : String} {s : Slice} {pos : s.Pos} :
IsRevMatch pat pos (s.sliceFrom pos).copy = pat := by
rw [isRevMatch_iff_slice, ForwardSliceSearcher.isRevMatch_iff]
simp
theorem isLongestMatchAt_iff {pat : String} {s : Slice} {pos pos₂ : s.Pos} (h : pat "") :
theorem isLongestMatch_iff {pat : String} {s : Slice} {pos : s.Pos} :
IsLongestMatch pat pos (s.sliceTo pos).copy = pat := by
rw [isLongestMatch_iff_isMatch, isMatch_iff]
theorem isLongestRevMatch_iff {pat : String} {s : Slice} {pos : s.Pos} :
IsLongestRevMatch pat pos (s.sliceFrom pos).copy = pat := by
rw [isLongestRevMatch_iff_isRevMatch, isRevMatch_iff]
theorem isLongestMatchAt_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestMatchAt pat pos₁ pos₂ h, (s.slice pos₁ pos₂ h).copy = pat := by
rw [isLongestMatchAt_iff_isLongestMatchAt_toSlice,
ForwardSliceSearcher.isLongestMatchAt_iff (toSlice_isEmpty h)]
ForwardSliceSearcher.isLongestMatchAt_iff]
simp
theorem isLongestMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
(h : pat "") :
theorem isLongestRevMatchAt_iff {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestRevMatchAt pat pos₁ pos₂ h, (s.slice pos₁ pos₂ h).copy = pat := by
rw [isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice,
ForwardSliceSearcher.isLongestRevMatchAt_iff]
simp
theorem isLongestMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
IsLongestMatchAt pat pos₁ pos₂
t₁ t₂, pos₁.Splits t₁ (pat ++ t₂) pos₂.Splits (t₁ ++ pat) t₂ := by
rw [isLongestMatchAt_iff_isLongestMatchAt_toSlice,
ForwardSliceSearcher.isLongestMatchAt_iff_splits (toSlice_isEmpty h)]
ForwardSliceSearcher.isLongestMatchAt_iff_splits]
simp
theorem isLongestMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos}
(h : pat "") :
theorem isLongestRevMatchAt_iff_splits {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} :
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]
simp
theorem isLongestMatchAt_iff_extract {pat : String} {s : Slice} {pos₁ pos₂ : s.Pos} (h : pat "") :
IsLongestMatchAt pat pos₁ pos₂
s.copy.toByteArray.extract pos₁.offset.byteIdx pos₂.offset.byteIdx = pat.toByteArray := by
rw [isLongestMatchAt_iff_isLongestMatchAt_toSlice,
ForwardSliceSearcher.isLongestMatchAt_iff_extract (toSlice_isEmpty h)]
ForwardSliceSearcher.isLongestMatchAt_iff_extract (by simpa)]
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 (by simpa)]
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
rw [show pat.utf8ByteSize = pat.toSlice.utf8ByteSize from utf8ByteSize_toSlice.symm]
exact ForwardSliceSearcher.offset_of_isLongestMatchAt (toSlice_isEmpty h)
exact ForwardSliceSearcher.offset_of_isLongestMatchAt (by simpa)
(isLongestMatchAt_iff_isLongestMatchAt_toSlice.1 h')
theorem matchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} (h : pat "") :
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 (by simpa)
(isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice.1 h')
theorem matchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} :
MatchesAt pat pos t₁ t₂, pos.Splits t₁ (pat ++ t₂) := by
rw [matchesAt_iff_toSlice,
ForwardSliceSearcher.matchesAt_iff_splits (toSlice_isEmpty h)]
ForwardSliceSearcher.matchesAt_iff_splits]
simp
theorem exists_matchesAt_iff_eq_append {pat : String} {s : Slice} (h : pat "") :
theorem revMatchesAt_iff_splits {pat : String} {s : Slice} {pos : s.Pos} :
RevMatchesAt pat pos t₁ t₂, pos.Splits (t₁ ++ pat) t₂ := by
rw [revMatchesAt_iff_toSlice,
ForwardSliceSearcher.revMatchesAt_iff_splits]
simp
theorem exists_matchesAt_iff_eq_append {pat : String} {s : Slice} :
( (pos : s.Pos), MatchesAt pat pos) t₁ t₂, s.copy = t₁ ++ pat ++ t₂ := by
simp only [matchesAt_iff_splits h]
simp only [matchesAt_iff_splits]
constructor
· rintro pos, t₁, t₂, hsplit
exact t₁, t₂, by rw [hsplit.eq_append, append_assoc]
@@ -230,35 +477,58 @@ 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} :
( (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]
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),
IsLongestMatchAt pat pos (s.pos _ h) := by
have key := ForwardSliceSearcher.matchesAt_iff_isLongestMatchAt (pat := pat.toSlice)
(toSlice_isEmpty h) (pos := pos)
(by simpa) (pos := 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)
(by simpa) (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),
(j), (hj : j < pat.toByteArray.size)
pat.toByteArray[j] = s.copy.toByteArray[pos.offset.byteIdx + j] := by
have key := ForwardSliceSearcher.matchesAt_iff_getElem (pat := pat.toSlice)
(toSlice_isEmpty h) (pos := pos)
(by simpa) (pos := pos)
simp only [copy_toSlice] at key
rwa [matchesAt_iff_toSlice]
theorem le_of_matchesAt {pat : String} {s : Slice} {pos : s.Pos} (h : pat "")
(h' : MatchesAt pat pos) : pos.offset.increaseBy pat.utf8ByteSize s.rawEndPos := by
rw [show pat.utf8ByteSize = pat.toSlice.utf8ByteSize from utf8ByteSize_toSlice.symm]
exact ForwardSliceSearcher.le_of_matchesAt (toSlice_isEmpty h)
exact ForwardSliceSearcher.le_of_matchesAt (by simpa)
(matchesAt_iff_toSlice.1 h')
theorem matchesAt_iff_matchesAt_toSlice {pat : String} {s : Slice}
{pos : s.Pos} : MatchesAt pat pos MatchesAt pat.toSlice pos := by
simp [matchesAt_iff_exists_isLongestMatchAt, isLongestMatchAt_iff_isLongestMatchAt_toSlice]
theorem revMatchesAt_iff_revMatchesAt_toSlice {pat : String} {s : Slice}
{pos : s.Pos} : RevMatchesAt pat pos RevMatchesAt pat.toSlice pos := by
simp [revMatchesAt_iff_exists_isLongestRevMatchAt,
isLongestRevMatchAt_iff_isLongestRevMatchAt_toSlice]
theorem toSearcher_eq {pat : String} {s : Slice} :
ToForwardSearcher.toSearcher pat s = ToForwardSearcher.toSearcher pat.toSlice s := (rfl)
@@ -275,6 +545,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

@@ -56,7 +56,7 @@ theorem skipPrefix?_eq_some_iff {pat s : Slice} {pos : s.Pos} :
simp only [reduceCtorEq, false_iff]
intro heq
have := h (s.sliceFrom pos).copy
simp [ heq, pos.splits.eq_append] at this
simp [ heq, -sliceTo_append_sliceFrom, pos.splits.eq_append] at this
theorem isSome_skipPrefix? {pat s : Slice} : (skipPrefix? pat s).isSome = startsWith pat s := by
fun_cases skipPrefix? <;> simp_all
@@ -76,12 +76,11 @@ namespace Model.ForwardSliceSearcher
open Pattern.ForwardSliceSearcher
public theorem lawfulForwardPatternModel {pat : Slice} (hpat : pat.isEmpty = false) :
LawfulForwardPatternModel pat where
skipPrefixOfNonempty?_eq h := rfl
startsWith_eq s := isSome_skipPrefix?.symm
public instance {pat : Slice} : LawfulForwardPatternModel pat where
skipPrefixOfNonempty?_eq _ := rfl
startsWith_eq _ := isSome_skipPrefix?.symm
skipPrefix?_eq_some_iff pos := by
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat]
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff]
end Model.ForwardSliceSearcher
@@ -89,15 +88,107 @@ namespace Model.ForwardStringSearcher
open Pattern.ForwardSliceSearcher
public theorem lawfulForwardPatternModel {pat : String} (hpat : pat "") :
LawfulForwardPatternModel pat where
skipPrefixOfNonempty?_eq h := rfl
startsWith_eq s := isSome_skipPrefix?.symm
public instance {pat : String} : LawfulForwardPatternModel pat where
skipPrefixOfNonempty?_eq _ := rfl
startsWith_eq _ := isSome_skipPrefix?.symm
skipPrefix?_eq_some_iff pos := by
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff hpat]
simp [ForwardPattern.skipPrefix?, skipPrefix?_eq_some_iff, isLongestMatch_iff]
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, -sliceTo_append_sliceFrom, 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} : LawfulBackwardPatternModel pat where
skipSuffixOfNonempty?_eq _ := rfl
endsWith_eq _ := isSome_skipSuffix?.symm
skipSuffix?_eq_some_iff pos := by
simp [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff,
ForwardSliceSearcher.isLongestRevMatch_iff]
end Model.BackwardSliceSearcher
namespace Model.BackwardStringSearcher
open Pattern.BackwardSliceSearcher
public instance {pat : String} : LawfulBackwardPatternModel pat where
skipSuffixOfNonempty?_eq _ := rfl
endsWith_eq _ := isSome_skipSuffix?.symm
skipSuffix?_eq_some_iff pos := by
simp [BackwardPattern.skipSuffix?, skipSuffix?_eq_some_iff,
ForwardStringSearcher.isLongestRevMatch_iff]
end Model.BackwardStringSearcher
end Pattern
public theorem startsWith_string_eq_startsWith_toSlice {pat : String} {s : Slice} :
@@ -116,19 +207,22 @@ public theorem Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice
{pat : String} {s : Slice} :
skipPrefix? pat s = skipPrefix? pat.toSlice s := (rfl)
public theorem Pos.skip?_string_eq_skip?_toSlice {pat : String} {s : Slice} {pos : s.Pos} :
pos.skip? pat = pos.skip? pat.toSlice := (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 [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice, h₁, h₂, ih]
simp [ Pos.skip?_string_eq_skip?_toSlice, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice, h, ih]
simp [ Pos.skip?_string_eq_skip?_toSlice, h, ih]
| case3 pos h =>
conv => rhs; rw [Pos.skipWhile]
simp [ Pattern.ForwardPattern.skipPrefix?_string_eq_skipPrefix?_toSlice]
simp [ Pos.skip?_string_eq_skip?_toSlice, h]
public theorem skipPrefixWhile_string_eq_skipPrefixWhile_toSlice {pat : String} {s : Slice} :
s.skipPrefixWhile pat = s.skipPrefixWhile pat.toSlice :=
@@ -144,7 +238,7 @@ public theorem takeWhile_string_eq_takeWhile_toSlice {pat : String} {s : Slice}
public theorem all_string_eq_all_toSlice {pat : String} {s : Slice} :
s.all pat = s.all pat.toSlice := by
simp only [all, dropWhile_string_eq_dropWhile_toSlice]
simp only [all, skipPrefixWhile_string_eq_skipPrefixWhile_toSlice]
public theorem endsWith_string_eq_endsWith_toSlice {pat : String} {s : Slice} :
s.endsWith pat = s.endsWith pat.toSlice := (rfl)
@@ -162,19 +256,22 @@ public theorem Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice
{pat : String} {s : Slice} :
skipSuffix? pat s = skipSuffix? pat.toSlice s := (rfl)
public theorem Pos.revSkip?_string_eq_revSkip?_toSlice {pat : String} {s : Slice} {pos : s.Pos} :
pos.revSkip? pat = pos.revSkip? pat.toSlice := (rfl)
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 [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice, h₁, h₂, ih]
simp [ Pos.revSkip?_string_eq_revSkip?_toSlice, h₁, h₂, ih]
| case2 pos nextCurr h ih =>
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice, h, ih]
simp [ Pos.revSkip?_string_eq_revSkip?_toSlice, h, ih]
| case3 pos h =>
conv => rhs; rw [Pos.revSkipWhile]
simp [ Pattern.BackwardPattern.skipSuffix?_string_eq_skipSuffix?_toSlice]
simp [ Pos.revSkip?_string_eq_revSkip?_toSlice, h]
public theorem skipSuffixWhile_string_eq_skipSuffixWhile_toSlice {pat : String} {s : Slice} :
s.skipSuffixWhile pat = s.skipSuffixWhile pat.toSlice :=
@@ -188,4 +285,8 @@ public theorem takeEndWhile_string_eq_takeEndWhile_toSlice {pat : String} {s : S
s.takeEndWhile pat = s.takeEndWhile pat.toSlice := by
simp only [takeEndWhile]; exact congrArg _ skipSuffixWhile_string_eq_skipSuffixWhile_toSlice
public theorem revAll_string_eq_revAll_toSlice {pat : String} {s : Slice} :
s.revAll pat = s.revAll pat.toSlice := by
simp [revAll, skipSuffixWhile_string_eq_skipSuffixWhile_toSlice]
end String.Slice

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,8 @@ public import Init.Data.String.TakeDrop
import Init.Data.String.Lemmas.Pattern.TakeDrop.Basic
import Init.Data.String.Lemmas.Pattern.Char
import Init.Data.Option.Lemmas
import Init.Data.String.Lemmas.FindPos
import Init.Data.List.Sublist
public section
@@ -52,7 +54,113 @@ theorem startsWith_char_eq_false_iff_forall_append {c : Char} {s : Slice} :
theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s res : Slice} (h : s.dropPrefix? c = some res) :
s.copy = singleton c ++ res.copy := by
simpa [ForwardPatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
theorem Pos.skip?_char_eq_some_iff {c : Char} {s : Slice} {pos res : s.Pos} :
pos.skip? c = some res h, res = pos.next h pos.get h = c := by
simp [Pattern.Model.Pos.skip?_eq_some_iff, Char.isLongestMatchAt_iff]
@[simp]
theorem Pos.skip?_char_eq_none_iff {c : Char} {s : Slice} {pos : s.Pos} :
pos.skip? c = none h, pos.get h c := by
simp [Pattern.Model.Pos.skip?_eq_none_iff, Char.matchesAt_iff]
theorem Pos.get_skipWhile_char_ne {c : Char} {s : Slice} {pos : s.Pos} {h} :
(pos.skipWhile c).get h c := by
have := Pattern.Model.Pos.not_matchesAt_skipWhile c pos
simp_all [Char.matchesAt_iff]
theorem Pos.skipWhile_char_eq_self_iff_get {c : Char} {s : Slice} {pos : s.Pos} :
pos.skipWhile c = pos h, pos.get h c := by
simp [Pattern.Model.Pos.skipWhile_eq_self_iff, Char.matchesAt_iff]
theorem Pos.get_eq_of_lt_skipWhile_char {c : Char} {s : Slice} {pos pos' : s.Pos}
(h₁ : pos pos') (h₂ : pos' < pos.skipWhile c) : pos'.get (ne_endPos_of_lt h₂) = c :=
(Char.isLongestMatchAtChain_iff.1 (Pattern.Model.Pos.isLongestMatchAtChain_skipWhile c pos)).2 _ h₁ h₂
theorem get_skipPrefixWhile_char_ne {c : Char} {s : Slice} {h} :
(s.skipPrefixWhile c).get h c := by
simp [skipPrefixWhile_eq_skipWhile_startPos, Pos.get_skipWhile_char_ne]
theorem get_eq_of_lt_skipPrefixWhile_char {c : Char} {s : Slice} {pos : s.Pos} (h : pos < s.skipPrefixWhile c) :
pos.get (Pos.ne_endPos_of_lt h) = c :=
Pos.get_eq_of_lt_skipWhile_char (Pos.startPos_le _) (by rwa [skipPrefixWhile_eq_skipWhile_startPos] at h)
@[simp]
theorem all_char_iff {c : Char} {s : Slice} : s.all c s.copy.toList = List.replicate s.copy.length c := by
rw [Bool.eq_iff_iff]
simp [Pattern.Model.all_eq_true_iff, Char.isLongestMatchAtChain_startPos_endPos_iff_toList]
theorem Pos.revSkip?_char_eq_some_iff {c : Char} {s : Slice} {pos res : s.Pos} :
pos.revSkip? c = some res h, res = pos.prev h (pos.prev h).get (by simp) = c := by
simp [Pattern.Model.Pos.revSkip?_eq_some_iff, Char.isLongestRevMatchAt_iff]
@[simp]
theorem Pos.revSkip?_char_eq_none_iff {c : Char} {s : Slice} {pos : s.Pos} :
pos.revSkip? c = none h, (pos.prev h).get (by simp) c := by
simp [Pattern.Model.Pos.revSkip?_eq_none_iff, Char.revMatchesAt_iff]
theorem Pos.get_revSkipWhile_char_ne {c : Char} {s : Slice} {pos : s.Pos} {h} :
((pos.revSkipWhile c).prev h).get (by simp) c := by
have := Pattern.Model.Pos.not_revMatchesAt_revSkipWhile c pos
simp_all [Char.revMatchesAt_iff]
theorem Pos.revSkipWhile_char_eq_self_iff_get {c : Char} {s : Slice} {pos : s.Pos} :
pos.revSkipWhile c = pos h, (pos.prev h).get (by simp) c := by
simp [Pattern.Model.Pos.revSkipWhile_eq_self_iff, Char.revMatchesAt_iff]
theorem Pos.get_eq_of_revSkipWhile_le_char {c : Char} {s : Slice} {pos pos' : s.Pos}
(h₁ : pos' < pos) (h₂ : pos.revSkipWhile c pos') : pos'.get (Pos.ne_endPos_of_lt h₁) = c :=
(Char.isLongestRevMatchAtChain_iff.1 (Pattern.Model.Pos.isLongestRevMatchAtChain_revSkipWhile c pos)).2 _ h₂ h₁
theorem get_skipSuffixWhile_char_ne {c : Char} {s : Slice} {h} :
((s.skipSuffixWhile c).prev h).get (by simp) c := by
simp [skipSuffixWhile_eq_revSkipWhile_endPos, Pos.get_revSkipWhile_char_ne]
theorem get_eq_of_skipSuffixWhile_le_char {c : Char} {s : Slice} {pos : s.Pos}
(h : s.skipSuffixWhile c pos) (h' : pos < s.endPos) :
pos.get (Pos.ne_endPos_of_lt h') = c :=
Pos.get_eq_of_revSkipWhile_le_char h' (by rwa [skipSuffixWhile_eq_revSkipWhile_endPos] at h)
@[simp]
theorem revAll_char_iff {c : Char} {s : Slice} : s.revAll c s.copy.toList = List.replicate s.copy.length c := by
rw [Bool.eq_iff_iff]
simp [Pattern.Model.revAll_eq_true_iff, Char.isLongestRevMatchAtChain_startPos_endPos_iff_toList]
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
@@ -63,19 +171,19 @@ theorem skipPrefix?_char_eq_some_iff {c : Char} {s : String} {pos : s.Pos} :
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]
simp [ 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]
simp [ 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?]
simp [ 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]
simp [ 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
@@ -86,4 +194,34 @@ theorem eq_append_of_dropPrefix?_char_eq_some {c : Char} {s : String} {res : Sli
rw [dropPrefix?_eq_dropPrefix?_toSlice] at h
simpa using Slice.eq_append_of_dropPrefix?_char_eq_some h
theorem skipSuffix?_char_eq_some_iff {c : Char} {s : String} {pos : s.Pos} :
s.skipSuffix? c = some pos h, pos = s.endPos.prev h (s.endPos.prev h).get (by simp) = c := by
simp [skipSuffix?_eq_skipSuffix?_toSlice, Slice.skipSuffix?_char_eq_some_iff, Pos.toSlice_inj,
Pos.prev_toSlice]
theorem endsWith_char_iff_get {c : Char} {s : String} :
s.endsWith c h, (s.endPos.prev h).get (by simp) = c := by
simp [ endsWith_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_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_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_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

@@ -8,10 +8,16 @@ module
prelude
public import Init.Data.String.Slice
public import Init.Data.String.TakeDrop
public import Init.Data.String.Lemmas.Order
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.Data.String.Lemmas.Intercalate
import Init.ByCases
import Init.Data.Order.Lemmas
import Init.Data.String.OrderInstances
import Init.Data.String.Lemmas.Basic
public section
@@ -45,9 +51,83 @@ theorem startsWith_bool_eq_head? {p : Char → Bool} {s : Slice} :
theorem eq_append_of_dropPrefix?_bool_eq_some {p : Char Bool} {s res : Slice} (h : s.dropPrefix? p = some res) :
c, s.copy = singleton c ++ res.copy p c = true := by
obtain _, c, rfl, h₁, h₂ := by simpa [ForwardPatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
obtain _, c, rfl, h₁, h₂ := by simpa [PatternModel.Matches] using Pattern.Model.eq_append_of_dropPrefix?_eq_some h
exact _, h₂, h₁
@[simp]
theorem Pos.skip?_bool_eq_some_iff {p : Char Bool} {s : Slice} {pos res : s.Pos} :
pos.skip? p = some res h, res = pos.next h p (pos.get h) := by
simp [Pattern.Model.Pos.skip?_eq_some_iff, CharPred.isLongestMatchAt_iff]
@[simp]
theorem Pos.skip?_bool_eq_none_iff {p : Char Bool} {s : Slice} {pos : s.Pos} :
pos.skip? p = none h, p (pos.get h) = false := by
simp [Pattern.Model.Pos.skip?_eq_none_iff, CharPred.matchesAt_iff]
theorem Pos.apply_skipWhile_bool_eq_false {p : Char Bool} {s : Slice} {pos : s.Pos} {h} :
p ((pos.skipWhile p).get h) = false := by
have := Pattern.Model.Pos.not_matchesAt_skipWhile p pos
simp_all [CharPred.matchesAt_iff]
theorem Pos.skipWhile_bool_eq_self_iff_get {p : Char Bool} {s : Slice} {pos : s.Pos} :
pos.skipWhile p = pos h, p (pos.get h) = false := by
simp [Pattern.Model.Pos.skipWhile_eq_self_iff, CharPred.matchesAt_iff]
theorem Pos.apply_eq_true_of_lt_skipWhile_bool {p : Char Bool} {s : Slice} {pos pos' : s.Pos}
(h₁ : pos pos') (h₂ : pos' < pos.skipWhile p) : p (pos'.get (ne_endPos_of_lt h₂)) = true :=
(CharPred.isLongestMatchAtChain_iff.1 (Pattern.Model.Pos.isLongestMatchAtChain_skipWhile p pos)).2 _ h₁ h₂
theorem apply_skipPrefixWhile_bool_eq_false {p : Char Bool} {s : Slice} {h} :
p ((s.skipPrefixWhile p).get h) = false := by
simp [skipPrefixWhile_eq_skipWhile_startPos, Pos.apply_skipWhile_bool_eq_false]
theorem apply_eq_true_of_lt_skipPrefixWhile_bool {p : Char Bool} {s : Slice} {pos : s.Pos} (h : pos < s.skipPrefixWhile p) :
p (pos.get (Pos.ne_endPos_of_lt h)) = true :=
Pos.apply_eq_true_of_lt_skipWhile_bool (Pos.startPos_le _) (skipPrefixWhile_eq_skipWhile_startPos h)
@[simp]
theorem all_bool_eq {p : Char Bool} {s : Slice} : s.all p = s.copy.toList.all p := by
rw [Bool.eq_iff_iff, Pattern.Model.all_eq_true_iff,
CharPred.isLongestMatchAtChain_startPos_endPos_iff_toList, List.all_eq_true]
@[simp]
theorem Pos.skip?_prop_eq_some_iff {P : Char Prop} [DecidablePred P] {s : Slice} {pos res : s.Pos} :
pos.skip? P = some res h, res = pos.next h P (pos.get h) := by
simp [Pos.skip?_prop_eq_skip?_decide, skip?_bool_eq_some_iff]
@[simp]
theorem Pos.skip?_prop_eq_none_iff {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} :
pos.skip? P = none h, ¬ P (pos.get h) := by
simp [Pos.skip?_prop_eq_skip?_decide, skip?_bool_eq_none_iff]
theorem Pos.apply_skipWhile_prop {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} {h} :
¬ P ((pos.skipWhile P).get h) := by
have := Pattern.Model.Pos.not_matchesAt_skipWhile P pos
simp_all [CharPred.Decidable.matchesAt_iff]
theorem Pos.skipWhile_prop_eq_self_iff_get {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} :
pos.skipWhile P = pos h, ¬ P (pos.get h) := by
simp [Pos.skipWhile_prop_eq_skipWhile_decide, skipWhile_bool_eq_self_iff_get]
theorem Pos.apply_of_lt_skipWhile_prop {P : Char Prop} [DecidablePred P] {s : Slice} {pos pos' : s.Pos}
(h₁ : pos pos') (h₂ : pos' < pos.skipWhile P) : P (pos'.get (ne_endPos_of_lt h₂)) := by
simp [Pos.skipWhile_prop_eq_skipWhile_decide] at h₂
simpa using apply_eq_true_of_lt_skipWhile_bool h₁ h₂
theorem apply_skipPrefixWhile_prop {P : Char Prop} [DecidablePred P] {s : Slice} {h} :
¬ P ((s.skipPrefixWhile P).get h) := by
simp [skipPrefixWhile_eq_skipWhile_startPos, Pos.apply_skipWhile_prop]
theorem apply_of_lt_skipPrefixWhile_prop {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos}
(h : pos < s.skipPrefixWhile P) : P (pos.get (Pos.ne_endPos_of_lt h)) := by
simp [skipPrefixWhile_prop_eq_skipPrefixWhile_decide] at h
simpa using apply_eq_true_of_lt_skipPrefixWhile_bool h
@[simp]
theorem all_prop_eq {P : Char Prop} [DecidablePred P] {s : Slice} :
s.all P = s.copy.toList.all (decide <| P ·) := by
simp [all_prop_eq_all_decide]
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]
@@ -64,11 +144,136 @@ theorem startsWith_prop_eq_head? {P : Char → Prop} [DecidablePred P] {s : Slic
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) :
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
@[simp]
theorem Pos.revSkip?_bool_eq_some_iff {p : Char Bool} {s : Slice} {pos res : s.Pos} :
pos.revSkip? p = some res h, res = pos.prev h p ((pos.prev h).get (by simp)) := by
simp [Pattern.Model.Pos.revSkip?_eq_some_iff, CharPred.isLongestRevMatchAt_iff]
@[simp]
theorem Pos.revSkip?_bool_eq_none_iff {p : Char Bool} {s : Slice} {pos : s.Pos} :
pos.revSkip? p = none h, p ((pos.prev h).get (by simp)) = false := by
simp [Pattern.Model.Pos.revSkip?_eq_none_iff, CharPred.revMatchesAt_iff]
theorem Pos.apply_revSkipWhile_bool_eq_false {p : Char Bool} {s : Slice} {pos : s.Pos} {h} :
p (((pos.revSkipWhile p).prev h).get (by simp)) = false := by
have := Pattern.Model.Pos.not_revMatchesAt_revSkipWhile p pos
simp_all [CharPred.revMatchesAt_iff]
theorem Pos.revSkipWhile_bool_eq_self_iff_get {p : Char Bool} {s : Slice} {pos : s.Pos} :
pos.revSkipWhile p = pos h, p ((pos.prev h).get (by simp)) = false := by
simp [Pattern.Model.Pos.revSkipWhile_eq_self_iff, CharPred.revMatchesAt_iff]
theorem Pos.apply_eq_true_of_revSkipWhile_le_bool {p : Char Bool} {s : Slice} {pos pos' : s.Pos}
(h₁ : pos' < pos) (h₂ : pos.revSkipWhile p pos') : p (pos'.get (Pos.ne_endPos_of_lt h₁)) = true :=
(CharPred.isLongestRevMatchAtChain_iff.1 (Pattern.Model.Pos.isLongestRevMatchAtChain_revSkipWhile p pos)).2 _ h₂ h₁
theorem apply_skipSuffixWhile_bool_eq_false {p : Char Bool} {s : Slice} {h} :
p (((s.skipSuffixWhile p).prev h).get (by simp)) = false := by
simp [skipSuffixWhile_eq_revSkipWhile_endPos, Pos.apply_revSkipWhile_bool_eq_false]
theorem apply_eq_true_of_skipSuffixWhile_le_bool {p : Char Bool} {s : Slice} {pos : s.Pos}
(h : s.skipSuffixWhile p pos) (h' : pos < s.endPos) :
p (pos.get (Pos.ne_endPos_of_lt h')) = true :=
Pos.apply_eq_true_of_revSkipWhile_le_bool h' (skipSuffixWhile_eq_revSkipWhile_endPos h)
@[simp]
theorem revAll_bool_eq {p : Char Bool} {s : Slice} : s.revAll p = s.copy.toList.all p := by
rw [Bool.eq_iff_iff, Pattern.Model.revAll_eq_true_iff,
CharPred.isLongestRevMatchAtChain_startPos_endPos_iff_toList, List.all_eq_true]
@[simp]
theorem Pos.revSkip?_prop_eq_some_iff {P : Char Prop} [DecidablePred P] {s : Slice} {pos res : s.Pos} :
pos.revSkip? P = some res h, res = pos.prev h P ((pos.prev h).get (by simp)) := by
simp [Pos.revSkip?_prop_eq_revSkip?_decide, revSkip?_bool_eq_some_iff]
@[simp]
theorem Pos.revSkip?_prop_eq_none_iff {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} :
pos.revSkip? P = none h, ¬ P ((pos.prev h).get (by simp)) := by
simp [Pos.revSkip?_prop_eq_revSkip?_decide, revSkip?_bool_eq_none_iff]
theorem Pos.apply_revSkipWhile_prop {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} {h} :
¬ P (((pos.revSkipWhile P).prev h).get (by simp)) := by
have := Pattern.Model.Pos.not_revMatchesAt_revSkipWhile P pos
simp_all [CharPred.Decidable.revMatchesAt_iff]
theorem Pos.revSkipWhile_prop_eq_self_iff_get {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos} :
pos.revSkipWhile P = pos h, ¬ P ((pos.prev h).get (by simp)) := by
simp [Pos.revSkipWhile_prop_eq_revSkipWhile_decide, revSkipWhile_bool_eq_self_iff_get]
theorem Pos.apply_of_revSkipWhile_le_prop {P : Char Prop} [DecidablePred P] {s : Slice} {pos pos' : s.Pos}
(h₁ : pos' < pos) (h₂ : pos.revSkipWhile P pos') : P (pos'.get (Pos.ne_endPos_of_lt h₁)) := by
have h₂' : pos.revSkipWhile (decide <| P ·) pos' :=
Pos.revSkipWhile_prop_eq_revSkipWhile_decide (p := P) pos h₂
simpa using Pos.apply_eq_true_of_revSkipWhile_le_bool h₁ h₂'
theorem apply_skipSuffixWhile_prop {P : Char Prop} [DecidablePred P] {s : Slice} {h} :
¬ P (((s.skipSuffixWhile P).prev h).get (by simp)) := by
have := Pattern.Model.Pos.not_revMatchesAt_revSkipWhile P s.endPos
simp_all [CharPred.Decidable.revMatchesAt_iff, skipSuffixWhile_eq_revSkipWhile_endPos]
theorem apply_of_skipSuffixWhile_le_prop {P : Char Prop} [DecidablePred P] {s : Slice} {pos : s.Pos}
(h : s.skipSuffixWhile P pos) (h' : pos < s.endPos) :
P (pos.get (Pos.ne_endPos_of_lt h')) :=
Pos.apply_of_revSkipWhile_le_prop h' (skipSuffixWhile_eq_revSkipWhile_endPos (pat := P) h)
@[simp]
theorem revAll_prop_eq {P : Char Prop} [DecidablePred P] {s : Slice} :
s.revAll P = s.copy.toList.all (decide <| P ·) := by
simp [revAll_prop_eq_revAll_decide, revAll_bool_eq]
end Slice
theorem skipPrefix?_bool_eq_some_iff {p : Char Bool} {s : String} {pos : s.Pos} :
@@ -78,21 +283,58 @@ theorem skipPrefix?_bool_eq_some_iff {p : Char → Bool} {s : String} {pos : s.P
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]
simp [ 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]
simp [ 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?]
simp [ 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
@[simp]
theorem Pos.skip?_bool_eq_some_iff {p : Char Bool} {s : String} {pos res : s.Pos} :
pos.skip? p = some res h, res = pos.next h p (pos.get h) := by
simp [skip?_eq_skip?_toSlice, toSlice_inj, toSlice_next]
@[simp]
theorem Pos.skip?_bool_eq_none_iff {p : Char Bool} {s : String} {pos : s.Pos} :
pos.skip? p = none h, p (pos.get h) = false := by
simp [skip?_eq_skip?_toSlice]
theorem Pos.apply_skipWhile_bool_eq_false {p : Char Bool} {s : String} {pos : s.Pos} {h} :
p ((pos.skipWhile p).get h) = false := by
simp [skipWhile_eq_skipWhile_toSlice, Slice.Pos.apply_skipWhile_bool_eq_false]
theorem Pos.skipWhile_bool_eq_self_iff_get {p : Char Bool} {s : String} {pos : s.Pos} :
pos.skipWhile p = pos h, p (pos.get h) = false := by
simp [skipWhile_eq_skipWhile_toSlice, toSlice_inj, Slice.Pos.skipWhile_bool_eq_self_iff_get]
theorem Pos.apply_eq_true_of_lt_skipWhile_bool {p : Char Bool} {s : String} {pos pos' : s.Pos}
(h₁ : pos pos') (h₂ : pos' < pos.skipWhile p) : p (pos'.get (ne_endPos_of_lt h₂)) = true := by
rw [Pos.get_eq_get_toSlice]
exact Slice.Pos.apply_eq_true_of_lt_skipWhile_bool (toSlice_le_toSlice_iff.2 h₁)
(by simpa [skipWhile_eq_skipWhile_toSlice] using h₂)
theorem apply_skipPrefixWhile_bool_eq_false {p : Char Bool} {s : String} {h} :
p ((s.skipPrefixWhile p).get h) = false := by
simp [skipPrefixWhile_eq_skipPrefixWhile_toSlice, Slice.apply_skipPrefixWhile_bool_eq_false]
theorem apply_eq_true_of_lt_skipPrefixWhile_bool {p : Char Bool} {s : String} {pos : s.Pos} (h : pos < s.skipPrefixWhile p) :
p (pos.get (Pos.ne_endPos_of_lt h)) = true := by
rw [Pos.get_eq_get_toSlice]
exact Slice.apply_eq_true_of_lt_skipPrefixWhile_bool (by simpa [skipPrefixWhile_eq_skipPrefixWhile_toSlice] using h)
@[simp]
theorem all_bool_eq {p : Char Bool} {s : String} : s.all p = s.toList.all p := by
simp [ all_toSlice]
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,
@@ -100,19 +342,198 @@ theorem skipPrefix?_prop_eq_some_iff {P : Char → Prop} [DecidablePred P] {s :
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]
simp [ 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]
simp [ 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?]
simp [ 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
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_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_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_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_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_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_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
@[simp]
theorem Pos.skip?_prop_eq_some_iff {P : Char Prop} [DecidablePred P] {s : String} {pos res : s.Pos} :
pos.skip? P = some res h, res = pos.next h P (pos.get h) := by
simp [skip?_eq_skip?_toSlice, toSlice_inj, toSlice_next]
@[simp]
theorem Pos.skip?_prop_eq_none_iff {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} :
pos.skip? P = none h, ¬ P (pos.get h) := by
simp [skip?_eq_skip?_toSlice]
theorem Pos.apply_skipWhile_prop {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} {h} :
¬ P ((pos.skipWhile P).get h) := by
simp [skipWhile_eq_skipWhile_toSlice, Slice.Pos.apply_skipWhile_prop]
theorem Pos.skipWhile_prop_eq_self_iff_get {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} :
pos.skipWhile P = pos h, ¬ P (pos.get h) := by
simp [skipWhile_eq_skipWhile_toSlice, toSlice_inj, Slice.Pos.skipWhile_prop_eq_self_iff_get]
theorem Pos.apply_of_lt_skipWhile_prop {P : Char Prop} [DecidablePred P] {s : String} {pos pos' : s.Pos}
(h₁ : pos pos') (h₂ : pos' < pos.skipWhile P) : P (pos'.get (ne_endPos_of_lt h₂)) := by
rw [Pos.get_eq_get_toSlice]
exact Slice.Pos.apply_of_lt_skipWhile_prop (toSlice_le_toSlice_iff.2 h₁)
(by simpa [skipWhile_eq_skipWhile_toSlice] using h₂)
theorem apply_skipPrefixWhile_prop {P : Char Prop} [DecidablePred P] {s : String} {h} :
¬ P ((s.skipPrefixWhile P).get h) := by
simp [skipPrefixWhile_eq_skipPrefixWhile_toSlice, Slice.apply_skipPrefixWhile_prop]
theorem apply_of_lt_skipPrefixWhile_prop {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos}
(h : pos < s.skipPrefixWhile P) : P (pos.get (Pos.ne_endPos_of_lt h)) := by
rw [Pos.get_eq_get_toSlice]
exact Slice.apply_of_lt_skipPrefixWhile_prop (by simpa [skipPrefixWhile_eq_skipPrefixWhile_toSlice] using h)
@[simp]
theorem all_prop_eq {P : Char Prop} [DecidablePred P] {s : String} :
s.all P = s.toList.all (decide <| P ·) := by
simp [ all_toSlice]
@[simp]
theorem Pos.revSkip?_bool_eq_some_iff {p : Char Bool} {s : String} {pos res : s.Pos} :
pos.revSkip? p = some res h, res = pos.prev h p ((pos.prev h).get (by simp)) := by
simp [revSkip?_eq_revSkip?_toSlice, toSlice_inj, toSlice_prev, get_eq_get_toSlice]
@[simp]
theorem Pos.revSkip?_bool_eq_none_iff {p : Char Bool} {s : String} {pos : s.Pos} :
pos.revSkip? p = none h, p ((pos.prev h).get (by simp)) = false := by
simp [revSkip?_eq_revSkip?_toSlice, Pos.prev_toSlice]
theorem Pos.apply_revSkipWhile_bool_eq_false {p : Char Bool} {s : String} {pos : s.Pos} {h} :
p (((pos.revSkipWhile p).prev h).get (by simp)) = false := by
have h' : pos.toSlice.revSkipWhile p s.toSlice.startPos := by
simpa [Pos.revSkipWhile_eq_revSkipWhile_toSlice, toSlice_inj] using h
have := Slice.Pos.apply_revSkipWhile_bool_eq_false (pos := pos.toSlice) (h := h')
simpa [Pos.revSkipWhile_eq_revSkipWhile_toSlice, Pos.prev_ofToSlice]
theorem Pos.revSkipWhile_bool_eq_self_iff_get {p : Char Bool} {s : String} {pos : s.Pos} :
pos.revSkipWhile p = pos h, p ((pos.prev h).get (by simp)) = false := by
simp [Pos.revSkipWhile_eq_revSkipWhile_toSlice, toSlice_inj, Slice.Pos.revSkipWhile_bool_eq_self_iff_get,
Pos.prev_toSlice]
theorem Pos.apply_eq_true_of_revSkipWhile_le_bool {p : Char Bool} {s : String} {pos pos' : s.Pos}
(h₁ : pos' < pos) (h₂ : pos.revSkipWhile p pos') : p (pos'.get (ne_endPos_of_lt h₁)) = true := by
rw [Pos.get_eq_get_toSlice]
exact Slice.Pos.apply_eq_true_of_revSkipWhile_le_bool
(Pos.toSlice_lt_toSlice_iff.2 h₁)
(by simpa [Pos.revSkipWhile_eq_revSkipWhile_toSlice, Pos.ofToSlice_le_iff] using h₂)
theorem apply_skipSuffixWhile_bool_eq_false {p : Char Bool} {s : String} {h} :
p (((s.skipSuffixWhile p).prev h).get (by simp)) = false := by
have h' : s.toSlice.skipSuffixWhile p s.toSlice.startPos := by
simpa [skipSuffixWhile_eq_skipSuffixWhile_toSlice, Pos.toSlice_inj] using h
have := Slice.apply_skipSuffixWhile_bool_eq_false (s := s.toSlice) (h := h')
simpa [skipSuffixWhile_eq_skipSuffixWhile_toSlice, Pos.prev_ofToSlice]
theorem apply_eq_true_of_skipSuffixWhile_le_bool {p : Char Bool} {s : String} {pos : s.Pos}
(h : s.skipSuffixWhile p pos) (h' : pos < s.endPos) :
p (pos.get (Pos.ne_endPos_of_lt h')) = true := by
rw [Pos.get_eq_get_toSlice]
exact Slice.apply_eq_true_of_skipSuffixWhile_le_bool
(by simpa [skipSuffixWhile_eq_skipSuffixWhile_toSlice, Pos.ofToSlice_le_iff] using h)
(by simpa [Pos.toSlice_lt_toSlice_iff] using h')
@[simp]
theorem revAll_bool_eq {p : Char Bool} {s : String} : s.revAll p = s.toList.all p := by
simp [ revAll_toSlice]
@[simp]
theorem Pos.revSkip?_prop_eq_some_iff {P : Char Prop} [DecidablePred P] {s : String} {pos res : s.Pos} :
pos.revSkip? P = some res h, res = pos.prev h P ((pos.prev h).get (by simp)) := by
simp [revSkip?_eq_revSkip?_toSlice, toSlice_inj, toSlice_prev, get_eq_get_toSlice]
@[simp]
theorem Pos.revSkip?_prop_eq_none_iff {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} :
pos.revSkip? P = none h, ¬ P ((pos.prev h).get (by simp)) := by
simp [revSkip?_eq_revSkip?_toSlice, Pos.prev_toSlice]
theorem Pos.apply_revSkipWhile_prop {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} {h} :
¬ P (((pos.revSkipWhile P).prev h).get (by simp)) := by
have h' : pos.toSlice.revSkipWhile P s.toSlice.startPos := by
simpa [Pos.revSkipWhile_eq_revSkipWhile_toSlice, toSlice_inj] using h
have := Slice.Pos.apply_revSkipWhile_prop (pos := pos.toSlice) (h := h')
simpa [Pos.revSkipWhile_eq_revSkipWhile_toSlice, Pos.prev_ofToSlice]
theorem Pos.revSkipWhile_prop_eq_self_iff_get {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos} :
pos.revSkipWhile P = pos h, ¬ P ((pos.prev h).get (by simp)) := by
simp [Pos.revSkipWhile_eq_revSkipWhile_toSlice, toSlice_inj,
Slice.Pos.revSkipWhile_prop_eq_self_iff_get, Pos.prev_toSlice]
theorem Pos.apply_of_revSkipWhile_le_prop {P : Char Prop} [DecidablePred P] {s : String} {pos pos' : s.Pos}
(h₁ : pos' < pos) (h₂ : pos.revSkipWhile P pos') : P (pos'.get (ne_endPos_of_lt h₁)) := by
rw [Pos.get_eq_get_toSlice]
exact Slice.Pos.apply_of_revSkipWhile_le_prop
(Pos.toSlice_lt_toSlice_iff.2 h₁)
(by simpa [Pos.revSkipWhile_eq_revSkipWhile_toSlice, Pos.ofToSlice_le_iff] using h₂)
theorem apply_skipSuffixWhile_prop {P : Char Prop} [DecidablePred P] {s : String} {h} :
¬ P (((s.skipSuffixWhile P).prev h).get (by simp)) := by
have h' : s.toSlice.skipSuffixWhile P s.toSlice.startPos := by
simpa [skipSuffixWhile_eq_skipSuffixWhile_toSlice, Pos.toSlice_inj] using h
have := Slice.apply_skipSuffixWhile_prop (s := s.toSlice) (h := h')
simpa [skipSuffixWhile_eq_skipSuffixWhile_toSlice, Pos.prev_ofToSlice]
theorem apply_of_skipSuffixWhile_le_prop {P : Char Prop} [DecidablePred P] {s : String} {pos : s.Pos}
(h : s.skipSuffixWhile P pos) (h' : pos < s.endPos) :
P (pos.get (Pos.ne_endPos_of_lt h')) := by
rw [Pos.get_eq_get_toSlice]
exact Slice.apply_of_skipSuffixWhile_le_prop
(by simpa [skipSuffixWhile_eq_skipSuffixWhile_toSlice, Pos.ofToSlice_le_iff] using h)
(by simpa [Pos.toSlice_lt_toSlice_iff] using h')
@[simp]
theorem revAll_prop_eq {P : Char Prop} [DecidablePred P] {s : String} :
s.revAll P = s.toList.all (decide <| P ·) := by
simp [ revAll_toSlice]
end String

View File

@@ -30,11 +30,7 @@ theorem skipPrefix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true)
@[simp]
theorem skipPrefix?_slice_eq_some_iff {pat s : Slice} {pos : s.Pos} :
s.skipPrefix? pat = some pos t, pos.Splits pat.copy t := by
match h : pat.isEmpty with
| false =>
have := ForwardSliceSearcher.lawfulForwardPatternModel h
rw [Pattern.Model.skipPrefix?_eq_some_iff, ForwardSliceSearcher.isLongestMatch_iff_splits h]
| true => simp [skipPrefix?_slice_of_isEmpty h, (show pat.copy = "" by simpa), eq_comm]
rw [Pattern.Model.skipPrefix?_eq_some_iff, ForwardSliceSearcher.isLongestMatch_iff_splits]
theorem startsWith_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
s.startsWith pat = true := by
@@ -43,14 +39,10 @@ theorem startsWith_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true)
@[simp]
theorem startsWith_slice_iff {pat s : Slice} :
s.startsWith pat pat.copy.toList <+: s.copy.toList := by
match h : pat.isEmpty with
| false =>
have := ForwardSliceSearcher.lawfulForwardPatternModel h
simp only [Model.startsWith_iff, ForwardSliceSearcher.matchesAt_iff_splits h,
splits_startPos_iff, exists_and_left, exists_eq_left]
simp only [ toList_inj, toList_append, List.prefix_iff_exists_append_eq]
exact fun t, ht => t.toList, by simp [ht], fun t, ht => String.ofList t, by simp [ ht]
| true => simp [startsWith_slice_of_isEmpty h, (show pat.copy = "" by simpa)]
simp only [Model.startsWith_iff, ForwardSliceSearcher.matchesAt_iff_splits,
splits_startPos_iff, exists_and_left, exists_eq_left]
simp only [ toList_inj, toList_append, List.prefix_iff_exists_append_eq]
exact fun t, ht => t.toList, by simp [ht], fun t, ht => String.ofList t, by simp [ ht]
@[simp]
theorem startsWith_slice_eq_false_iff {pat s : Slice} :
@@ -63,14 +55,18 @@ theorem dropPrefix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true)
theorem eq_append_of_dropPrefix?_slice_eq_some {pat s res : Slice} (h : s.dropPrefix? pat = some res) :
s.copy = pat.copy ++ res.copy := by
match hpat : pat.isEmpty with
| false =>
have := ForwardSliceSearcher.lawfulForwardPatternModel hpat
have := Pattern.Model.eq_append_of_dropPrefix?_eq_some h
simp only [ForwardPatternModel.Matches] at this
obtain _, -, rfl, h := this
exact h
| true => simp [Option.some.inj (h dropPrefix?_slice_of_isEmpty hpat), (show pat.copy = "" by simpa)]
have := Pattern.Model.eq_append_of_dropPrefix?_eq_some h
simp only [PatternModel.Matches] at this
obtain _, -, rfl, h := this
exact h
@[simp]
theorem all_slice_iff {pat s : Slice} : s.all pat n, s.copy = String.join (List.replicate n pat.copy) := by
simp [Pattern.Model.all_eq_true_iff, ForwardSliceSearcher.isLongestMatchAtChain_startPos_endPos_iff]
@[simp]
theorem revAll_slice_iff {pat s : Slice} : s.revAll pat n, s.copy = String.join (List.replicate n pat.copy) := by
simp [Pattern.Model.revAll_eq_true_iff, ForwardSliceSearcher.isLongestRevMatchAtChain_startPos_endPos_iff]
@[simp]
theorem skipPrefix?_string_eq_some_iff {pat : String} {s : Slice} {pos : s.Pos} :
@@ -104,6 +100,76 @@ theorem eq_append_of_dropPrefix?_string_eq_some {pat : String} {s res : Slice} (
rw [dropPrefix?_string_eq_dropPrefix?_toSlice] at h
simpa using eq_append_of_dropPrefix?_slice_eq_some h
theorem skipSuffix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
s.skipSuffix? pat = some s.endPos := by
rw [skipSuffix?_eq_backwardPatternSkipSuffix?, BackwardSliceSearcher.skipSuffix?_of_isEmpty hpat]
@[simp]
theorem skipSuffix?_slice_eq_some_iff {pat s : Slice} {pos : s.Pos} :
s.skipSuffix? pat = some pos t, pos.Splits t pat.copy := by
rw [Pattern.Model.skipSuffix?_eq_some_iff, ForwardSliceSearcher.isLongestRevMatch_iff_splits]
theorem endsWith_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
s.endsWith pat = true := by
rw [endsWith_eq_backwardPatternEndsWith, BackwardSliceSearcher.endsWith_of_isEmpty hpat]
@[simp]
theorem endsWith_slice_iff {pat s : Slice} :
s.endsWith pat pat.copy.toList <:+ s.copy.toList := by
simp only [Model.endsWith_iff, ForwardSliceSearcher.revMatchesAt_iff_splits,
splits_endPos_iff, exists_eq_right]
simp only [ toList_inj, toList_append, List.suffix_iff_exists_append_eq]
exact fun t, ht => t.toList, by simp [ht], fun t, ht => String.ofList t, by simp [ ht]
@[simp]
theorem endsWith_slice_eq_false_iff {pat s : Slice} :
s.endsWith pat = false ¬ (pat.copy.toList <:+ s.copy.toList) := by
simp [ Bool.not_eq_true, endsWith_slice_iff]
theorem dropSuffix?_slice_of_isEmpty {pat s : Slice} (hpat : pat.isEmpty = true) :
s.dropSuffix? pat = some s := by
simp [dropSuffix?_eq_map_skipSuffix?, skipSuffix?_slice_of_isEmpty hpat]
theorem eq_append_of_dropSuffix?_slice_eq_some {pat s res : Slice} (h : s.dropSuffix? pat = some res) :
s.copy = res.copy ++ pat.copy := by
have := Pattern.Model.eq_append_of_dropSuffix?_eq_some h
simp only [PatternModel.Matches] at this
obtain _, -, rfl, h := this
exact h
@[simp]
theorem skipSuffix?_string_eq_some_iff' {pat : String} {s : Slice} {pos : s.Pos} :
s.skipSuffix? pat = some pos t, pos.Splits t pat := by
simp [skipSuffix?_string_eq_skipSuffix?_toSlice]
@[simp]
theorem skipSuffix?_string_empty {s : Slice} : s.skipSuffix? "" = some s.endPos := by
simp
@[simp]
theorem endsWith_string_iff {pat : String} {s : Slice} :
s.endsWith pat pat.toList <:+ s.copy.toList := by
simp [endsWith_string_eq_endsWith_toSlice]
@[simp]
theorem endsWith_string_empty {s : Slice} : s.endsWith "" = true := by
simp
@[simp]
theorem endsWith_string_eq_false_iff {pat : String} {s : Slice} :
s.endsWith pat = false ¬ (pat.toList <:+ s.copy.toList) := by
simp [endsWith_string_eq_endsWith_toSlice]
@[simp]
theorem dropSuffix?_string_empty {s : Slice} : s.dropSuffix? "" = some s := by
simpa [dropSuffix?_string_eq_dropSuffix?_toSlice] using dropSuffix?_slice_of_isEmpty (by simp)
theorem eq_append_of_dropSuffix?_string_eq_some {pat : String} {s res : Slice} (h : s.dropSuffix? pat = some res) :
s.copy = res.copy ++ pat := by
rw [dropSuffix?_string_eq_dropSuffix?_toSlice] at h
simpa using eq_append_of_dropSuffix?_slice_eq_some h
end Slice
theorem skipPrefix?_slice_of_isEmpty {pat : Slice} {s : String} (hpat : pat.isEmpty = true) :
@@ -127,12 +193,12 @@ theorem startsWith_slice_of_isEmpty {pat : Slice} {s : String} (hpat : pat.isEmp
@[simp]
theorem startsWith_slice_iff {pat : Slice} {s : String} :
s.startsWith pat pat.copy.toList <+: s.toList := by
simp [startsWith_eq_startsWith_toSlice]
simp [ startsWith_toSlice]
@[simp]
theorem startsWith_slice_eq_false_iff {pat : Slice} {s : String} :
s.startsWith pat = false ¬ (pat.copy.toList <+: s.toList) := by
simp [startsWith_eq_startsWith_toSlice]
simp [ startsWith_toSlice]
theorem dropPrefix?_slice_of_isEmpty {pat : Slice} {s : String} (hpat : pat.isEmpty = true) :
s.dropPrefix? pat = some s.toSlice := by
@@ -158,21 +224,21 @@ theorem skipPrefix?_string_eq_some_iff {pat s : String} {pos : s.Pos} :
@[simp]
theorem startsWith_string_empty {s : String} : s.startsWith "" = true := by
simp [startsWith_eq_startsWith_toSlice]
simp [ startsWith_toSlice]
@[simp]
theorem startsWith_string_iff {pat s : String} :
s.startsWith pat pat.toList <+: s.toList := by
simp [startsWith_eq_startsWith_toSlice]
simp [ startsWith_toSlice]
@[simp]
theorem startsWith_string_eq_false_iff {pat s : String} :
s.startsWith pat = false ¬ (pat.toList <+: s.toList) := by
simp [startsWith_eq_startsWith_toSlice]
simp [ startsWith_toSlice]
@[simp]
theorem dropPrefix?_string_empty {s : String} : s.dropPrefix? "" = some s.toSlice := by
simp [dropPrefix?_eq_dropPrefix?_toSlice]
simp [ dropPrefix?_toSlice]
theorem eq_append_of_dropPrefix?_string_eq_some {s pat : String} {res : Slice} (h : s.dropPrefix? pat = some res) :
s = pat ++ res.copy := by

View File

@@ -8,6 +8,8 @@ module
prelude
public import Init.Data.String.Search
import all Init.Data.String.Search
import Init.Data.String.Lemmas.Slice
import Init.Data.String.Lemmas.FindPos
public section
@@ -28,4 +30,42 @@ theorem Pos.le_find {s : String} (pos : s.Pos) (pattern : ρ) [ToForwardSearcher
pos pos.find pattern := by
simp [Pos.find, toSlice_le]
@[simp]
theorem front?_toSlice {s : String} : s.toSlice.front? = s.front? :=
(rfl)
theorem front?_eq_get? {s : String} : s.front? = s.startPos.get? := by
simp [ front?_toSlice, Pos.get?_toSlice, Slice.front?_eq_get?]
theorem front?_eq {s : String} : s.front? = s.toList.head? := by
simp [ front?_toSlice, Slice.front?_eq]
@[simp]
theorem front_toSlice {s : String} : s.toSlice.front = s.front :=
(rfl)
@[simp]
theorem front_eq {s : String} : s.front = s.front?.getD default := by
simp [ front_toSlice, Slice.front_eq]
@[simp]
theorem back?_toSlice {s : String} : s.toSlice.back? = s.back? :=
(rfl)
theorem back?_eq_get? {s : String} : s.back? = s.endPos.prev?.bind Pos.get? := by
simp only [ back?_toSlice, Slice.back?_eq_get?, endPos_toSlice, Slice.Pos.prev?_eq_dif,
startPos_toSlice, Pos.toSlice_inj, Pos.prev?_eq_dif]
split <;> simp [ Pos.get?_toSlice, Pos.toSlice_prev]
theorem back?_eq {s : String} : s.back? = s.toList.getLast? := by
simp [ back?_toSlice, Slice.back?_eq]
@[simp]
theorem back_toSlice {s : String} : s.toSlice.back = s.back :=
(rfl)
@[simp]
theorem back_eq {s : String} : s.back = s.back?.getD default := by
simp [ back_toSlice, Slice.back_eq]
end String

View File

@@ -11,6 +11,8 @@ import all Init.Data.String.Slice
import Init.Data.String.Lemmas.Pattern.Memcmp
import Init.Data.String.Lemmas.Basic
import Init.Data.ByteArray.Lemmas
import Init.Data.String.Lemmas.IsEmpty
import Init.Data.String.Lemmas.FindPos
public section
@@ -33,9 +35,104 @@ theorem beq_eq_true_iff {s t : Slice} : s == t ↔ s.copy = t.copy := by
theorem beq_eq_false_iff {s t : Slice} : (s == t) = false s.copy t.copy := by
simp [ Bool.not_eq_true]
theorem beq_eq_decide {s t : Slice} : (s == t) = decide (s.copy = t.copy) := by
cases h : s == t <;> simp_all
theorem beq_eq_decide {s t : Slice} : (s == t) = decide (s.copy = t.copy) :=
Bool.eq_iff_iff.2 (by simp)
instance : EquivBEq String.Slice :=
equivBEq_of_iff_apply_eq copy (by simp)
theorem beq_list_iff {l l' : List String.Slice} : l == l' l.map copy = l'.map copy := by
induction l generalizing l' <;> cases l' <;> simp_all
theorem beq_list_eq_false_iff {l l' : List String.Slice} :
(l == l') = false l.map copy l'.map copy := by
simp [ Bool.not_eq_true, beq_list_iff]
theorem beq_list_eq_decide {l l' : List String.Slice} :
(l == l') = decide (l.map copy = l'.map copy) :=
Bool.eq_iff_iff.2 (by simp [beq_list_iff])
end BEq
end String.Slice
namespace Pos
theorem get?_eq_dif {s : Slice} {p : s.Pos} : p.get? = if h : p = s.endPos then none else some (p.get h) :=
(rfl)
theorem get?_eq_some_get {s : Slice} {p : s.Pos} (h : p s.endPos) : p.get? = some (p.get h) := by
simp [Pos.get?, h]
@[simp]
theorem get?_eq_none_iff {s : Slice} {p : s.Pos} : p.get? = none p = s.endPos := by
simp [Pos.get?]
theorem get?_eq_none {s : Slice} {p : s.Pos} (h : p = s.endPos) : p.get? = none :=
get?_eq_none_iff.2 h
@[simp]
theorem get?_endPos {s : Slice} : s.endPos.get? = none := by
simp
end Pos
end Slice
namespace Pos
theorem get?_toSlice {s : String} {p : s.Pos} : p.toSlice.get? = p.get? :=
(rfl)
theorem get?_eq_dif {s : String} {p : s.Pos} : p.get? = if h : p = s.endPos then none else some (p.get h) := by
simp [ get?_toSlice, Slice.Pos.get?_eq_dif]
theorem get?_eq_some_get {s : String} {p : s.Pos} (h : p s.endPos) : p.get? = some (p.get h) := by
simpa [ get?_toSlice] using Slice.Pos.get?_eq_some_get (by simpa)
@[simp]
theorem get?_eq_none_iff {s : String} {p : s.Pos} : p.get? = none p = s.endPos := by
simp [ get?_toSlice]
theorem get?_eq_none {s : String} {p : s.Pos} (h : p = s.endPos) : p.get? = none :=
get?_eq_none_iff.2 h
@[simp]
theorem get?_endPos {s : String} : s.endPos.get? = none := by
simp
end Pos
namespace Slice
theorem front?_eq_get? {s : Slice} : s.front? = s.startPos.get? :=
(rfl)
theorem front?_eq {s : Slice} : s.front? = s.copy.toList.head? := by
simp only [front?_eq_get?, Pos.get?_eq_dif]
split
· simp_all [startPos_eq_endPos_iff, eq_comm (a := none)]
· rename_i h
obtain t, ht := s.splits_startPos.exists_eq_singleton_append h
simp [ht]
@[simp]
theorem front_eq {s : Slice} : s.front = s.front?.getD default := by
simp [front]
theorem back?_eq_get? {s : Slice} : s.back? = s.endPos.prev?.bind Pos.get? :=
(rfl)
theorem back?_eq {s : Slice} : s.back? = s.copy.toList.getLast? := by
simp [back?_eq_get?, Pos.prev?_eq_dif]
split
· simp_all [startPos_eq_endPos_iff, eq_comm (a := s.endPos), eq_comm (a := none)]
· rename_i h
obtain t, ht := s.splits_endPos.exists_eq_append_singleton_of_ne_startPos h
simp [ht, Pos.get?_eq_some_get]
@[simp]
theorem back_eq {s : Slice} : s.back = s.back?.getD default := by
simp [back]
end Slice
end String

View File

@@ -17,6 +17,8 @@ import Init.Data.String.OrderInstances
import Init.Data.Nat.Order
import Init.Omega
import Init.Data.String.Lemmas.FindPos
import Init.Data.List.TakeDrop
import Init.Data.List.Nat.TakeDrop
/-!
# `Splits` predicates on `String.Pos` and `String.Slice.Pos`.
@@ -97,6 +99,11 @@ theorem Pos.splits {s : String} (p : s.Pos) :
eq_append := by simp [ toByteArray_inj, Slice.toByteArray_copy, size_toByteArray]
offset_eq_rawEndPos := by simp
@[simp]
theorem sliceTo_append_sliceFrom {s : String} {pos : s.Pos} :
(s.sliceTo pos).copy ++ (s.sliceFrom pos).copy = s :=
pos.splits.eq_append.symm
theorem Slice.Pos.splits {s : Slice} (p : s.Pos) :
p.Splits (s.sliceTo p).copy (s.sliceFrom p).copy where
eq_append := copy_eq_copy_sliceTo
@@ -365,7 +372,7 @@ theorem Slice.Pos.Splits.of_prev {s : Slice} {p : s.Pos} {hp}
obtain rfl, rfl, rfl := by simpa using h.eq (splits_prev p hp)
exact splits_prev_right p hp
theorem Slice.sliceTo_copy_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : String} :
theorem Slice.copy_sliceTo_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ : String} :
(s.sliceTo p).copy = t₁ t₂, p.Splits t₁ t₂ := by
refine ?_, ?_
· rintro rfl
@@ -373,13 +380,37 @@ theorem Slice.sliceTo_copy_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₁ :
· rintro t₂, h
exact p.splits.eq_left h
theorem sliceTo_copy_eq_iff_exists_splits {s : String} {p : s.Pos} {t₁ : String} :
(s.sliceTo p).copy = t₁ t₂, p.Splits t₁ t₂ := by
theorem Slice.copy_sliceTo_eq_iff_splits {s : Slice} {p : s.Pos} {t₁ : String} :
(s.sliceTo p).copy = t₁ p.Splits t₁ (s.sliceFrom p).copy :=
fun h => h p.splits, p.splits.eq_left
theorem Slice.copy_sliceFrom_eq_iff_exists_splits {s : Slice} {p : s.Pos} {t₂ : String} :
(s.sliceFrom p).copy = t₂ t₁, p.Splits t₁ t₂ := by
refine ?_, ?_
· rintro rfl
exact _, p.splits
· rintro t₂, h
exact p.splits.eq_left h
exact p.splits.eq_right h
theorem Slice.copy_sliceFrom_eq_iff_splits {s : Slice} {p : s.Pos} {t₂ : String} :
(s.sliceFrom p).copy = t₂ p.Splits (s.sliceTo p).copy t₂ :=
fun h => h p.splits, p.splits.eq_right
theorem copy_sliceTo_eq_iff_exists_splits {s : String} {p : s.Pos} {t₁ : String} :
(s.sliceTo p).copy = t₁ t₂, p.Splits t₁ t₂ := by
simp [ Pos.splits_toSlice_iff, Slice.copy_sliceTo_eq_iff_exists_splits]
theorem copy_sliceTo_eq_iff_splits {s : String} {p : s.Pos} {t₁ : String} :
(s.sliceTo p).copy = t₁ p.Splits t₁ (s.sliceFrom p).copy :=
fun h => h p.splits, p.splits.eq_left
theorem copy_sliceFrom_eq_iff_exists_splits {s : String} {p : s.Pos} {t₂ : String} :
(s.sliceFrom p).copy = t₂ t₁, p.Splits t₁ t₂ := by
simp [ Pos.splits_toSlice_iff, Slice.copy_sliceFrom_eq_iff_exists_splits]
theorem copy_sliceFrom_eq_iff_splits {s : String} {p : s.Pos} {t₂ : String} :
(s.sliceFrom p).copy = t₂ p.Splits (s.sliceTo p).copy t₂ :=
fun h => h p.splits, p.splits.eq_right
theorem Pos.Splits.offset_eq_decreaseBy {s : String} {p : s.Pos} (h : p.Splits t₁ t₂) :
p.offset = s.rawEndPos.decreaseBy t₂.utf8ByteSize := by
@@ -425,8 +456,7 @@ theorem Slice.splits_singleton_iff {s : Slice} {p : s.Pos} {c : Char} {t : Strin
simp [startPos_ne_endPos_iff, copy_ne_empty_iff, h.eq_append]
have spl : (s.startPos.next this).Splits (singleton c) t := by
rw [ empty_append (s := singleton c)]
apply Pos.Splits.next
simp [h.eq_append]
exact Pos.Splits.next (by simp [h.eq_append])
refine this, h.pos_eq spl, ?_, h.eq_append
rw [ empty_append (s := singleton c)] at spl
exact spl.get_eq_of_singleton
@@ -440,6 +470,27 @@ theorem splits_singleton_iff {s : String} {p : s.Pos} {c : Char} {t : String} :
rw [ Pos.splits_toSlice_iff, Slice.splits_singleton_iff]
simp [ Pos.ofToSlice_inj]
theorem Slice.splits_singleton_right_iff {s : Slice} {p : s.Pos} {c : Char} {t : String} :
p.Splits t (singleton c)
h, p = s.endPos.prev h (s.endPos.prev h).get (by simp) = c s.copy = t ++ singleton c := by
refine fun h => ?_, ?_
· have : s.endPos s.startPos := by
simp [ne_comm (a := s.endPos), startPos_ne_endPos_iff, copy_ne_empty_iff, h.eq_append]
have spl : (s.endPos.prev this).Splits t (singleton c) := by
rw [ append_empty (s := singleton c)]
exact Pos.Splits.prev (by simp [h.eq_append])
refine this, h.pos_eq spl, ?_, h.eq_append
exact (h.eq_append Pos.next_prev (h := this) s.splits_endPos).get_eq_of_singleton
· rintro h, rfl, rfl, h'
rw [ String.append_empty (s := singleton _)]
exact Pos.Splits.prev (by simp [h'])
theorem splits_singleton_right_iff {s : String} {p : s.Pos} {c : Char} {t : String} :
p.Splits t (singleton c)
h, p = s.endPos.prev h (s.endPos.prev h).get (by simp) = c s = t ++ singleton c := by
rw [ Pos.splits_toSlice_iff, Slice.splits_singleton_right_iff]
simp [ Pos.ofToSlice_inj, Pos.prev_toSlice]
theorem Slice.splits_next_startPos {s : Slice} {h : s.startPos s.endPos} :
(s.startPos.next h).Splits
(singleton (s.startPos.get h)) (s.sliceFrom (s.startPos.next h)).copy := by
@@ -454,6 +505,20 @@ theorem splits_next_startPos {s : String} {h : s.startPos ≠ s.endPos} :
rw [ Pos.splits_toSlice_iff]
apply (Slice.splits_next_startPos).of_eq <;> simp [String.Pos.next_toSlice]
theorem Slice.splits_prev_endPos {s : Slice} {h : s.endPos s.startPos} :
(s.endPos.prev h).Splits
(s.sliceTo (s.endPos.prev h)).copy (singleton ((s.endPos.prev h).get (by simp))) := by
rw [ String.append_empty (s := singleton _)]
apply Slice.Pos.Splits.prev
have := Slice.Pos.splits_prev_right s.endPos h
rwa [copy_sliceFrom_endPos] at this
theorem splits_prev_endPos {s : String} {h : s.endPos s.startPos} :
(s.endPos.prev h).Splits
(s.sliceTo (s.endPos.prev h)).copy (singleton ((s.endPos.prev h).get (by simp))) := by
rw [ Pos.splits_toSlice_iff]
apply (Slice.splits_prev_endPos).of_eq <;> simp [String.Pos.prev_toSlice, h]
theorem Slice.Pos.Splits.toByteArray_eq_left {s : Slice} {p : s.Pos} {t₁ t₂ : String} (h : p.Splits t₁ t₂) :
t₁.toByteArray = s.copy.toByteArray.extract 0 p.offset.byteIdx := by
rw [h.eq_left p.splits]
@@ -597,6 +662,28 @@ theorem Pos.splits_append_rawEndPos {s t : String} :
eq_append := rfl
offset_eq_rawEndPos := rfl
/--
Given a slice `s` such that `s.copy = t₁ ++ t₂`, obtain the position sitting between `t₁` and `t₂`.
-/
def Slice.Pos.ofEqAppend {s : Slice} {t₁ t₂ : String} (h : s.copy = t₁ ++ t₂) : s.Pos :=
s.pos t₁.rawEndPos
(by simpa [ Pos.Raw.isValid_copy_iff, h] using ((Pos.Raw.isValid_rawEndPos).append_right t₂))
theorem Slice.Pos.splits_ofEqAppend {s : Slice} {t₁ t₂ : String} (h : s.copy = t₁ ++ t₂) :
(ofEqAppend h).Splits t₁ t₂ where
eq_append := h
offset_eq_rawEndPos := by simp [ofEqAppend]
/--
Given a string `s` such that `s = t₁ ++ t₂`, obtain the position sitting between `t₁` and `t₂`.
-/
def Pos.ofEqAppend {s t₁ t₂ : String} (h : s = t₁ ++ t₂) : s.Pos :=
((t₁ ++ t₂).pos t₁.rawEndPos ((Pos.Raw.isValid_rawEndPos).append_right t₂)).cast h.symm
theorem Pos.splits_ofEqAppend {s t₁ t₂ : String} (h : s = t₁ ++ t₂) : (ofEqAppend h).Splits t₁ t₂ where
eq_append := h
offset_eq_rawEndPos := by simp [ofEqAppend]
theorem Pos.Splits.copy_sliceTo_eq {s : String} {p : s.Pos} (h : p.Splits t₁ t₂) :
(s.sliceTo p).copy = t₁ :=
p.splits.eq_left h
@@ -649,4 +736,91 @@ theorem Slice.splits_slice {s : Slice} {p₀ p₁ : s.Pos} (h) (p : (s.slice p
p.Splits (s.slice p₀ (Pos.ofSlice p) Pos.le_ofSlice).copy (s.slice (Pos.ofSlice p) p₁ Pos.ofSlice_le).copy := by
simpa using p.splits
theorem Slice.Pos.Splits.nextn {s : Slice} {t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
(p.nextn n).Splits (t₁ ++ String.ofList (t₂.toList.take n)) (String.ofList (t₂.toList.drop n)) := by
induction n generalizing p t₁ t₂ with
| zero => simpa
| succ n ih =>
rw [Pos.nextn_add_one]
split
· simp_all
· obtain t₂, rfl := h.exists_eq_singleton_append _
simpa [ append_assoc] using ih h.next
theorem Slice.splits_nextn_startPos (s : Slice) (n : Nat) :
(s.startPos.nextn n).Splits (String.ofList (s.copy.toList.take n)) (String.ofList (s.copy.toList.drop n)) := by
simpa using s.splits_startPos.nextn n
theorem Pos.Splits.nextn {s t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (i : Nat) :
(p.nextn i).Splits (t₁ ++ String.ofList (t₂.toList.take i)) (String.ofList (t₂.toList.drop i)) := by
simpa [ splits_toSlice_iff, toSlice_nextn] using h.toSlice.nextn i
theorem splits_nextn_startPos (s : String) (n : Nat) :
(s.startPos.nextn n).Splits (String.ofList (s.toList.take n)) (String.ofList (s.toList.drop n)) := by
simpa using s.splits_startPos.nextn n
theorem Slice.Pos.Splits.prevn {s : Slice} {t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.length - n))) (String.ofList (t₁.toList.drop (t₁.length - n)) ++ t₂) := by
induction n generalizing p t₁ t₂ with
| zero => simpa [ String.length_toList]
| succ n ih =>
rw [Pos.prevn_add_one]
split
· simp_all
· obtain t₂, rfl := h.exists_eq_append_singleton_of_ne_startPos _
simpa [Nat.add_sub_add_right, List.take_append, List.drop_append, append_assoc] using ih h.prev
theorem Slice.splits_prevn_endPos (s : Slice) (n : Nat) :
(s.endPos.prevn n).Splits (String.ofList (s.copy.toList.take (s.copy.length - n)))
(String.ofList (s.copy.toList.drop (s.copy.length - n))) := by
simpa using s.splits_endPos.prevn n
theorem Pos.Splits.prevn {s t₁ t₂ : String} {p : s.Pos} (h : p.Splits t₁ t₂) (n : Nat) :
(p.prevn n).Splits (String.ofList (t₁.toList.take (t₁.length - n))) (String.ofList (t₁.toList.drop (t₁.length - n)) ++ t₂) := by
simpa [ splits_toSlice_iff, toSlice_prevn] using h.toSlice.prevn n
theorem splits_prevn_endPos (s : String) (n : Nat) :
(s.endPos.prevn n).Splits (String.ofList (s.toList.take (s.length - n))) (String.ofList (s.toList.drop (s.length - n))) := by
simpa using s.splits_endPos.prevn n
@[simp]
theorem Slice.copy_sliceFrom_cast {s t : Slice} (hst : s.copy = t.copy) {pos : s.Pos} :
(t.sliceFrom (pos.cast hst)).copy = (s.sliceFrom pos).copy := by
simpa [copy_sliceFrom_eq_iff_exists_splits] using _, pos.splits
@[simp]
theorem Slice.copy_sliceTo_cast {s t : Slice} (hst : s.copy = t.copy) {pos : s.Pos} :
(t.sliceTo (pos.cast hst)).copy = (s.sliceTo pos).copy := by
simpa [copy_sliceTo_eq_iff_exists_splits] using _, pos.splits
@[simp]
theorem copy_sliceFrom_cast {s t : String} (hst : s = t) {pos : s.Pos} :
(t.sliceFrom (pos.cast hst)).copy = (s.sliceFrom pos).copy := by
simpa [copy_sliceFrom_eq_iff_exists_splits] using _, pos.splits
@[simp]
theorem copy_sliceTo_cast {s t : String} (hst : s = t) {pos : s.Pos} :
(t.sliceTo (pos.cast hst)).copy = (s.sliceTo pos).copy := by
simpa [copy_sliceTo_eq_iff_exists_splits] using _, pos.splits
theorem Slice.Pos.sliceFrom_cast {s t : Slice} {hst : s.copy = t.copy} (p q : s.Pos) {h} :
Slice.Pos.sliceFrom (p.cast hst) (q.cast hst) h =
(Slice.Pos.sliceFrom p q (by simpa using h)).cast (by simp) := by
ext1; simp
theorem Slice.Pos.sliceTo_cast {s t : Slice} {hst : s.copy = t.copy} (p q : s.Pos) {h} :
Slice.Pos.sliceTo (p.cast hst) (q.cast hst) h =
(Slice.Pos.sliceTo p q (by simpa using h)).cast (by simp) := by
ext1; simp
theorem Pos.sliceFrom_cast {s t : String} {hst : s = t} (p q : s.Pos) {h} :
Pos.sliceFrom (p.cast hst) (q.cast hst) h =
(Pos.sliceFrom p q (by simpa using h)).cast (by simp) := by
ext1; simp
theorem Pos.sliceTo_cast {s t : String} {hst : s = t} (p q : s.Pos) {h} :
Pos.sliceTo (p.cast hst) (q.cast hst) h =
(Pos.sliceTo p q (by simpa using h)).cast (by simp) := by
ext1; simp
end String

View File

@@ -0,0 +1,49 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Init.Data.String.Basic
public import Init.Data.Order.Classes
import Init.Data.List.Lex
import Init.Data.Char.Lemmas
import Init.Data.Char.Order
import Init.Data.Order.Factories
import Init.Data.Order.Lemmas
public section
open Std
namespace String
@[simp] protected theorem not_le {a b : String} : ¬ a b b < a := Decidable.not_not
@[simp] protected theorem not_lt {a b : String} : ¬ a < b b a := Iff.rfl
@[simp] protected theorem le_refl (a : String) : a a := List.le_refl _
@[simp] protected theorem lt_irrefl (a : String) : ¬ a < a := List.lt_irrefl _
attribute [local instance] Char.notLTTrans Char.ltTrichotomous Char.ltAsymm
protected theorem le_trans {a b c : String} : a b b c a c := List.le_trans
protected theorem lt_trans {a b c : String} : a < b b < c a < c := List.lt_trans
protected theorem le_total (a b : String) : a b b a := List.le_total _ _
protected theorem le_antisymm {a b : String} : a b b a a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.toList) (bs := b.toList) h₁ h₂)
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
protected theorem ne_of_lt {a b : String} (h : a < b) : a b := by
have := String.lt_irrefl a
intro h; subst h; contradiction
instance instIsLinearOrder : IsLinearOrder String := by
apply IsLinearOrder.of_le
case le_antisymm => constructor; apply String.le_antisymm
case le_trans => constructor; apply String.le_trans
case le_total => constructor; apply String.le_total
instance : LawfulOrderLT String where
lt_iff a b := by
simp [ String.not_le, Decidable.imp_iff_not_or, Std.Total.total]
end String

View File

@@ -0,0 +1,86 @@
/-
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Julia Markus Himmel
-/
module
prelude
public import Init.Data.String.TakeDrop
import all Init.Data.String.Slice
import all Init.Data.String.TakeDrop
import Init.Data.String.Lemmas.Splits
public section
namespace String
namespace Slice
theorem drop_eq_sliceFrom {s : Slice} {n : Nat} : s.drop n = s.sliceFrom (s.startPos.nextn n) :=
(rfl)
@[simp]
theorem toList_copy_drop {s : Slice} {n : Nat} : (s.drop n).copy.toList = s.copy.toList.drop n := by
simp [drop_eq_sliceFrom, (s.splits_nextn_startPos n).copy_sliceFrom_eq]
theorem dropEnd_eq_sliceTo {s : Slice} {n : Nat} : s.dropEnd n = s.sliceTo (s.endPos.prevn n) :=
(rfl)
@[simp]
theorem toList_copy_dropEnd {s : Slice} {n : Nat} :
(s.dropEnd n).copy.toList = s.copy.toList.take (s.copy.length - n) := by
simp [dropEnd_eq_sliceTo, (s.splits_prevn_endPos n).copy_sliceTo_eq]
theorem take_eq_sliceTo {s : Slice} {n : Nat} : s.take n = s.sliceTo (s.startPos.nextn n) :=
(rfl)
@[simp]
theorem toList_copy_take {s : Slice} {n : Nat} : (s.take n).copy.toList = s.copy.toList.take n := by
simp [take_eq_sliceTo, (s.splits_nextn_startPos n).copy_sliceTo_eq]
theorem takeEnd_eq_sliceFrom {s : Slice} {n : Nat} : s.takeEnd n = s.sliceFrom (s.endPos.prevn n) :=
(rfl)
@[simp]
theorem toList_copy_takeEnd {s : Slice} {n : Nat} :
(s.takeEnd n).copy.toList = s.copy.toList.drop (s.copy.length - n) := by
simp [takeEnd_eq_sliceFrom, (s.splits_prevn_endPos n).copy_sliceFrom_eq]
end Slice
@[simp]
theorem drop_toSlice {s : String} {n : Nat} : s.toSlice.drop n = s.drop n :=
(rfl)
@[simp]
theorem toList_copy_drop {s : String} {n : Nat} : (s.drop n).copy.toList = s.toList.drop n := by
simp [ drop_toSlice]
@[simp]
theorem dropEnd_toSlice {s : String} {n : Nat} : s.toSlice.dropEnd n = s.dropEnd n :=
(rfl)
@[simp]
theorem toList_copy_dropEnd {s : String} {n : Nat} :
(s.dropEnd n).copy.toList = s.toList.take (s.length - n) := by
simp [ dropEnd_toSlice]
@[simp]
theorem take_toSlice {s : String} {n : Nat} : s.toSlice.take n = s.take n :=
(rfl)
@[simp]
theorem toList_copy_take {s : String} {n : Nat} : (s.take n).copy.toList = s.toList.take n := by
simp [ take_toSlice]
@[simp]
theorem takeEnd_toSlice {s : String} {n : Nat} : s.toSlice.takeEnd n = s.takeEnd n :=
(rfl)
@[simp]
theorem toList_copy_takeEnd {s : String} {n : Nat} :
(s.takeEnd n).copy.toList = s.toList.drop (s.length - n) := by
simp [ takeEnd_toSlice]
end String

View File

@@ -96,6 +96,44 @@ theorem endPos_ofSliceFrom {s : Slice} {p : s.Pos} {st : SearchStep (s.sliceFrom
st.ofSliceFrom.endPos = Slice.Pos.ofSliceFrom st.endPos := by
cases st <;> simp [ofSliceFrom]
/--
Converts a {lean}`SearchStep s` into a {lean}`SearchStep t` by applying {name}`Slice.Pos.cast` to the
start and end position.
-/
@[inline]
def cast {s t : Slice} (hst : s.copy = t.copy) : SearchStep s SearchStep t
| .rejected startPos endPos => .rejected (startPos.cast hst) (endPos.cast hst)
| .matched startPos endPos => .matched (startPos.cast hst) (endPos.cast hst)
@[simp]
theorem cast_rejected {s t : Slice} {hst : s.copy = t.copy} {startPos endPos : s.Pos} :
(SearchStep.rejected startPos endPos).cast hst = .rejected (startPos.cast hst) (endPos.cast hst) :=
(rfl)
@[simp]
theorem cast_matched {s t : Slice} {hst : s.copy = t.copy} {startPos endPos : s.Pos} :
(SearchStep.matched startPos endPos).cast hst = .matched (startPos.cast hst) (endPos.cast hst) :=
(rfl)
@[simp]
theorem startPos_cast {s t : Slice} (hst : s.copy = t.copy) {st : SearchStep s} :
(st.cast hst).startPos = st.startPos.cast hst := by
cases st <;> simp
@[simp]
theorem endPos_cast {s t : Slice} (hst : s.copy = t.copy) {st : SearchStep s} :
(st.cast hst).endPos = st.endPos.cast hst := by
cases st <;> simp
@[simp]
theorem cast_rfl {s : Slice} {st : SearchStep s} : st.cast rfl = st := by
cases st <;> simp
@[simp]
theorem cast_cast {s t u : Slice} {hst : s.copy = t.copy} {htu : t.copy = u.copy} {st : SearchStep s} :
(st.cast hst).cast htu = st.cast (hst.trans htu) := by
cases st <;> simp
end SearchStep
/--
@@ -117,7 +155,7 @@ class ForwardPattern {ρ : Type} (pat : ρ) where
-/
startsWith : (s : Slice) Bool := fun s => (skipPrefix? s).isSome
@[deprecated ForwardPattern.dropPrefix? (since := "2026-03-19")]
@[deprecated ForwardPattern.skipPrefix? (since := "2026-03-19")]
def ForwardPattern.dropPrefix? {ρ : Type} (pat : ρ) [ForwardPattern pat] (s : Slice) : Option s.Pos :=
ForwardPattern.skipPrefix? pat s

View File

@@ -47,8 +47,8 @@ instance {c : Char} : LawfulBackwardPattern c where
skipSuffixOfNonempty?_eq h := LawfulBackwardPattern.skipSuffixOfNonempty?_eq (pat := (· == c)) h
endsWith_eq s := LawfulBackwardPattern.endsWith_eq (pat := (· == c)) s
instance {c : Char} : ToBackwardSearcher c (ToBackwardSearcher.DefaultBackwardSearcher c) :=
.defaultImplementation
instance {c : Char} : ToBackwardSearcher c (ToBackwardSearcher.DefaultBackwardSearcher (· == c)) where
toSearcher s := ToBackwardSearcher.toSearcher (· == c) s
end Char

View File

@@ -139,8 +139,9 @@ instance {p : Char → Prop} [DecidablePred p] : LawfulBackwardPattern p where
skipSuffixOfNonempty?_eq h := LawfulBackwardPattern.skipSuffixOfNonempty?_eq (pat := (decide <| p ·)) h
endsWith_eq s := LawfulBackwardPattern.endsWith_eq (pat := (decide <| p ·)) s
instance {p : Char Prop} [DecidablePred p] : ToBackwardSearcher p (ToBackwardSearcher.DefaultBackwardSearcher p) :=
.defaultImplementation
instance {p : Char Prop} [DecidablePred p] :
ToBackwardSearcher p (ToBackwardSearcher.DefaultBackwardSearcher (decide <| p ·)) where
toSearcher s := ToBackwardSearcher.toSearcher (decide <| p ·) s
end Decidable

View File

@@ -311,23 +311,6 @@ def Internal.containsImpl (s : String) (c : Char) : Bool :=
def Internal.anyImpl (s : String) (p : Char Bool) :=
String.any s p
/--
Checks whether a slice only consists of matches of the pattern {name}`pat`.
Short-circuits at the first pattern mis-match.
This function is generic over all currently supported patterns.
Examples:
* {lean}`"brown".all Char.isLower = true`
* {lean}`"brown and orange".all Char.isLower = false`
* {lean}`"aaaaaa".all 'a' = true`
* {lean}`"aaaaaa".all "aa" = true`
* {lean}`"aaaaaaa".all "aa" = false`
-/
@[inline, suggest_for String.every] def all (s : String) (pat : ρ) [ForwardPattern pat] : Bool :=
s.toSlice.all pat
/--
Checks whether the string can be interpreted as the decimal representation of a natural number.

View File

@@ -11,7 +11,7 @@ public import Init.Data.Ord.Basic
public import Init.Data.Iterators.Combinators.FilterMap
public import Init.Data.String.ToSlice
public import Init.Data.String.Subslice
public import Init.Data.String.Iter
public import Init.Data.String.Iter.Basic
public import Init.Data.String.Iterate
import Init.Data.Iterators.Consumers.Collect
import Init.Data.Iterators.Consumers.Loop
@@ -84,10 +84,11 @@ instance : ToString String.Slice where
theorem toStringToString_eq : ToString.toString = String.Slice.copy := (rfl)
@[extern "lean_slice_hash"]
opaque hash (s : @& Slice) : UInt64
protected def hash (s : @& Slice) : UInt64 :=
String.hash s.copy
instance : Hashable Slice where
hash := hash
hash := Slice.hash
instance : LT Slice where
lt x y := x.copy < y.copy
@@ -425,13 +426,13 @@ Advances {name}`pos` as long as {name}`pat` matches.
-/
@[specialize pat]
def Pos.skipWhile {s : Slice} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : s.Pos :=
if let some nextCurr := ForwardPattern.skipPrefix? pat (s.sliceFrom pos) then
if pos < Pos.ofSliceFrom nextCurr then
skipWhile (Pos.ofSliceFrom nextCurr) pat
match pos.skip? pat with
| some nextCurr =>
if pos < nextCurr then
skipWhile nextCurr pat
else
pos
else
pos
| none => pos
termination_by pos
/--
@@ -571,7 +572,7 @@ Examples:
-/
@[inline]
def all (s : Slice) (pat : ρ) [ForwardPattern pat] : Bool :=
s.dropWhile pat |>.isEmpty
s.skipPrefixWhile pat == s.endPos
end ForwardPatternUsers
@@ -705,14 +706,14 @@ Returns {name}`none` otherwise.
This function is generic over all currently supported patterns.
-/
@[inline]
def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos :=
((s.sliceFrom pos).skipPrefix? pat).map Pos.ofSliceFrom
def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos :=
((s.sliceTo pos).skipSuffix? pat).map Pos.ofSliceTo
/--
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
Use {name (scope := "Init.Data.String.Slice")}`String.Slice.dropSuffix` to return the slice
unchanged when {name}`pat` does not match a prefix.
unchanged when {name}`pat` does not match a suffix.
This function is generic over all currently supported patterns.
@@ -764,23 +765,53 @@ Rewinds {name}`pos` as long as {name}`pat` matches.
-/
@[specialize pat]
def Pos.revSkipWhile {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : s.Pos :=
if let some nextCurr := BackwardPattern.skipSuffix? pat (s.sliceTo pos) then
if Pos.ofSliceTo nextCurr < pos then
revSkipWhile (Pos.ofSliceTo nextCurr) pat
match pos.revSkip? pat with
| some nextCurr =>
if nextCurr < pos then
revSkipWhile nextCurr pat
else
pos
else
pos
| none => pos
termination_by pos.down
/--
Returns the position a the start of the longest suffix of {name}`s` for which {name}`pat` matches
Returns the position at the start of the longest suffix of {name}`s` for which {name}`pat` matches
(potentially repeatedly).
-/
@[inline]
def skipSuffixWhile (s : Slice) (pat : ρ) [BackwardPattern pat] : s.Pos :=
s.endPos.revSkipWhile pat
/--
Checks whether a slice only consists of matches of the pattern {name}`pat`, starting from the back
of the string.
Short-circuits at the first pattern mis-match.
This function is generic over all currently supported patterns.
For many types of patterns, this function can be expected to return the same result as
{name}`Slice.all`. If mismatches are expected to occur close to the end of the string, this function
might be more efficient.
For some types of patterns, this function will return a different result than {name}`Slice.all`.
Consider, for example, a pattern that matches the longest string at the given position that matches
the regular expression {lean}`"a|aa|ab"`. Then, given the input string {lean}`"aab"`, performing
{name}`Slice.all` will greedily match the prefix {lean}`"aa"` and then get stuck on the remainder
{lean}`"b"`, causing it to return {lean}`false`. On the other hand, {name}`Slice.revAll` will match
the suffix {lean}`"ab"` and then match the remainder {lean}`"a"`, so it will return {lean}`true`.
Examples:
* {lean}`"brown".toSlice.revAll Char.isLower = true`
* {lean}`"brown and orange".toSlice.revAll Char.isLower = false`
* {lean}`"aaaaaa".toSlice.revAll 'a' = true`
* {lean}`"aaaaaa".toSlice.revAll "aa" = true`
* {lean}`"aaaaaaa".toSlice.revAll "aa" = false`
-/
@[inline]
def revAll (s : Slice) (pat : ρ) [BackwardPattern pat] : Bool :=
s.skipSuffixWhile pat == s.startPos
/--
Creates a new slice that contains the longest suffix of {name}`s` for which {name}`pat` matched
(potentially repeatedly).
@@ -1151,6 +1182,19 @@ where go (acc : String) (s : Slice) : List Slice → String
| a :: as => go (acc ++ s ++ a) s as
| [] => acc
/--
Appends all the slices in a list of slices, in order.
Use {name}`String.Slice.intercalate` to place a separator string between the strings in a list.
Examples:
* {lean}`String.Slice.join ["gr", "ee", "n"] = "green"`
* {lean}`String.Slice.join ["b", "", "l", "", "ue"] = "blue"`
* {lean}`String.Slice.join [] = ""`
-/
def join (l : List String.Slice) : String :=
l.foldl (fun (r : String) (s : String.Slice) => r ++ s) ""
/--
Converts a string to the Lean compiler's representation of names. The resulting name is
hierarchical, and the string is split at the dots ({lean}`'.'`).

View File

@@ -23,7 +23,7 @@ Given a {name}`Slice` {name}`s`, the type {lean}`s.Subslice` is the type of half
in {name}`s` delineated by a valid position on both sides.
This type is useful to track regions of interest within some larger slice that is also of interest.
In contrast, {name}`Slice` is used to track regions of interest whithin some larger string that is
In contrast, {name}`Slice` is used to track regions of interest within some larger string that is
not or no longer relevant.
Equality on {name}`Subslice` is somewhat better behaved than on {name}`Slice`, but note that there

View File

@@ -224,6 +224,53 @@ Returns the position after the longest prefix of {name}`s` for which {name}`pat`
@[inline] def skipPrefixWhile (s : String) (pat : ρ) [ForwardPattern pat] : s.Pos :=
Pos.ofToSlice (s.toSlice.skipPrefixWhile pat)
/--
Checks whether a string only consists of matches of the pattern {name}`pat`.
Short-circuits at the first pattern mis-match.
This function is generic over all currently supported patterns.
Examples:
* {lean}`"brown".all Char.isLower = true`
* {lean}`"brown and orange".all Char.isLower = false`
* {lean}`"aaaaaa".all 'a' = true`
* {lean}`"aaaaaa".all "aa" = true`
* {lean}`"aaaaaaa".all "aa" = false`
-/
@[inline, suggest_for String.every] def all (s : String) (pat : ρ) [ForwardPattern pat] : Bool :=
s.toSlice.all pat
/--
Checks whether a string only consists of matches of the pattern {name}`pat`, starting from the back
of the string.
Short-circuits at the first pattern mis-match.
This function is generic over all currently supported patterns.
For many types of patterns, this function can be expected to return the same result as
{name}`String.all`. If mismatches are expected to occur close to the end of the string, this function
might be more efficient.
For some types of patterns, this function will return a different result than {name}`String.all`.
Consider, for example, a pattern that matches the longest string at the given position that matches
the regular expression {lean}`"a|aa|ab"`. Then, given the input string {lean}`"aab"`, performing
{name}`String.all` will greedily match the prefix {lean}`"aa"` and then get stuck on the remainder
{lean}`"b"`, causing it to return {lean}`false`. On the other hand, {name}`String.revAll` will match
the suffix {lean}`"ab"` and then match the remainder {lean}`"a"`, so it will return {lean}`true`.
Examples:
* {lean}`"brown".revAll Char.isLower = true`
* {lean}`"brown and orange".revAll Char.isLower = false`
* {lean}`"aaaaaa".revAll 'a' = true`
* {lean}`"aaaaaa".revAll "aa" = true`
* {lean}`"aaaaaaa".revAll "aa" = false`
-/
@[inline]
def revAll (s : String) (pat : ρ) [BackwardPattern pat] : Bool :=
s.toSlice.revAll pat
/--
If {name}`pat` matches at {name}`pos`, returns the position after the end of the match.
Returns {name}`none` otherwise.
@@ -314,7 +361,7 @@ Returns {name}`none` otherwise.
This function is generic over all currently supported patterns.
-/
@[inline]
def Pos.revSkip? {s : String} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos :=
def Pos.revSkip? {s : String} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos :=
(pos.toSlice.revSkip? pat).map Pos.ofToSlice
/--
@@ -461,7 +508,7 @@ def dropPrefix? (s : String) (pat : ρ) [ForwardPattern pat] : Option String.Sli
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
Use {name (scope := "Init.Data.String.TakeDrop")}`String.dropSuffix` to return the slice
unchanged when {name}`pat` does not match a prefix.
unchanged when {name}`pat` does not match a suffix.
This is a cheap operation because it does not allocate a new string to hold the result.
To convert the result into a string, use {name}`String.Slice.copy`.

View File

@@ -506,6 +506,16 @@ Examples:
@[inline, expose] def sum [Add α] [Zero α] (xs : Vector α n) : α :=
xs.toArray.sum
/--
Computes the product of the elements of a vector.
Examples:
* `#v[a, b, c].prod = a * (b * (c * 1))`
* `#v[1, 2, 5].prod = 10`
-/
@[inline, expose] def prod [Mul α] [One α] (xs : Vector α n) : α :=
xs.toArray.prod
/--
Pad a vector on the left with a given element.

View File

@@ -30,4 +30,16 @@ theorem sum_reverse_int (xs : Vector Int n) : xs.reverse.sum = xs.sum := by
theorem sum_eq_foldl_int {xs : Vector Int n} : xs.sum = xs.foldl (b := 0) (· + ·) := by
simp only [foldl_eq_foldr_reverse, Int.add_comm, sum_eq_foldr, sum_reverse_int]
@[simp] theorem prod_replicate_int {n : Nat} {a : Int} : (replicate n a).prod = a ^ n := by
simp [ prod_toArray, Array.prod_replicate_int]
theorem prod_append_int {as₁ as₂ : Vector Int n} : (as₁ ++ as₂).prod = as₁.prod * as₂.prod := by
simp [ prod_toArray]
theorem prod_reverse_int (xs : Vector Int n) : xs.reverse.prod = xs.prod := by
simp [prod_reverse]
theorem prod_eq_foldl_int {xs : Vector Int n} : xs.prod = xs.foldl (b := 1) (· * ·) := by
simp only [foldl_eq_foldr_reverse, Int.mul_comm, prod_eq_foldr, prod_reverse_int]
end Vector

View File

@@ -278,6 +278,12 @@ theorem toArray_mk {xs : Array α} (h : xs.size = n) : (Vector.mk xs h).toArray
@[simp, grind =] theorem sum_toArray [Add α] [Zero α] {xs : Vector α n} :
xs.toArray.sum = xs.sum := rfl
@[simp] theorem prod_mk [Mul α] [One α] {xs : Array α} (h : xs.size = n) :
(Vector.mk xs h).prod = xs.prod := rfl
@[simp, grind =] theorem prod_toArray [Mul α] [One α] {xs : Vector α n} :
xs.toArray.prod = xs.prod := rfl
@[simp] theorem eq_mk : xs = Vector.mk as h xs.toArray = as := by
cases xs
simp
@@ -551,6 +557,10 @@ theorem toArray_toList {xs : Vector α n} : xs.toList.toArray = xs.toArray := rf
xs.toList.sum = xs.sum := by
rw [ toList_toArray, Array.sum_toList, sum_toArray]
@[simp, grind =] theorem prod_toList [Mul α] [One α] {xs : Vector α n} :
xs.toList.prod = xs.prod := by
rw [ toList_toArray, Array.prod_toList, prod_toArray]
@[simp] theorem getElem_toList {xs : Vector α n} {i : Nat} (h : i < xs.toList.length) :
xs.toList[i] = xs[i]'(by simpa using h) := by
cases xs
@@ -3134,3 +3144,39 @@ theorem sum_eq_foldl [Zero α] [Add α]
{xs : Vector α n} :
xs.sum = xs.foldl (b := 0) (· + ·) := by
simp [ sum_toList, List.sum_eq_foldl]
/-! ### prod -/
@[simp, grind =] theorem prod_empty [Mul α] [One α] : (#v[] : Vector α 0).prod = 1 := rfl
theorem prod_eq_foldr [Mul α] [One α] {xs : Vector α n} :
xs.prod = xs.foldr (b := 1) (· * ·) :=
rfl
@[simp, grind =]
theorem prod_append [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.LeftIdentity (α := α) (· * ·) 1] [Std.LawfulLeftIdentity (α := α) (· * ·) 1]
{as₁ as₂ : Vector α n} : (as₁ ++ as₂).prod = as₁.prod * as₂.prod := by
simp [ prod_toList, List.prod_append]
@[simp, grind =]
theorem prod_singleton [Mul α] [One α] [Std.LawfulRightIdentity (· * ·) (1 : α)] {x : α} :
#v[x].prod = x := by
simp [ prod_toList, Std.LawfulRightIdentity.right_id x]
@[simp, grind =]
theorem prod_push [Mul α] [One α] [Std.Associative (α := α) (· * ·)]
[Std.LawfulIdentity (· * ·) (1 : α)] {xs : Vector α n} {x : α} :
(xs.push x).prod = xs.prod * x := by
simp [ prod_toArray]
@[simp, grind =]
theorem prod_reverse [One α] [Mul α] [Std.Associative (α := α) (· * ·)]
[Std.Commutative (α := α) (· * ·)]
[Std.LawfulLeftIdentity (α := α) (· * ·) 1] (xs : Vector α n) : xs.reverse.prod = xs.prod := by
simp [ prod_toList, List.prod_reverse]
theorem prod_eq_foldl [One α] [Mul α]
[Std.Associative (α := α) (· * ·)] [Std.LawfulIdentity (· * ·) (1 : α)]
{xs : Vector α n} :
xs.prod = xs.foldl (b := 1) (· * ·) := by
simp [ prod_toList, List.prod_eq_foldl]

View File

@@ -37,4 +37,23 @@ theorem sum_reverse_nat (xs : Vector Nat n) : xs.reverse.sum = xs.sum := by
theorem sum_eq_foldl_nat {xs : Vector Nat n} : xs.sum = xs.foldl (b := 0) (· + ·) := by
simp only [foldl_eq_foldr_reverse, Nat.add_comm, sum_eq_foldr, sum_reverse_nat]
protected theorem prod_pos_iff_forall_pos_nat {xs : Vector Nat n} : 0 < xs.prod x xs, 0 < x := by
simp [ prod_toArray, Array.prod_pos_iff_forall_pos_nat]
protected theorem prod_eq_zero_iff_exists_zero_nat {xs : Vector Nat n} :
xs.prod = 0 x xs, x = 0 := by
simp [ prod_toArray, Array.prod_eq_zero_iff_exists_zero_nat]
@[simp] theorem prod_replicate_nat {n : Nat} {a : Nat} : (replicate n a).prod = a ^ n := by
simp [ prod_toArray, Array.prod_replicate_nat]
theorem prod_append_nat {as₁ as₂ : Vector Nat n} : (as₁ ++ as₂).prod = as₁.prod * as₂.prod := by
simp [ prod_toArray]
theorem prod_reverse_nat (xs : Vector Nat n) : xs.reverse.prod = xs.prod := by
simp [prod_reverse]
theorem prod_eq_foldl_nat {xs : Vector Nat n} : xs.prod = xs.foldl (b := 1) (· * ·) := by
simp only [foldl_eq_foldr_reverse, Nat.mul_comm, prod_eq_foldr, prod_reverse_nat]
end Vector

View File

@@ -107,6 +107,9 @@ syntax (name := showLocalThms) "show_local_thms" : grind
-/
syntax (name := showTerm) "show_term " grindSeq : grind
/-- Shows the pending goals. -/
syntax (name := showGoals) "show_goals" : grind
declare_syntax_cat grind_ref (behavior := both)
syntax:max anchor : grind_ref
@@ -315,5 +318,8 @@ Only available in `sym =>` mode.
-/
syntax (name := symSimp) "simp" (ppSpace colGt ident)? (" [" ident,* "]")? : grind
/-- `exact e` closes the main goal if its target type matches that of `e`. -/
macro "exact " e:term : grind => `(grind| tactic => exact $e:term)
end Grind
end Lean.Parser.Tactic

View File

@@ -564,6 +564,28 @@ end Ring
end IsCharP
/--
`PowIdentity α p` states that `x ^ p = x` holds for all elements of `α`.
The primary source of instances is Fermat's little theorem: for a finite field with `q` elements,
`x ^ q = x` for every `x`. For `Fin p` or `ZMod p` with prime `p`, this gives `x ^ p = x`.
The `grind` ring solver uses this typeclass to add the relation `x ^ p - x = 0` to the
Groebner basis, which allows it to reduce high-degree polynomials. Mathlib can provide
instances for general finite fields via `FiniteField.pow_card`.
-/
class PowIdentity (α : Type u) [CommSemiring α] (p : outParam Nat) : Prop where
/-- Every element satisfies `x ^ p = x`. -/
pow_eq (x : α) : x ^ p = x
namespace PowIdentity
variable [CommSemiring α] [PowIdentity α p]
theorem pow (x : α) : x ^ p = x := pow_eq x
end PowIdentity
open AddCommGroup
theorem no_int_zero_divisors {α : Type u} [IntModule α] [NoNatZeroDivisors α] {k : Int} {a : α}

View File

@@ -30,7 +30,13 @@ simpMatchDiscrsOnly (match 0 with | 0 => true | _ => false) = true
```
using `eq_self`.
-/
def simpMatchDiscrsOnly {α : Sort u} (a : α) : α := a
@[expose] def simpMatchDiscrsOnly {α : Sort u} (a : α) : α := a
/--
Gadget for protecting lambda abstractions created by `abstractGroundMismatches?`
from beta reduction during preprocessing. See `ProveEq.lean` for details.
-/
@[expose] def abstractFn {α : Sort u} (a : α) : α := a
/-- Gadget for representing offsets `t+k` in patterns. -/
def offset (a b : Nat) : Nat := a + b

View File

@@ -156,6 +156,12 @@ instance [i : NeZero n] : ToInt.Pow (Fin n) (.co 0 n) where
rw [pow_succ, ToInt.Mul.toInt_mul, ih, ToInt.wrap_toInt,
IntInterval.wrap_mul (by simp), Int.pow_succ, ToInt.wrap_toInt]
instance : PowIdentity (Fin 2) 2 where
pow_eq x := by
match x with
| 0, _ => rfl
| 1, _ => rfl
end Fin
end Lean.Grind

View File

@@ -624,6 +624,23 @@ existing code. It may be removed in a future version of the library.
syntax (name := deprecated) "deprecated" (ppSpace ident)? (ppSpace str)?
(" (" &"since" " := " str ")")? : attr
/--
The attribute `@[deprecated_arg old new]` marks a named parameter as deprecated.
When a caller uses the old name with a replacement available, a deprecation warning is emitted
and the argument is silently forwarded to the new parameter. When no replacement is provided,
the parameter is treated as removed and using it produces an error.
* `@[deprecated_arg old new (since := "2026-03-18")]` marks `old` as a deprecated alias for `new`.
* `@[deprecated_arg old new "use foo instead" (since := "2026-03-18")]` adds a custom message.
* `@[deprecated_arg old (since := "2026-03-18")]` marks `old` as a removed parameter (no replacement).
* `@[deprecated_arg old "no longer needed" (since := "2026-03-18")]` removed with a custom message.
A warning is emitted if `(since := "...")` is omitted.
-/
syntax (name := deprecated_arg) "deprecated_arg" ppSpace ident (ppSpace ident)? (ppSpace str)?
(" (" &"since" " := " str ")")? : attr
/--
The attribute `@[suggest_for ..]` on a declaration suggests likely ways in which
someone might **incorrectly** refer to a definition.

View File

@@ -36,9 +36,6 @@ private local instance : ToString Int where
private local instance : Repr Int where
reprPrec i prec := if i < 0 then Repr.addAppParen (toString i) prec else toString i
private local instance : Append String where
append := String.Internal.append
/-- Internal representation of a linear combination of atoms, and a constant term. -/
structure LinearCombo where
/-- Constant term. -/

View File

@@ -145,7 +145,7 @@ Examples:
The constant function that ignores its argument.
If `a : α`, then `Function.const β a : β → α` is the “constant function with value `a`”. For all
arguments `b : β`, `Function.const β a b = a`.
arguments `b : β`, `Function.const β a b = a`. It is often written directly as `fun _ => a`.
Examples:
* `Function.const Bool 10 true = 10`
@@ -185,15 +185,36 @@ example : foo.default = (default, default) :=
abbrev inferInstance {α : Sort u} [i : α] : α := i
set_option checkBinderAnnotations false in
/-- `inferInstanceAs α` synthesizes an instance of type `α` and normalizes it to
"instance normal form": the result is a constructor application whose sub-instance fields
are canonical instances and whose types match `α` exactly. This is useful when `α` is
definitionally equal to some `α'` for which instances are registered, as it prevents
leaking the definition's RHS at lower transparencies. See `Lean.Meta.InstanceNormalForm`
for details. Example:
/--
`inferInstanceAs α` synthesizes an instance of type `α` and then adjusts it to conform to the
expected type `β`, which must be inferable from context.
Example:
```
#check inferInstanceAs (Inhabited Nat) -- Inhabited Nat
def D := Nat
instance : Inhabited D := inferInstanceAs (Inhabited Nat)
```
The adjustment will make sure that when the resulting instance will not "leak" the RHS `Nat` when
reduced at transparency levels below `semireducible`, i.e. where `D` would not be unfolded either,
preventing "defeq abuse".
More specifically, given the "source type" (the argument) and "target type" (the expected type),
`inferInstanceAs` synthesizes an instance for the source type and then unfolds and rewraps 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)
If you just need to synthesize an instance without transporting between types, use `inferInstance`
instead, potentially with a type annotation for the expected type.
-/
abbrev «inferInstanceAs» (α : Sort u) [i : α] : α := i
@@ -3261,7 +3282,7 @@ Version of `Array.get!Internal` that does not increment the reference count of i
This is only intended for direct use by the compiler.
-/
@[extern "lean_array_get_borrowed"]
unsafe opaque Array.get!InternalBorrowed {α : Type u} [Inhabited α] (a : @& Array α) (i : @& Nat) : α
unsafe opaque Array.get!InternalBorrowed {α : Type u} [@&Inhabited α] (a : @& Array α) (i : @& Nat) : α
/--
Use the indexing notation `a[i]!` instead.
@@ -3269,7 +3290,7 @@ Use the indexing notation `a[i]!` instead.
Access an element from an array, or panic if the index is out of bounds.
-/
@[extern "lean_array_get"]
def Array.get!Internal {α : Type u} [Inhabited α] (a : @& Array α) (i : @& Nat) : α :=
def Array.get!Internal {α : Type u} [@&Inhabited α] (a : @& Array α) (i : @& Nat) : α :=
Array.getD a i default
/--
@@ -3648,8 +3669,8 @@ will prevent the actual monad from being "copied" to the code being specialized.
When we reimplement the specializer, we may consider copying `inst` if it also
occurs outside binders or if it is an instance.
-/
@[never_extract, extern "lean_panic_fn"]
def panicCore {α : Sort u} [Inhabited α] (msg : String) : α := default
@[never_extract, extern "lean_panic_fn_borrowed"]
def panicCore {α : Sort u} [@&Inhabited α] (msg : String) : α := default
/--
`(panic "msg" : α)` has a built-in implementation which prints `msg` to
@@ -3667,7 +3688,7 @@ def panic {α : Sort u} [Inhabited α] (msg : String) : α :=
panicCore msg
-- TODO: this be applied directly to `Inhabited`'s definition when we remove the above workaround
attribute [nospecialize] Inhabited
attribute [weak_specialize] Inhabited
/--
The `>>=` operator is overloaded via instances of `bind`.
@@ -3733,7 +3754,7 @@ class Functor (f : Type u → Type v) : Type (max (u+1) v) where
/--
Mapping a constant function.
Given `a : α` and `v : f α`, `mapConst a v` is equivalent to `Function.const _ a <$> v`. For some
Given `a : α` and `v : f β`, `mapConst a v` is equivalent to `(fun _ => a) <$> v`. For some
functors, this can be implemented more efficiently; for all other functors, the default
implementation may be used.
-/
@@ -4082,7 +4103,7 @@ Actions in the resulting monad are functions that take the local value as a para
ordinary actions in `m`.
-/
def ReaderT (ρ : Type u) (m : Type u Type v) (α : Type u) : Type (max u v) :=
ρ m α
(a : @&ρ) m α
/--
Interpret `ρ → m α` as an element of `ReaderT ρ m α`.

View File

@@ -49,6 +49,14 @@ syntax (name := ground) "ground" : sym_simproc
/-- Simplify telescope binders but not the final body. -/
syntax (name := telescope) "telescope" : sym_simproc
/-- Simplify control-flow expressions (`if-then-else`, `match`, `cond`, `dite`).
Visits only conditions and discriminants. Intended as a `pre` simproc. -/
syntax (name := control) "control" : sym_simproc
/-- Simplify arrow telescopes (`p₁ → p₂ → ... → q`) without entering binders.
Simplifies each `pᵢ` and `q` individually. Intended as a `pre` simproc. -/
syntax (name := arrowTelescope) "arrow_telescope" : sym_simproc
/-- Rewrite using a named theorem set. Optionally specify a discharger for conditional rewrites. -/
syntax (name := rewriteSet) "rewrite" ident (" with " sym_discharger)? : sym_simproc

View File

@@ -1880,3 +1880,12 @@ lead to undefined behavior.
-/
@[extern "lean_runtime_forget"]
def Runtime.forget (a : α) : BaseIO Unit := return
set_option linter.unusedVariables false in
/--
Ensures `a` remains at least alive until the call site by holding a reference to `a`. This can be useful
for unsafe code (such as an FFI) that relies on a Lean object not being freed until after some point
in the program. At runtime, this will be a no-op as the C compiler will optimize away this call.
-/
@[extern "lean_runtime_hold"]
def Runtime.hold (a : @& α) : BaseIO Unit := return

View File

@@ -2259,42 +2259,6 @@ with grind
```
This is more convenient than the equivalent `· by rename_i _ acc _; exact I1 acc`.
### Witnesses
When a specification has a parameter whose type is tagged with `@[mvcgen_witness_type]`, `mvcgen`
classifies the corresponding goal as a *witness* rather than a verification condition.
Witnesses are concrete values that the user must provide (inspired by zero-knowledge proofs),
as opposed to invariants (predicates maintained across loop iterations) or verification conditions
(propositions to prove).
Witness goals are labelled `witness1`, `witness2`, etc. and can be provided in a `witnesses` section
that appears before the `invariants` section:
```
mvcgen [...] witnesses
· W1
· W2
invariants
· I1
with grind
```
Like invariants, witnesses support case label syntax:
```
mvcgen [...] witnesses
| witness1 => W1
```
See the `@[mvcgen_witness_type]` attribute for how to register custom witness types.
### Invariant and witness type attributes
The `@[mvcgen_invariant_type]` and `@[mvcgen_witness_type]` tag attributes control how `mvcgen`
classifies subgoals:
* A goal whose type is an application of a type tagged with `@[mvcgen_invariant_type]` is classified
as an invariant (`inv<n>`).
* A goal whose type is an application of a type tagged with `@[mvcgen_witness_type]` is classified
as a witness (`witness<n>`).
* All other goals are classified as verification conditions (`vc<n>`).
### Invariant suggestions
`mvcgen` will suggest invariants for you if you use the `invariants?` keyword.

View File

@@ -9,6 +9,7 @@ prelude
public import Lean.Meta.Sorry
public import Lean.Util.CollectAxioms
public import Lean.OriginalConstKind
import Lean.Compiler.MetaAttr
import all Lean.OriginalConstKind -- for accessing `privateConstKindsExt`
public section
@@ -208,8 +209,12 @@ where
catch _ => pure ()
def addAndCompile (decl : Declaration) (logCompileErrors : Bool := true) : CoreM Unit := do
def addAndCompile (decl : Declaration) (logCompileErrors : Bool := true)
(markMeta : Bool := false) : CoreM Unit := do
addDecl decl
if markMeta then
for n in decl.getNames do
modifyEnv (Lean.markMeta · n)
compileDecl decl (logErrors := logCompileErrors)
end Lean

View File

@@ -186,11 +186,11 @@ def registerTagAttribute (name : Name) (descr : String)
mkInitial := pure {}
addImportedFn := fun _ _ => pure {}
addEntryFn := fun (s : NameSet) n => s.insert n
exportEntriesFnEx := fun env es _ =>
let r : Array Name := es.foldl (fun a e => a.push e) #[]
-- Do not export info for private defs
let r := r.filter (env.contains (skipRealize := false))
r.qsort Name.quickLt
exportEntriesFnEx := fun env es =>
let all : Array Name := es.foldl (fun a e => a.push e) #[] |>.qsort Name.quickLt
-- Do not export info for private defs at exported/server levels
let exported := all.filter ((env.setExporting true).contains (skipRealize := false))
{ exported, server := exported, «private» := all }
statsFn := fun s => "tag attribute" ++ Format.line ++ "number of local entries: " ++ format s.size
asyncMode := asyncMode
replay? := some fun _ newState newConsts s =>
@@ -266,15 +266,14 @@ def registerParametricAttribute (impl : ParametricAttributeImpl α) : IO (Parame
mkInitial := pure ([], {})
addImportedFn := fun _ => pure ([], {})
addEntryFn := fun (decls, m) (p : Name × α) => (p.1 :: decls, m.insert p.1 p.2)
exportEntriesFnEx := fun env (decls, m) lvl => Id.run do
let mut r := if impl.preserveOrder then
exportEntriesFnEx := fun env (decls, m) => Id.run do
let all := if impl.preserveOrder then
decls.toArray.reverse.filterMap (fun n => return (n, m.find? n))
else
let r := m.foldl (fun a n p => a.push (n, p)) #[]
r.qsort (fun a b => Name.quickLt a.1 b.1)
if lvl != .private then
r := r.filter (fun n, a => impl.filterExport env n a)
r
let exported := all.filter (fun n, a => impl.filterExport env n a)
{ exported, server := exported, «private» := all }
statsFn := fun (_, m) => "parametric attribute" ++ Format.line ++ "number of local entries: " ++ format m.size
}
let attrImpl : AttributeImpl := {
@@ -333,11 +332,11 @@ def registerEnumAttributes (attrDescrs : List (Name × String × α))
mkInitial := pure {}
addImportedFn := fun _ _ => pure {}
addEntryFn := fun (s : NameMap α) (p : Name × α) => s.insert p.1 p.2
exportEntriesFnEx := fun env m _ =>
let r : Array (Name × α) := m.foldl (fun a n p => a.push (n, p)) #[]
-- Do not export info for private defs
let r := r.filter (env.contains (skipRealize := false) ·.1)
r.qsort (fun a b => Name.quickLt a.1 b.1)
exportEntriesFnEx := fun env m =>
let all : Array (Name × α) := m.foldl (fun a n p => a.push (n, p)) #[] |>.qsort (fun a b => Name.quickLt a.1 b.1)
-- Do not export info for private defs at exported/server levels
let exported := all.filter ((env.setExporting true).contains (skipRealize := false) ·.1)
{ exported, server := exported, «private» := all }
statsFn := fun s => "enumeration attribute extension" ++ Format.line ++ "number of local entries: " ++ format s.size
-- We assume (and check in `modifyState`) that, if used asynchronously, enum attributes are set
-- only in the same context in which the tagged declaration was created

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