Compare commits

..

76 Commits

Author SHA1 Message Date
Leonardo de Moura
03bae7b676 perf: cache visited exprs at CheckAssignmentQuick 2024-09-01 14:22:07 -07:00
Joachim Breitner
a993934839 feat: generate f.eq_unfold lemmas (#5141)
With this, lean produces the following zoo of rewrite rules:
```
Option.map.eq_1      : Option.map f none = none
Option.map.eq_2      : Option.map f (some x) = some (f x)
Option.map.eq_def    : Option.map f p = match o with | none => none | (some x) => some (f x)
Option.map.eq_unfold : Option.map = fun f p => match o with | none => none | (some x) => some (f x)
```

The `f.eq_unfold` variant is especially useful to rewrite with `rw`
under
binders.

This implements and fixes #5110
2024-08-29 16:47:40 +00:00
Joachim Breitner
aa3c87b2c7 fix: conv => arg n to handle .subsingletonInst (#5149)
this fixes #4394, see there for an analysis.
2024-08-29 15:48:31 +00:00
thorimur
869e42b7c3 fix: handle AttributeKinds in LabelAttributes correctly (#3698)
This PR propagates the `AttributeKind` to `SimpleScopedEnvExtension.add`
in attributes created with `register_label_attr`.

This also fixes a nearby stale docstring which referenced `Std`.

---

Closes #3697
2024-08-29 17:57:14 +02:00
Lean stage0 autoupdater
bdbadbd74b chore: update stage0 2024-08-29 13:56:52 +00:00
Henrik Böving
3120c3d8f8 feat: add bv_decide benchmarks (#5203) 2024-08-29 12:45:58 +00:00
FR
e1cbae26cc doc: fix typo in Quotient.liftOn docstring (#5202) 2024-08-29 12:00:36 +00:00
Marc Huisinga
9009c1ac91 fix: ilean loading performance (#4900)
This PR roughly halves the time needed to load the .ilean files by
optimizing the JSON parser and the conversion from JSON to Lean data
structures.

The code is optimized roughly as follows:
- String operations are inlined more aggressively
- Parsers are changed to use new `String.Iterator` functions `curr'` and
`next'` that receive a proof and hence do not need to perform an
additional check
- The `RefIdent` of .ilean files now uses a `String` instead of a `Name`
to avoid the expensive parse step from `String` to `Name` (despite the
fact that we only very rarely actually need a `Name` in downstream code)
- Instead of `List`s and `Subarray`s, the JSON to Lean conversion now
directly passes around arrays and array indices to avoid redundant
boxing
- Parsec's `peek?` sometimes generates redundant `Option` wrappers
because the generation of basic blocks interferes with the ctor-match
optimization, so it is changed to use an `isEof` check where possible
- Early returns and inline-do-blocks cause the code generator to
generate new functions, which then interfere with optimizations, so they
are now avoided
- Mutual defs are used instead of unspecialized passing of higher-order
functions to generate faster code
- The object parser is made tail-recursive

This PR also fixes a stack overflow in `Lean.Json.compress` that would
occur with long lists and adds a benchmark for the .ilean roundtrip
(compressed pretty-printing -> parsing).
2024-08-29 11:51:48 +00:00
Sebastian Ullrich
5c61ad38be chore: revert "chore: temporarily remove test broken by #4746" (#5201)
This reverts commit 7aec6c9ae7.
2024-08-29 08:47:48 +00:00
Kim Morrison
44985dc9a6 chore: remove >6 month deprecations (#5199) 2024-08-29 05:18:44 +00:00
Kim Morrison
3dfa7812f9 chore: cleanup allowUnsafeReducibility (#5198) 2024-08-29 05:12:54 +00:00
Kim Morrison
2dd6b2b9c8 chore: upstream Fin.le_antisymm (#5197) 2024-08-29 04:45:27 +00:00
Kim Morrison
6d0b00885e feat: List.Pairwise_erase and related lemmas (#5196) 2024-08-28 23:11:02 +00:00
Kim Morrison
75c0373c1a feat: lemmas about if-then-else improving confluence (#5191) 2024-08-28 23:10:13 +00:00
Henrik Böving
b37df8e31a chore: update-stage0 2024-08-28 18:14:39 +02:00
Henrik Böving
da9c68a37a feat: import LeanSAT's tactic frontends
Co-authored-by: Markus Himmel <markus@lean-fro.org>
2024-08-28 18:14:39 +02:00
Marc Huisinga
6fce7b82bc fix: duplicate "import out of date" messages (#5185)
This PR fixes a small bug where over time, "import out of data" messages
would accumulate in files when their size changed before restarting its
file worker.
2024-08-28 14:03:17 +00:00
Marc Huisinga
f220efc5ba doc: update quickstart guide for new display name (#5193)
https://github.com/leanprover/vscode-lean4/pull/521 changed the display
name of the VS Code extension so that it can be found more easily when
searching for "Lean" (before it would appear far down in the list). This
PR updates the quickstart guide to reflect this fact.
2024-08-28 13:29:16 +00:00
Kim Morrison
613dbf1637 feat: Int and Nat simp lemmas (#5190)
`@[simp]` lemmas for Int and Nat that improve confluence.
2024-08-28 10:53:28 +00:00
Kim Morrison
8e68c5d44e chore: cleanup simps in CNF.Basic / DHashMap.Internal.List (#5189)
A few unused implementation detail simp lemmas had leaked out and were
being detected by the confluence checker. Just remove them or make them
local.
2024-08-28 06:53:07 +00:00
Kim Morrison
9ce15fb0c6 chore: remove bad simp lemmas (#5180)
This disables some simp lemmas with bad discrimination tree keys, as
identified by @mattrobball on
[zulip](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Infrastructure.20for.20tracking.20frequently.20applied.20simp.20theorems/near/459926416).
2024-08-28 02:55:17 +00:00
Kim Morrison
0dc317c73c feat: restore reduceCtorEq in norm_cast tactic (#5187)
#5167 removed `reduceCtorEq` from the default simproc set. `norm_cast`
relies on it, so we add it back in there.
2024-08-28 02:38:57 +00:00
Jannis Limperg
44366382d3 fix: ignore implementationDetail hyps in rename_i (#5183)
Closes #5176
2024-08-27 14:45:16 +00:00
Markus Himmel
095c7b2bfc chore: deprecate Nat.strongInductionOn (#5179) 2024-08-27 07:18:06 +00:00
Kim Morrison
c4e4248487 chore: remove @[simp] from List.getLast_eq_iff_getLast_eq_some (#5178)
This was not a great simp lemma, and hurts simp confluence. Better to
just use it locally where it is useful.

Similarly `List.head_eq_iff_head?_eq_some`.
2024-08-27 03:23:39 +00:00
Kim Morrison
9ef996259b feat: add BitVec.intMax_add_one 2024-08-27 11:26:16 +10:00
Kim Morrison
30fa18816c feat: activate and use boolToPropSimps 2024-08-27 11:26:16 +10:00
Kim Morrison
94fd406c04 chore: update stage0 2024-08-27 11:26:16 +10:00
Tobias Grosser
3411935e53 feat: add BitVec.intMin
This PR also pulls in some mathlib theorems on testBit and Nat and establishes facts about 2^w that are needed here and which are generally useful for bitvector reasoning.

The following theorem is not generalized to arbitrary x instead of 2, as this would require a condition to be added for x > 1 which would have to be passed to simp each time this theorem should fire.

chore: derive from testBit_two_pow

chore: convert first to prop and then decide

chore: move intMax down as well

chore: add simp set

Add simp-set into this PR

chore: fix simp extension

Move file to src/Lean to fix build

Add prelude

update date

Add university of cambridge as copyright holder

improve naming

use whitespace uniformly

use decide (n = m)

Drop the 'Nat.' namespace

Update src/Init/Data/BitVec/Lemmas.lean

Co-authored-by: Siddharth <siddu.druid@gmail.com>

Update src/Init/Data/BitVec/Lemmas.lean

Co-authored-by: Siddharth <siddu.druid@gmail.com>

Fix build

add some theorems

Revert "add some theorems"

This reverts commit fb97bc2007e371854b40badb3d6014da034c1f5e.

WIP

Shorten proof

Update src/Init/Data/Nat/Lemmas.lean

finish proofs

Update src/Init/Data/BitVec/Lemmas.lean

Co-authored-by: Kim Morrison <scott@tqft.net>

Update src/Init/Data/Nat/Lemmas.lean

Co-authored-by: Kim Morrison <scott@tqft.net>

chore: move BoolToPropSimps
2024-08-27 11:26:16 +10:00
Kim Morrison
b518091bd4 chore: better statement for List.find?_filterMap (#5177) 2024-08-27 00:22:59 +00:00
Siddharth
a58a09056f feat: relate BitVec.signExtend to truncate (#4392)
This adds helper lemmas to relate sign extension to truncation, and as a
corollary shows that sign extension to the same width is a no-op.
2024-08-26 23:39:49 +00:00
Jon Eugster
c45a6a93f9 chore: use emoji variant of ️,️,💥️ (#5173)
First part of #5015, using emoji variant of unicode symbols for
️,️,💥️.

---

(Partially) closes #5015
2024-08-26 19:46:37 +00:00
Leonardo de Moura
f917f811c8 chore: cleanup #5167 workarounds after update stage0 (#5175)
PR #5167 implemented RFC #5046, but it required several workarounds due
to staging issues. This PR cleans up these workarounds.
2024-08-26 17:53:30 +00:00
Lean stage0 autoupdater
3c687df6d5 chore: update stage0 2024-08-26 15:32:27 +00:00
Leonardo de Moura
45475d6434 feat: allow users to disable simpCtorEq simproc (#5167)
`simp only` will not apply this simproc anymore. Users must now write
`simp only [reduceCtorEq]`. See RFC #5046 for motivation.
This PR also renames simproc to `reduceCtorEq`. 

close #5046 


@semorrison A few `simp only ...` tactics will probably break in
Mathlib. Fix: include `reduceCtorEq`.
2024-08-26 13:51:21 +00:00
Tobias Grosser
c6feffa2bd feat: add Bitvec.ofInt_ofNat (#5081)
We use `no_index` to work around special-handling of `OfNat.ofNat` in
`DiscrTree`, which has been reported as an issue in
https://github.com/leanprover/lean4/issues/2867 and is currently in the
process of being fixed in https://github.com/leanprover/lean4/pull/3684.
As the potential fix seems non-trivial and might need some time to
arrive in-tree, we meanwhile add the `no_index` keyword to the
problematic subterm.

---------

Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
2024-08-26 13:12:40 +00:00
Matthew Robert Ballard
b54a9ec9b9 feat: swap arguments to Membership.mem (#5020)
We swap the arguments for `Membership.mem` so that when proceeded by a
`SetLike` coercion, as is often the case in Mathlib, the resulting
expression is recognized as eta expanded and reduce for many
computations. The most beneficial outcome is that the discrimination
tree keys for instances and simp lemmas concerning subsets become more
robust resulting in more efficient searches.

Closes `RFC` #4932

---------

Co-authored-by: Kim Morrison <kim@tqft.net>
Co-authored-by: Henrik Böving <hargonix@gmail.com>
2024-08-26 12:35:47 +00:00
Tobias Grosser
68bb92a35a feat: add BitVec.toInt_[pos|neg]_iff (#5083)
Co-authored-by: Kim Morrison <scott@tqft.net>
2024-08-26 09:44:58 +00:00
Sebastian Ullrich
dcdbb9b411 fix: Syntax.unsetTrailing (#5170)
Fixes #4958
2024-08-26 07:56:04 +00:00
Jeremy Tan Jie Rui
dd22447afd chore: @[elab_as_elim] additions (#5147)
This adds `@[elab_as_elim]` to `Quot.rec`, `Nat.strongInductionOn` and
`Nat.casesStrongInductionOn`, and also renames the latter two to
`Nat.strongRecOn` and `Nat.casesStrongRecOn`.

The first change resolves the todos in
[`Mathlib.Init.Quot`](ca6a6fdc07/Mathlib/Init/Quot.lean)
while the other two are based on a suggestion of @YaelDillies on [the
Zulip](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/Technical.20Debt.20Counters/near/464804567)
and related to
https://github.com/leanprover-community/mathlib4/pull/16096.
2024-08-26 07:44:54 +00:00
Kim Morrison
f0b0c60e0f chore: running simpNF linter (#5168) 2024-08-26 07:07:52 +00:00
Kim Morrison
9305049f1e feat: lemmas about List.find? and range'/range/iota (#5164) 2024-08-26 04:44:17 +00:00
Kim Morrison
852ee1683f feat: Int lemmas relating neg and emod/mod (#5166) 2024-08-26 03:05:16 +00:00
Kim Morrison
4c9db2fab8 feat: adjusting Int simp lemmas (#5165) 2024-08-26 03:05:10 +00:00
Kim Morrison
70c1e5690d feat: more improvements to List simp confluence (#5163) 2024-08-26 03:04:58 +00:00
Kim Morrison
5d84aebeb9 feat: lemmas about Function.comp that help confluence (#5162) 2024-08-26 03:04:53 +00:00
Kim Morrison
7e5d1103c2 feat: more lemmas about List.pmap/attach (#5160) 2024-08-26 02:15:58 +00:00
Kim Morrison
2d9cbdb450 feat: more List.findSome? lemmas (#5161) 2024-08-26 01:51:40 +00:00
Kim Morrison
fcdecacc4f feat: head/getLast lemmas for List.range (#5158) 2024-08-26 01:48:45 +00:00
Leonardo de Moura
c9c2c8720a fix: PANIC at Fin.isValue (#5159)
closes #4983
2024-08-26 00:36:47 +00:00
Leonardo de Moura
703658391e fix: PANIC at Lean.MVarId.falseOrByContra (#5157)
closes #4985
closes #4984
2024-08-26 00:28:28 +00:00
Kim Morrison
8898c8eaa9 feat: Bool lemmas improving confluence (#5155) 2024-08-25 11:15:07 +00:00
Kim Morrison
2d89693b71 chore: Option lemmas (#5154) 2024-08-25 09:20:24 +00:00
Kim Morrison
c3655b626e chore: remove bad simp lemma in omega theory (#5156) 2024-08-25 07:47:16 +00:00
Wojciech Nawrocki
644a12744b doc: fix option name (#5150)
Small typo fix. I don't believe there is an `autoBoundImplicitLocal`
option.
2024-08-25 07:16:44 +00:00
Kim Morrison
92b271ee64 feat: lemmas about List.erase(|P|Idx) (#5152) 2024-08-25 07:01:46 +00:00
Joachim Breitner
24f550fd6f feat: same equational lemmas for recursive and non-recursive functions (#5129)
This is part of #3983.

After #4154 introduced equational lemmas for non-recursive functions and
#5055
unififed the lemmas for structural and wf recursive funcitons, this now
disables the special handling of recursive functions in
`findMatchToSplit?`, so that the equational lemmas should be the same no
matter how the function was defined.

The new option `eqns.deepRecursiveSplit` can be disabled to get the old
behavior.

### Breaking change

This can break existing code, as there now can be extra equational
lemmas:

* Explicit uses of `f.eq_2` might have to be adjusted if the numbering
  changed.

* Uses of `rw [f]` or `simp [f]` may no longer apply if they previously
  matched (and introduced a `match` statement), when the equational
  lemmas got more fine-grained.

  In this case either case analysis on the parameters before rewriting
  helps, or setting the option `opt.deepRecursiveSplit false` while
  defining the function
2024-08-25 06:51:03 +00:00
Kim Morrison
cee84286e6 feat: improving confluence of List simp lemmas (#5151)
More theorems coming shortly that are easier after these changes, but
I'll test Mathlib on these simp changes first.
2024-08-25 04:32:45 +00:00
Kim Morrison
75781b46f5 feat: lemmas about List.attach/pmap (#5153) 2024-08-25 03:58:54 +00:00
Kim Morrison
ea97aac83b feat: improve Nat simp lemma confluence (#5148) 2024-08-24 11:37:37 +00:00
Kim Morrison
b1ebe7b484 feat: missing Nat.and_xor_distrib_(left|right) (#5146) 2024-08-24 07:46:57 +00:00
Kim Morrison
07013da720 chore: running the simpNF linter over Lean (#5133)
This should resolve nearly all of the simpNF lints. This is a follow-up
to #4620.
2024-08-24 07:10:07 +00:00
Wojciech Nawrocki
2bc87298d9 doc: update user widget manual (#5006)
Updates the user widget manual to account for more recent changes. One
issue is that the samples no longer work on https://live.lean-lang.org/
because it uses an outdated version of the `@leanprover/infoview` NPM
package. They work on https://lean.math.hhu.de/ and in recent versions
of the VSCode extension.
2024-08-23 19:03:39 +00:00
Sebastian Ullrich
390a9a63a2 fix: mixing variable binder updates and declarations (#5142)
Fixes #2143
2024-08-23 09:31:49 +00:00
Sebastian Ullrich
6d4ec153ad feat: ship cadical (#4325)
Co-authored-by: Henrik Böving <hargonix@gmail.com>
2024-08-23 09:13:27 +00:00
Kim Morrison
bf304769e0 feat: misc List lemma updates (#5127) 2024-08-23 01:17:17 +00:00
Kim Morrison
7488b27b0d feat: lemmas about membership of sublists (#5132) 2024-08-23 01:16:53 +00:00
Sebastian Ullrich
33d24c3bca fix: improper handling of strict-implicit section variables (#5138)
This was actually broken even before `include`
2024-08-22 14:20:25 +00:00
Sebastian Ullrich
f71a1fb4ae test: add missing test 2024-08-22 16:48:11 +02:00
Joachim Breitner
01ec8c5e14 doc: unfold tactic docstring (#5109) 2024-08-22 13:58:42 +00:00
Joachim Breitner
d975e4302e feat: fine-grained equational lemmas for non-recursive functions (#4154)
This is part of #3983.

Fine-grained equational lemmas are useful even for non-recursive
functions, so this adds them.

The new option `eqns.nonrecursive` can be set to `false` to have the old
behavior.

### Breaking channge

This is a breaking change: Previously, `rw [Option.map]` would rewrite
`Option.map f o` to `match o with … `. Now this rewrite will fail
because the equational lemmas require constructors here (like they do
for, say, `List.map`).

Remedies:

 * Split on `o` before rewriting.
* Use `rw [Option.map.eq_def]`, which rewrites any (saturated)
application of `Option.map`
* Use `set_option eqns.nonrecursive false` when *defining* the function
in question.

### Interaction with simp

The `simp` tactic so far had a special provision for non-recursive
functions so that `simp [f]` will try to use the equational lemmas, but
will also unfold `f` else, so less breakage here (but maybe performance
improvements with functions with many cases when applied to a
constructor, as the simplifier will no longer unfold to a large
`match`-statement and then collapse it right away).

For projection functions and functions marked `[reducible]`, `simp [f]`
won’t use the equational theorems, and will only use its internal
unfolding machinery.

### Implementation notes

It uses the same `mkEqnTypes` function as for recursive functions, so we
are close to a consistency here. There is still the wrinkle that for
recursive functions we don't split matches without an interesting
recursive call inside. Unifying that is future work.
2024-08-22 13:26:58 +00:00
Henrik Böving
74715a0f9c feat: support for secure temporary files (#5125)
Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
2024-08-22 13:01:40 +00:00
Kim Morrison
d540ba787a feat: Option lemmas (#5128) 2024-08-22 12:43:16 +00:00
Kim Morrison
b33d08078d feat: more lemmas about List.append (#5131) 2024-08-22 12:42:57 +00:00
Kim Morrison
e9025bdf79 feat: lemmas about List.join (#5130) 2024-08-22 12:09:45 +00:00
Sebastian Ullrich
5651a11ac8 feat: improve unused section variable warning (#5036)
See
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/Opt.20out.20of.20.22included.20section.20variable.20is.20not.20used.22.20linter
2024-08-22 10:18:09 +00:00
600 changed files with 6613 additions and 1176 deletions

View File

@@ -176,7 +176,7 @@ jobs:
"check-level": 2,
"CMAKE_PRESET": "debug",
// exclude seriously slow tests
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest|bv_bitblast_stress'"
},
// TODO: suddenly started failing in CI
/*{

View File

@@ -30,6 +30,35 @@ if(NOT (DEFINED STAGE0_CMAKE_EXECUTABLE_SUFFIX))
set(STAGE0_CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}")
endif()
# Don't do anything with cadical on wasm
if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
# On CI Linux, we source cadical from Nix instead; see flake.nix
find_program(CADICAL cadical)
if(NOT CADICAL)
set(CADICAL_CXX c++)
find_program(CCACHE ccache)
if(CCACHE)
set(CADICAL_CXX "${CCACHE} ${CADICAL_CXX}")
endif()
# missing stdio locking API on Windows
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
string(APPEND CADICAL_CXXFLAGS " -DNUNLOCKED")
endif()
ExternalProject_add(cadical
PREFIX cadical
GIT_REPOSITORY https://github.com/arminbiere/cadical
GIT_TAG rel-1.9.5
CONFIGURE_COMMAND ""
# https://github.com/arminbiere/cadical/blob/master/BUILD.md#manual-build
BUILD_COMMAND $(MAKE) -f ${CMAKE_SOURCE_DIR}/src/cadical.mk CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} CXX=${CADICAL_CXX} CXXFLAGS=${CADICAL_CXXFLAGS}
BUILD_IN_SOURCE ON
INSTALL_COMMAND "")
set(CADICAL ${CMAKE_BINARY_DIR}/cadical/cadical${CMAKE_EXECUTABLE_SUFFIX} CACHE FILEPATH "path to cadical binary" FORCE)
set(EXTRA_DEPENDS "cadical")
endif()
list(APPEND CL_ARGS -DCADICAL=${CADICAL})
endif()
ExternalProject_add(stage0
SOURCE_DIR "${LEAN_SOURCE_DIR}/stage0"
SOURCE_SUBDIR src

View File

@@ -43,3 +43,5 @@
/src/Init/Guard.lean @digama0
/src/Lean/Server/CodeActions/ @digama0
/src/Std/ @TwoFX
/src/Std/Tactic/BVDecide/ @hargoniX
/src/Lean/Elab/Tactic/BVDecide/ @hargoniX

View File

@@ -1341,3 +1341,33 @@ whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
==============================================================================
CaDiCaL is under the MIT License:
==============================================================================
MIT License
Copyright (c) 2016-2021 Armin Biere, Johannes Kepler University Linz, Austria
Copyright (c) 2020-2021 Mathias Fleury, Johannes Kepler University Linz, Austria
Copyright (c) 2020-2021 Nils Froleyks, Johannes Kepler University Linz, Austria
Copyright (c) 2022-2024 Katalin Fazekas, Vienna University of Technology, Austria
Copyright (c) 2021-2024 Armin Biere, University of Freiburg, Germany
Copyright (c) 2021-2024 Mathias Fleury, University of Freiburg, Germany
Copyright (c) 2023-2024 Florian Pollitt, University of Freiburg, Germany
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -4,15 +4,18 @@ open Lean Widget
/-!
# The user-widgets system
Proving and programming are inherently interactive tasks. Lots of mathematical objects and data
structures are visual in nature. *User widgets* let you associate custom interactive UIs with
sections of a Lean document. User widgets are rendered in the Lean infoview.
Proving and programming are inherently interactive tasks.
Lots of mathematical objects and data structures are visual in nature.
*User widgets* let you associate custom interactive UIs
with sections of a Lean document.
User widgets are rendered in the Lean infoview.
![Rubik's cube](../images/widgets_rubiks.png)
## Trying it out
To try it out, simply type in the following code and place your cursor over the `#widget` command.
To try it out, type in the following code and place your cursor over the `#widget` command.
You can also [view this manual entry in the online editor](https://live.lean-lang.org/#url=https%3A%2F%2Fraw.githubusercontent.com%2Fleanprover%2Flean4%2Fmaster%2Fdoc%2Fexamples%2Fwidgets.lean).
-/
@[widget_module]
@@ -21,38 +24,37 @@ def helloWidget : Widget.Module where
import * as React from 'react';
export default function(props) {
const name = props.name || 'world'
return React.createElement('p', {}, name + '!')
return React.createElement('p', {}, 'Hello ' + name + '!')
}"
#widget helloWidget
/-!
If you want to dive into a full sample right away, check out
[`RubiksCube`](https://github.com/leanprover/lean4-samples/blob/main/RubiksCube/).
[`Rubiks`](https://github.com/leanprover-community/ProofWidgets4/blob/main/ProofWidgets/Demos/Rubiks.lean).
This sample uses higher-level widget components from the ProofWidgets library.
Below, we'll explain the system piece by piece.
⚠️ WARNING: All of the user widget APIs are **unstable** and subject to breaking changes.
## Widget sources and instances
## Widget modules and instances
A *widget source* is a valid JavaScript [ESModule](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
which exports a [React component](https://reactjs.org/docs/components-and-props.html). To access
React, the module must use `import * as React from 'react'`. Our first example of a widget source
is of course the value of `helloWidget.javascript`.
A [widget module](https://leanprover-community.github.io/mathlib4_docs/Lean/Widget/UserWidget.html#Lean.Widget.Module)
is a valid JavaScript [ESModule](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
that can execute in the Lean infoview.
Most widget modules export a [React component](https://reactjs.org/docs/components-and-props.html)
as the piece of user interface to be rendered.
To access React, the module can use `import * as React from 'react'`.
Our first example of a widget module is `helloWidget` above.
Widget modules must be registered with the `@[widget_module]` attribute.
We can register a widget source with the `@[widget]` attribute, giving it a friendlier name
in the `name` field. This is bundled together in a `UserWidgetDefinition`.
A *widget instance* is then the identifier of a `UserWidgetDefinition` (so `` `helloWidget ``,
not `"Hello"`) associated with a range of positions in the Lean source code. Widget instances
are stored in the *infotree* in the same manner as other information about the source file
such as the type of every expression. In our example, the `#widget` command stores a widget instance
with the entire line as its range. We can think of a widget instance as an instruction for the
infoview: "when the user places their cursor here, please render the following widget".
Every widget instance also contains a `props : Json` value. This value is passed as an argument
to the React component. In our first invocation of `#widget`, we set it to `.null`. Try out what
happens when you type in:
A [widget instance](https://leanprover-community.github.io/mathlib4_docs/Lean/Widget/Types.html#Lean.Widget.WidgetInstance)
is then the identifier of a widget module (e.g. `` `helloWidget ``)
bundled with a value for its props.
This value is passed as the argument to the React component.
In our first invocation of `#widget`, we set it to `.null`.
Try out what happens when you type in:
-/
structure HelloWidgetProps where
@@ -62,21 +64,37 @@ structure HelloWidgetProps where
#widget helloWidget with { name? := "<your name here>" : HelloWidgetProps }
/-!
💡 NOTE: The RPC system presented below does not depend on JavaScript. However the primary use case
is the web-based infoview in VSCode.
Under the hood, widget instances are associated with a range of positions in the source file.
Widget instances are stored in the *infotree*
in the same manner as other information about the source file
such as the type of every expression.
In our example, the `#widget` command stores a widget instance
with the entire line as its range.
One can think of the infotree entry as an instruction for the infoview:
"when the user places their cursor here, please render the following widget".
-/
/-!
## Querying the Lean server
Besides enabling us to create cool client-side visualizations, user widgets come with the ability
to communicate with the Lean server. Thanks to this, they have the same metaprogramming capabilities
as custom elaborators or the tactic framework. To see this in action, let's implement a `#check`
command as a web input form. This example assumes some familiarity with React.
💡 NOTE: The RPC system presented below does not depend on JavaScript.
However, the primary use case is the web-based infoview in VSCode.
The first thing we'll need is to create an *RPC method*. Meaning "Remote Procedure Call", this
is basically a Lean function callable from widget code (possibly remotely over the internet).
Besides enabling us to create cool client-side visualizations,
user widgets have the ability to communicate with the Lean server.
Thanks to this, they have the same metaprogramming capabilities
as custom elaborators or the tactic framework.
To see this in action, let's implement a `#check` command as a web input form.
This example assumes some familiarity with React.
The first thing we'll need is to create an *RPC method*.
Meaning "Remote Procedure Call",this is a Lean function callable from widget code
(possibly remotely over the internet).
Our method will take in the `name : Name` of a constant in the environment and return its type.
By convention, we represent the input data as a `structure`. Since it will be sent over from JavaScript,
we need `FromJson` and `ToJson`. We'll see below why the position field is needed.
By convention, we represent the input data as a `structure`.
Since it will be sent over from JavaScript,
we need `FromJson` and `ToJson` instnace.
We'll see why the position field is needed later.
-/
structure GetTypeParams where
@@ -87,25 +105,33 @@ structure GetTypeParams where
deriving FromJson, ToJson
/-!
After its arguments, we define the `getType` method. Every RPC method executes in the `RequestM`
monad and must return a `RequestTask α` where `α` is its "actual" return type. The `Task` is so
that requests can be handled concurrently. A first guess for `α` might be `Expr`. However,
expressions in general can be large objects which depend on an `Environment` and `LocalContext`.
Thus we cannot directly serialize an `Expr` and send it to the widget. Instead, there are two
options:
- One is to send a *reference* which points to an object residing on the server. From JavaScript's
point of view, references are entirely opaque, but they can be sent back to other RPC methods for
further processing.
- Two is to pretty-print the expression and send its textual representation called `CodeWithInfos`.
This representation contains extra data which the infoview uses for interactivity. We take this
strategy here.
After its argument structure, we define the `getType` method.
RPCs method execute in the `RequestM` monad and must return a `RequestTask α`
where `α` is the "actual" return type.
The `Task` is so that requests can be handled concurrently.
As a first guess, we'd use `Expr` as `α`.
However, expressions in general can be large objects
which depend on an `Environment` and `LocalContext`.
Thus we cannot directly serialize an `Expr` and send it to JavaScript.
Instead, there are two options:
RPC methods execute in the context of a file, but not any particular `Environment` so they don't
know about the available `def`initions and `theorem`s. Thus, we need to pass in a position at which
we want to use the local `Environment`. This is why we store it in `GetTypeParams`. The `withWaitFindSnapAtPos`
method launches a concurrent computation whose job is to find such an `Environment` and a bit
more information for us, in the form of a `snap : Snapshot`. With this in hand, we can call
`MetaM` procedures to find out the type of `name` and pretty-print it.
- One is to send a *reference* which points to an object residing on the server.
From JavaScript's point of view, references are entirely opaque,
but they can be sent back to other RPC methods for further processing.
- The other is to pretty-print the expression and send its textual representation called `CodeWithInfos`.
This representation contains extra data which the infoview uses for interactivity.
We take this strategy here.
RPC methods execute in the context of a file,
but not of any particular `Environment`,
so they don't know about the available `def`initions and `theorem`s.
Thus, we need to pass in a position at which we want to use the local `Environment`.
This is why we store it in `GetTypeParams`.
The `withWaitFindSnapAtPos` method launches a concurrent computation
whose job is to find such an `Environment` for us,
in the form of a `snap : Snapshot`.
With this in hand, we can call `MetaM` procedures
to find out the type of `name` and pretty-print it.
-/
open Server RequestM in
@@ -121,18 +147,22 @@ def getType (params : GetTypeParams) : RequestM (RequestTask CodeWithInfos) :=
/-!
## Using infoview components
Now that we have all we need on the server side, let's write the widget source. By importing
`@leanprover/infoview`, widgets can render UI components used to implement the infoview itself.
For example, the `<InteractiveCode>` component displays expressions with `term : type` tooltips
as seen in the goal view. We will use it to implement our custom `#check` display.
Now that we have all we need on the server side, let's write the widget module.
By importing `@leanprover/infoview`, widgets can render UI components used to implement the infoview itself.
For example, the `<InteractiveCode>` component displays expressions
with `term : type` tooltips as seen in the goal view.
We will use it to implement our custom `#check` display.
⚠️ WARNING: Like the other widget APIs, the infoview JS API is **unstable** and subject to breaking changes.
The code below demonstrates useful parts of the API. To make RPC method calls, we use the `RpcContext`.
The `useAsync` helper packs the results of a call into an `AsyncState` structure which indicates
whether the call has resolved successfully, has returned an error, or is still in-flight. Based
on this we either display an `InteractiveCode` with the type, `mapRpcError` the error in order
to turn it into a readable message, or show a `Loading..` message, respectively.
The code below demonstrates useful parts of the API.
To make RPC method calls, we invoke the `useRpcSession` hook.
The `useAsync` helper packs the results of an RPC call into an `AsyncState` structure
which indicates whether the call has resolved successfully,
has returned an error, or is still in-flight.
Based on this we either display an `InteractiveCode` component with the result,
`mapRpcError` the error in order to turn it into a readable message,
or show a `Loading..` message, respectively.
-/
@[widget_module]
@@ -140,10 +170,10 @@ def checkWidget : Widget.Module where
javascript := "
import * as React from 'react';
const e = React.createElement;
import { RpcContext, InteractiveCode, useAsync, mapRpcError } from '@leanprover/infoview';
import { useRpcSession, InteractiveCode, useAsync, mapRpcError } from '@leanprover/infoview';
export default function(props) {
const rs = React.useContext(RpcContext)
const rs = useRpcSession()
const [name, setName] = React.useState('getType')
const st = useAsync(() =>
@@ -159,7 +189,7 @@ export default function(props) {
"
/-!
Finally we can try out the widget.
We can now try out the widget.
-/
#widget checkWidget
@@ -169,30 +199,31 @@ Finally we can try out the widget.
## Building widget sources
While typing JavaScript inline is fine for a simple example, for real developments we want to use
packages from NPM, a proper build system, and JSX. Thus, most actual widget sources are built with
Lake and NPM. They consist of multiple files and may import libraries which don't work as ESModules
by default. On the other hand a widget source must be a single, self-contained ESModule in the form
of a string. Readers familiar with web development may already have guessed that to obtain such a
string, we need a *bundler*. Two popular choices are [`rollup.js`](https://rollupjs.org/guide/en/)
and [`esbuild`](https://esbuild.github.io/). If we go with `rollup.js`, to make a widget work with
the infoview we need to:
While typing JavaScript inline is fine for a simple example,
for real developments we want to use packages from NPM, a proper build system, and JSX.
Thus, most actual widget sources are built with Lake and NPM.
They consist of multiple files and may import libraries which don't work as ESModules by default.
On the other hand a widget module must be a single, self-contained ESModule in the form of a string.
Readers familiar with web development may already have guessed that to obtain such a string, we need a *bundler*.
Two popular choices are [`rollup.js`](https://rollupjs.org/guide/en/)
and [`esbuild`](https://esbuild.github.io/).
If we go with `rollup.js`, to make a widget work with the infoview we need to:
- Set [`output.format`](https://rollupjs.org/guide/en/#outputformat) to `'es'`.
- [Externalize](https://rollupjs.org/guide/en/#external) `react`, `react-dom`, `@leanprover/infoview`.
These libraries are already loaded by the infoview so they should not be bundled.
In the RubiksCube sample, we provide a working `rollup.js` build configuration in
[rollup.config.js](https://github.com/leanprover/lean4-samples/blob/main/RubiksCube/widget/rollup.config.js).
ProofWidgets provides a working `rollup.js` build configuration in
[rollup.config.js](https://github.com/leanprover-community/ProofWidgets4/blob/main/widget/rollup.config.js).
## Inserting text
We can also instruct the editor to insert text, copy text to the clipboard, or
reveal a certain location in the document.
To do this, use the `React.useContext(EditorContext)` React context.
This will return an `EditorConnection` whose `api` field contains a number of methods to
interact with the text editor.
Besides making RPC calls, widgets can instruct the editor to carry out certain actions.
We can insert text, copy text to the clipboard, or highlight a certain location in the document.
To do this, use the `EditorContext` React context.
This will return an `EditorConnection`
whose `api` field contains a number of methods that interact with the editor.
You can see the full API for this [here](https://github.com/leanprover/vscode-lean4/blob/master/lean4-infoview-api/src/infoviewApi.ts#L52)
The full API can be viewed [here](https://github.com/leanprover/vscode-lean4/blob/master/lean4-infoview-api/src/infoviewApi.ts#L52).
-/
@[widget_module]
@@ -212,6 +243,4 @@ export default function(props) {
}
"
/-! Finally, we can try this out: -/
#widget insertTextWidget

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -5,11 +5,11 @@ See [Setup](./setup.md) for supported platforms and other ways to set up Lean 4.
1. Install [VS Code](https://code.visualstudio.com/).
1. Launch VS Code and install the `lean4` extension by clicking on the "Extensions" sidebar entry and searching for "lean4".
1. Launch VS Code and install the `Lean 4` extension by clicking on the 'Extensions' sidebar entry and searching for 'Lean 4'.
![installing the vscode-lean4 extension](images/code-ext.png)
1. Open the Lean 4 setup guide by creating a new text file using "File > New Text File" (`Ctrl+N` / `Cmd+N`), clicking on the ∀-symbol in the top right and selecting "Documentation… > Docs: Show Setup Guide".
1. Open the Lean 4 setup guide by creating a new text file using 'File > New Text File' (`Ctrl+N` / `Cmd+N`), clicking on the ∀-symbol in the top right and selecting 'Documentation… > Docs: Show Setup Guide'.
![show setup guide](images/show-setup-guide.png)

17
flake.lock generated
View File

@@ -34,6 +34,22 @@
"type": "github"
}
},
"nixpkgs-cadical": {
"locked": {
"lastModified": 1722221733,
"narHash": "sha256-sga9SrrPb+pQJxG1ttJfMPheZvDOxApFfwXCFO0H9xw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "12bf09802d77264e441f48e25459c10c93eada2e",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "12bf09802d77264e441f48e25459c10c93eada2e",
"type": "github"
}
},
"nixpkgs-old": {
"flake": false,
"locked": {
@@ -55,6 +71,7 @@
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"nixpkgs-cadical": "nixpkgs-cadical",
"nixpkgs-old": "nixpkgs-old"
}
},

View File

@@ -5,6 +5,8 @@
# old nixpkgs used for portable release with older glibc (2.27)
inputs.nixpkgs-old.url = "github:NixOS/nixpkgs/nixos-19.03";
inputs.nixpkgs-old.flake = false;
# for cadical 1.9.5; sync with CMakeLists.txt
inputs.nixpkgs-cadical.url = "github:NixOS/nixpkgs/12bf09802d77264e441f48e25459c10c93eada2e";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, nixpkgs-old, flake-utils, ... }@inputs: flake-utils.lib.eachDefaultSystem (system:
@@ -14,6 +16,11 @@
pkgsDist-old = import nixpkgs-old { inherit system; };
# An old nixpkgs for creating releases with an old glibc
pkgsDist-old-aarch = import nixpkgs-old { localSystem.config = "aarch64-unknown-linux-gnu"; };
pkgsCadical = import inputs.nixpkgs-cadical { inherit system; };
cadical = if pkgs.stdenv.isLinux then
# use statically-linked cadical on Linux to avoid glibc versioning troubles
pkgsCadical.pkgsStatic.cadical.overrideAttrs { doCheck = false; }
else pkgsCadical.cadical;
lean-packages = pkgs.callPackage (./nix/packages.nix) { src = ./.; };
@@ -21,11 +28,9 @@
stdenv = pkgs.overrideCC pkgs.stdenv lean-packages.llvmPackages.clang;
} ({
buildInputs = with pkgs; [
cmake gmp libuv ccache
cmake gmp libuv ccache cadical
lean-packages.llvmPackages.llvm # llvm-symbolizer for asan/lsan
gdb
# TODO: only add when proven to not affect the flakification
#pkgs.python3
tree # for CI
];
# https://github.com/NixOS/nixpkgs/issues/60919

View File

@@ -1,5 +1,5 @@
{ src, debug ? false, stage0debug ? false, extraCMakeFlags ? [],
stdenv, lib, cmake, gmp, libuv, git, gnumake, bash, buildLeanPackage, writeShellScriptBin, runCommand, symlinkJoin, lndir, perl, gnused, darwin, llvmPackages, linkFarmFromDrvs,
stdenv, lib, cmake, gmp, libuv, cadical, git, gnumake, bash, buildLeanPackage, writeShellScriptBin, runCommand, symlinkJoin, lndir, perl, gnused, darwin, llvmPackages, linkFarmFromDrvs,
... } @ args:
with builtins;
lib.warn "The Nix-based build is deprecated" rec {
@@ -17,7 +17,7 @@ lib.warn "The Nix-based build is deprecated" rec {
'';
} // args // {
src = args.realSrc or (sourceByRegex args.src [ "[a-z].*" "CMakeLists\.txt" ]);
cmakeFlags = (args.cmakeFlags or [ "-DSTAGE=1" "-DPREV_STAGE=./faux-prev-stage" "-DUSE_GITHASH=OFF" ]) ++ (args.extraCMakeFlags or extraCMakeFlags) ++ lib.optional (args.debug or debug) [ "-DCMAKE_BUILD_TYPE=Debug" ];
cmakeFlags = (args.cmakeFlags or [ "-DSTAGE=1" "-DPREV_STAGE=./faux-prev-stage" "-DUSE_GITHASH=OFF" "-DCADICAL=${cadical}/bin/cadical" ]) ++ (args.extraCMakeFlags or extraCMakeFlags) ++ lib.optional (args.debug or debug) [ "-DCMAKE_BUILD_TYPE=Debug" ];
preConfigure = args.preConfigure or "" + ''
# ignore absence of submodule
sed -i 's!lake/Lake.lean!!' CMakeLists.txt
@@ -158,7 +158,7 @@ lib.warn "The Nix-based build is deprecated" rec {
test = buildCMake {
name = "lean-test-${desc}";
realSrc = lib.sourceByRegex src [ "src.*" "tests.*" ];
buildInputs = [ gmp libuv perl git ];
buildInputs = [ gmp libuv perl git cadical ];
preConfigure = ''
cd src
'';

View File

@@ -535,6 +535,12 @@ else()
OUTPUT_NAME leancpp)
endif()
if((${STAGE} GREATER 0) AND CADICAL)
add_custom_target(copy-cadical
COMMAND cmake -E copy_if_different "${CADICAL}" "${CMAKE_BINARY_DIR}/bin/cadical${CMAKE_EXECUTABLE_SUFFIX}")
add_dependencies(leancpp copy-cadical)
endif()
# MSYS2 bash usually handles Windows paths relatively well, but not when putting them in the PATH
string(REGEX REPLACE "^([a-zA-Z]):" "/\\1" LEAN_BIN "${CMAKE_BINARY_DIR}/bin")
@@ -633,6 +639,10 @@ file(COPY ${LEAN_SOURCE_DIR}/bin/leanmake DESTINATION ${CMAKE_BINARY_DIR}/bin)
install(DIRECTORY "${CMAKE_BINARY_DIR}/bin/" USE_SOURCE_PERMISSIONS DESTINATION bin)
if (${STAGE} GREATER 0 AND CADICAL)
install(PROGRAMS "${CADICAL}" DESTINATION bin)
endif()
add_custom_target(clean-stdlib
COMMAND rm -rf "${CMAKE_BINARY_DIR}/lib" || true)

View File

@@ -57,7 +57,7 @@ theorem apply_ite (f : α → β) (P : Prop) [Decidable P] (x y : α) :
-- We don't mark this as `simp` as it is already handled by `ite_eq_right_iff`.
theorem ite_some_none_eq_none [Decidable P] :
(if P then some x else none) = none ¬ P := by
simp only [ite_eq_right_iff]
simp only [ite_eq_right_iff, reduceCtorEq]
rfl
@[simp] theorem ite_some_none_eq_some [Decidable P] :

View File

@@ -36,6 +36,17 @@ and `flip (·<·)` is the greater-than relation.
theorem Function.comp_def {α β δ} (f : β δ) (g : α β) : f g = fun x => f (g x) := rfl
@[simp] theorem Function.const_comp {f : α β} {c : γ} :
(Function.const β c f) = Function.const α c := by
rfl
@[simp] theorem Function.comp_const {f : β γ} {b : β} :
(f Function.const α b) = Function.const α (f b) := by
rfl
@[simp] theorem Function.true_comp {f : α β} : ((fun _ => true) f) = fun _ => true := by
rfl
@[simp] theorem Function.false_comp {f : α β} : ((fun _ => false) f) = fun _ => false := by
rfl
attribute [simp] namedPattern
/--
@@ -1553,7 +1564,7 @@ so you should consider the simpler versions if they apply:
* `Quot.recOnSubsingleton`, when the target type is a `Subsingleton`
* `Quot.hrecOn`, which uses `HEq (f a) (f b)` instead of a `sound p ▸ f a = f b` assummption
-/
protected abbrev rec
@[elab_as_elim] protected abbrev rec
(f : (a : α) motive (Quot.mk r a))
(h : (a b : α) (p : r a b) Eq.ndrec (f a) (sound p) = f b)
(q : Quot r) : motive q :=
@@ -1639,7 +1650,7 @@ protected theorem ind {α : Sort u} {s : Setoid α} {motive : Quotient s → Pro
/--
The analogue of `Quot.liftOn`: if `f : α → β` respects the equivalence relation `≈`,
then it lifts to a function on `Quotient s` such that `lift (mk a) f h = f a`.
then it lifts to a function on `Quotient s` such that `liftOn (mk a) f h = f a`.
-/
protected abbrev liftOn {α : Sort u} {β : Sort v} {s : Setoid α} (q : Quotient s) (f : α β) (c : (a b : α) a b f a = f b) : β :=
Quot.liftOn q f c

View File

@@ -260,7 +260,7 @@ theorem Context.evalList_sort (ctx : Context α) (h : ContextInformation.isComm
simp [ContextInformation.isComm, Option.isSome] at h
match h₂ : ctx.comm with
| none =>
simp only [h₂] at h
simp [h₂] at h
| some val =>
simp [h₂] at h
exact val.down

View File

@@ -13,11 +13,11 @@ namespace Array
/-- `a ∈ as` is a predicate which asserts that `a` is in the array `as`. -/
-- NB: This is defined as a structure rather than a plain def so that a lemma
-- like `sizeOf_lt_of_mem` will not apply with no actual arrays around.
structure Mem (a : α) (as : Array α) : Prop where
structure Mem (as : Array α) (a : α) : Prop where
val : a as.data
instance : Membership α (Array α) where
mem a as := Mem a as
mem := Mem
theorem sizeOf_lt_of_mem [SizeOf α] {as : Array α} (h : a as) : sizeOf a < sizeOf as := by
cases as with | _ as =>

View File

@@ -152,9 +152,6 @@ theorem getLsb_ofNat (n : Nat) (x : Nat) (i : Nat) :
getLsb (BitVec.ofNat n x) i = (i < n && x.testBit i) := by
simp [getLsb, BitVec.ofNat, Fin.val_ofNat']
@[simp, deprecated toNat_ofNat (since := "2024-02-22")]
theorem toNat_zero (n : Nat) : (0#n).toNat = 0 := by trivial
@[simp] theorem getLsb_zero : (0#w).getLsb i = false := by simp [getLsb]
@[simp] theorem getMsb_zero : (0#w).getMsb i = false := by simp [getMsb]
@@ -303,6 +300,17 @@ theorem toInt_ofNat {n : Nat} (x : Nat) :
@[simp] theorem ofInt_natCast (w n : Nat) :
BitVec.ofInt w (n : Int) = BitVec.ofNat w n := rfl
@[simp] theorem ofInt_ofNat (w n : Nat) :
BitVec.ofInt w (no_index (OfNat.ofNat n)) = BitVec.ofNat w (OfNat.ofNat n) := rfl
theorem toInt_neg_iff {w : Nat} {x : BitVec w} :
BitVec.toInt x < 0 2 ^ w 2 * x.toNat := by
simp [toInt_eq_toNat_cond]; omega
theorem toInt_pos_iff {w : Nat} {x : BitVec w} :
0 BitVec.toInt x 2 * x.toNat < 2 ^ w := by
simp [toInt_eq_toNat_cond]; omega
/-! ### zeroExtend and truncate -/
theorem truncate_eq_zeroExtend {v : Nat} {x : BitVec w} :
@@ -413,11 +421,9 @@ theorem msb_truncate (x : BitVec w) : (x.truncate (k + 1)).msb = x.getLsb k := b
(x.truncate l).truncate k = x.truncate k :=
zeroExtend_zeroExtend_of_le x h
/--Truncating by the bitwidth has no effect. -/
@[simp]
theorem truncate_eq_self {x : BitVec w} : x.truncate w = x := by
ext i
simp [getLsb_zeroExtend]
/-- Truncating by the bitwidth has no effect. -/
-- This doesn't need to be a `@[simp]` lemma, as `zeroExtend_eq` applies.
theorem truncate_eq_self {x : BitVec w} : x.truncate w = x := zeroExtend_eq _
@[simp] theorem truncate_cast {h : w = v} : (cast h x).truncate k = x.truncate k := by
apply eq_of_getLsb_eq
@@ -776,7 +782,6 @@ theorem shiftLeft_shiftLeft {w : Nat} (x : BitVec w) (n m : Nat) :
@[simp]
theorem shiftLeft_eq' {x : BitVec w₁} {y : BitVec w₂} : x <<< y = x <<< y.toNat := by rfl
@[simp]
theorem shiftLeft_zero' {x : BitVec w₁} : x <<< 0#w₂ = x := by simp
theorem shiftLeft_shiftLeft' {x : BitVec w₁} {y : BitVec w₂} {z : BitVec w₃} :
@@ -986,7 +991,7 @@ theorem signExtend_eq_not_zeroExtend_not_of_msb_false {x : BitVec w} {v : Nat} (
ext i
by_cases hv : i < v
· simp only [signExtend, getLsb, getLsb_zeroExtend, hv, decide_True, Bool.true_and, toNat_ofInt,
BitVec.toInt_eq_msb_cond, hmsb, reduceIte]
BitVec.toInt_eq_msb_cond, hmsb, reduceIte, reduceCtorEq]
rw [Int.ofNat_mod_ofNat, Int.toNat_ofNat, Nat.testBit_mod_two_pow]
simp [BitVec.testBit_toNat]
· simp only [getLsb_zeroExtend, hv, decide_False, Bool.false_and]
@@ -1022,6 +1027,18 @@ theorem signExtend_eq_not_zeroExtend_not_of_msb_true {x : BitVec w} {v : Nat} (h
· rw [signExtend_eq_not_zeroExtend_not_of_msb_true hmsb]
by_cases (i < v) <;> by_cases (i < w) <;> simp_all <;> omega
/-- Sign extending to a width smaller than the starting width is a truncation. -/
theorem signExtend_eq_truncate_of_lt (x : BitVec w) {v : Nat} (hv : v w):
x.signExtend v = x.truncate v := by
ext i
simp only [getLsb_signExtend, Fin.is_lt, decide_True, Bool.true_and, getLsb_zeroExtend,
ite_eq_left_iff, Nat.not_lt]
omega
/-- Sign extending to the same bitwidth is a no op. -/
theorem signExtend_eq (x : BitVec w) : x.signExtend w = x := by
rw [signExtend_eq_truncate_of_lt _ (Nat.le_refl _), truncate_eq]
/-! ### append -/
theorem append_def (x : BitVec v) (y : BitVec w) :
@@ -1449,20 +1466,6 @@ protected theorem lt_of_le_ne (x y : BitVec n) (h1 : x <= y) (h2 : ¬ x = y) : x
simp
exact Nat.lt_of_le_of_ne
/-! ### intMax -/
/-- The bitvector of width `w` that has the largest value when interpreted as an integer. -/
def intMax (w : Nat) : BitVec w := BitVec.ofNat w (2^w - 1)
theorem getLsb_intMax_eq (w : Nat) : (intMax w).getLsb i = decide (i < w) := by
simp [intMax, getLsb]
theorem toNat_intMax_eq : (intMax w).toNat = 2^w - 1 := by
have h : 2^w - 1 < 2^w := by
have pos : 2^w > 0 := Nat.pow_pos (by decide)
omega
simp [intMax, Nat.shiftLeft_eq, Nat.one_mul, natCast_eq_ofNat, toNat_ofNat, Nat.mod_eq_of_lt h]
/-! ### ofBoolList -/
@[simp] theorem getMsb_ofBoolListBE : (ofBoolListBE bs).getMsb i = bs.getD i false := by
@@ -1786,4 +1789,54 @@ theorem getLsb_replicate {n w : Nat} (x : BitVec w) :
simp only [show ¬i < w * n by omega, decide_False, cond_false, hi, Bool.false_and]
apply BitVec.getLsb_ge (x := x) (i := i - w * n) (ge := by omega)
/-! ### intMin -/
/-- The bitvector of width `w` that has the smallest value when interpreted as an integer. -/
abbrev intMin (w : Nat) := twoPow w (w - 1)
theorem getLsb_intMin (w : Nat) : (intMin w).getLsb i = decide (i + 1 = w) := by
simp only [getLsb_twoPow, boolToPropSimps]
omega
@[simp, bv_toNat]
theorem toNat_intMin : (intMin w).toNat = 2 ^ (w - 1) % 2 ^ w := by
simp
@[simp]
theorem neg_intMin {w : Nat} : -intMin w = intMin w := by
by_cases h : 0 < w
· simp [bv_toNat, h]
· simp only [Nat.not_lt, Nat.le_zero_eq] at h
simp [bv_toNat, h]
/-! ### intMax -/
/-- The bitvector of width `w` that has the largest value when interpreted as an integer. -/
abbrev intMax (w : Nat) := (twoPow w (w - 1)) - 1
@[simp, bv_toNat]
theorem toNat_intMax : (intMax w).toNat = 2 ^ (w - 1) - 1 := by
simp only [intMax]
by_cases h : w = 0
· simp [h]
· have h' : 0 < w := by omega
rw [toNat_sub, toNat_twoPow, Nat.sub_add_comm (by simpa [h'] using Nat.one_le_two_pow),
Nat.add_sub_assoc (by simpa [h'] using Nat.one_le_two_pow),
Nat.two_pow_pred_mod_two_pow h', ofNat_eq_ofNat, toNat_ofNat, Nat.one_mod_two_pow h',
Nat.add_mod_left, Nat.mod_eq_of_lt]
have := Nat.two_pow_pred_lt_two_pow h'
have := Nat.two_pow_pos w
omega
@[simp]
theorem getLsb_intMax (w : Nat) : (intMax w).getLsb i = decide (i + 1 < w) := by
rw [ testBit_toNat, toNat_intMax, Nat.testBit_two_pow_sub_one, decide_eq_decide]
omega
@[simp] theorem intMax_add_one {w : Nat} : intMax w + 1#w = intMin w := by
simp only [toNat_eq, toNat_intMax, toNat_add, toNat_intMin, toNat_ofNat, Nat.add_mod_mod]
by_cases h : w = 0
· simp [h]
· rw [Nat.sub_add_cancel (Nat.two_pow_pos (w - 1)), Nat.two_pow_pred_mod_two_pow (by omega)]
end BitVec

View File

@@ -55,6 +55,12 @@ theorem eq_iff_iff {a b : Bool} : a = b ↔ (a ↔ b) := by cases b <;> simp
theorem decide_true_eq {b : Bool} [Decidable (true = b)] : decide (true = b) = b := by cases b <;> simp
theorem decide_false_eq {b : Bool} [Decidable (false = b)] : decide (false = b) = !b := by cases b <;> simp
-- These lemmas assist with confluence.
@[simp] theorem eq_false_imp_eq_true_iff :
(a b : Bool), ((a = false b = true) (b = false a = true)) = True := by decide
@[simp] theorem eq_true_imp_eq_false_iff :
(a b : Bool), ((a = true b = false) (b = true a = false)) = True := by decide
/-! ### and -/
@[simp] theorem and_self_left : (a b : Bool), (a && (a && b)) = (a && b) := by decide
@@ -91,6 +97,11 @@ Needed for confluence of term `(a && b) ↔ a` which reduces to `(a && b) = a` v
@[simp] theorem iff_self_and : (a b : Bool), (a = (a && b)) (a b) := by decide
@[simp] theorem iff_and_self : (a b : Bool), (b = (a && b)) (b a) := by decide
@[simp] theorem not_and_iff_left_iff_imp : (a b : Bool), ((!a && b) = a) !a !b := by decide
@[simp] theorem and_not_iff_right_iff_imp : (a b : Bool), ((a && !b) = b) !a !b := by decide
@[simp] theorem iff_not_self_and : (a b : Bool), (a = (!a && b)) !a !b := by decide
@[simp] theorem iff_and_not_self : (a b : Bool), (b = (a && !b)) !a !b := by decide
/-! ### or -/
@[simp] theorem or_self_left : (a b : Bool), (a || (a || b)) = (a || b) := by decide
@@ -120,6 +131,11 @@ Needed for confluence of term `(a || b) ↔ a` which reduces to `(a || b) = a` v
@[simp] theorem iff_self_or : (a b : Bool), (a = (a || b)) (b a) := by decide
@[simp] theorem iff_or_self : (a b : Bool), (b = (a || b)) (a b) := by decide
@[simp] theorem not_or_iff_left_iff_imp : (a b : Bool), ((!a || b) = a) a b := by decide
@[simp] theorem or_not_iff_right_iff_imp : (a b : Bool), ((a || !b) = b) a b := by decide
@[simp] theorem iff_not_self_or : (a b : Bool), (a = (!a || b)) a b := by decide
@[simp] theorem iff_or_not_self : (a b : Bool), (b = (a || !b)) a b := by decide
theorem or_comm : (x y : Bool), (x || y) = (y || x) := by decide
instance : Std.Commutative (· || ·) := or_comm
@@ -134,7 +150,7 @@ theorem and_or_distrib_right : ∀ (x y z : Bool), ((x || y) && z) = (x && z ||
theorem or_and_distrib_left : (x y z : Bool), (x || y && z) = ((x || y) && (x || z)) := by decide
theorem or_and_distrib_right : (x y z : Bool), (x && y || z) = ((x || z) && (y || z)) := by decide
theorem and_xor_distrib_left : (x y z : Bool), (x && xor y z) = xor (x && y) (x && z) := by decide
theorem and_xor_distrib_left : (x y z : Bool), (x && xor y z) = xor (x && y) (x && z) := by decide
theorem and_xor_distrib_right : (x y z : Bool), (xor x y && z) = xor (x && z) (y && z) := by decide
/-- De Morgan's law for boolean and -/
@@ -202,8 +218,11 @@ instance : Std.LawfulIdentity (· != ·) false where
@[simp] theorem not_beq_self : (x : Bool), ((!x) == x) = false := by decide
@[simp] theorem beq_not_self : (x : Bool), (x == !x) = false := by decide
@[simp] theorem not_bne_self : (x : Bool), ((!x) != x) = true := by decide
@[simp] theorem bne_not_self : (x : Bool), (x != !x) = true := by decide
@[simp] theorem not_bne : (a b : Bool), ((!a) != b) = !(a != b) := by decide
@[simp] theorem bne_not : (a b : Bool), (a != !b) = !(a != b) := by decide
theorem not_bne_self : (x : Bool), ((!x) != x) = true := by decide
theorem bne_not_self : (x : Bool), (x != !x) = true := by decide
/-
Added for equivalence with `Bool.not_beq_self` and needed for confluence
@@ -235,8 +254,10 @@ theorem beq_eq_decide_eq [BEq α] [LawfulBEq α] [DecidableEq α] (a b : α) :
· simp [ne_of_beq_false h]
· simp [eq_of_beq h]
@[simp] theorem not_eq_not : {a b : Bool}, ¬a = !b a = b := by decide
theorem eq_not : (a b : Bool), (a = (!b)) (a b) := by decide
theorem not_eq : (a b : Bool), ((!a) = b) (a b) := by decide
@[simp] theorem not_eq_not : {a b : Bool}, ¬a = !b a = b := by decide
@[simp] theorem not_not_eq : {a b : Bool}, ¬(!a) = b a = b := by decide
@[simp] theorem coe_iff_coe : (a b : Bool), (a b) a = b := by decide
@@ -360,9 +381,6 @@ def toNat (b : Bool) : Nat := cond b 1 0
theorem toNat_le (c : Bool) : c.toNat 1 := by
cases c <;> trivial
@[deprecated toNat_le (since := "2024-02-23")]
abbrev toNat_le_one := toNat_le
theorem toNat_lt (b : Bool) : b.toNat < 2 :=
Nat.lt_succ_of_le (toNat_le _)
@@ -427,16 +445,18 @@ theorem not_ite_eq_false_eq_true (p : Prop) [h : Decidable p] (b c : Bool) :
cases h with | _ p => simp [p]
/-
Added for confluence between `if_true_left` and `ite_false_same` on
`if b = true then True else b = true`
It would be nice to have this for confluence between `if_true_left` and `ite_false_same` on
`if b = true then True else b = true`.
However the discrimination tree key is just `→`, so this is tried too often.
-/
@[simp] theorem eq_false_imp_eq_true : (b:Bool), (b = false b = true) (b = true) := by decide
theorem eq_false_imp_eq_true : (b:Bool), (b = false b = true) (b = true) := by decide
/-
Added for confluence between `if_true_left` and `ite_false_same` on
`if b = false then True else b = false`
It would be nice to have this for confluence between `if_true_left` and `ite_false_same` on
`if b = false then True else b = false`.
However the discrimination tree key is just `→`, so this is tried too often.
-/
@[simp] theorem eq_true_imp_eq_false : (b:Bool), (b = true b = false) (b = false) := by decide
theorem eq_true_imp_eq_false : (b:Bool), (b = true b = false) (b = false) := by decide
/-! ### forall -/
@@ -509,6 +529,10 @@ protected theorem cond_false {α : Type u} {a b : α} : cond false a b = b := co
@[simp] theorem cond_true_right : (c t : Bool), cond c t true = (!c || t) := by decide
@[simp] theorem cond_false_right : (c t : Bool), cond c t false = ( c && t) := by decide
-- These restore confluence between the above lemmas and `cond_not`.
@[simp] theorem cond_true_not_same : (c b : Bool), cond c (!c) b = (!c && b) := by decide
@[simp] theorem cond_false_not_same : (c b : Bool), cond c b (!c) = (!c || b) := by decide
@[simp] theorem cond_true_same : (c b : Bool), cond c c b = (c || b) := by decide
@[simp] theorem cond_false_same : (c b : Bool), cond c b c = (c && b) := by decide
@@ -522,7 +546,7 @@ theorem apply_cond (f : α → β) {b : Bool} {a a' : α} :
f (bif b then a else a') = bif b then f a else f a' := by
cases b <;> simp
/-# decidability -/
/-! # decidability -/
protected theorem decide_coe (b : Bool) [Decidable (b = true)] : decide (b = true) = b := decide_eq_true
@@ -538,6 +562,21 @@ protected theorem decide_coe (b : Bool) [Decidable (b = true)] : decide (b = tru
decide (p q) = (decide p == decide q) := by
cases dp with | _ p => simp [p]
@[boolToPropSimps]
theorem and_eq_decide (p q : Prop) [dpq : Decidable (p q)] [dp : Decidable p] [dq : Decidable q] :
(p && q) = decide (p q) := by
cases dp with | _ p => simp [p]
@[boolToPropSimps]
theorem or_eq_decide (p q : Prop) [dpq : Decidable (p q)] [dp : Decidable p] [dq : Decidable q] :
(p || q) = decide (p q) := by
cases dp with | _ p => simp [p]
@[boolToPropSimps]
theorem decide_beq_decide (p q : Prop) [dpq : Decidable (p q)] [dp : Decidable p] [dq : Decidable q] :
(decide p == decide q) = decide (p q) := by
cases dp with | _ p => simp [p]
end Bool
export Bool (cond_eq_if)

View File

@@ -275,6 +275,22 @@ def atEnd : Iterator → Bool
def hasNext : Iterator Bool
| arr, i => i < arr.size
/-- The byte at the current position. --/
@[inline]
def curr' (it : Iterator) (h : it.hasNext) : UInt8 :=
match it with
| arr, i =>
have : i < arr.size := by
simp only [hasNext, decide_eq_true_eq] at h
assumption
arr[i]
/-- Moves the iterator's position forward by one byte. --/
@[inline]
def next' (it : Iterator) (_h : it.hasNext) : Iterator :=
match it with
| arr, i => arr, i + 1
/-- True if the position is not zero. -/
@[inline]
def hasPrev : Iterator Bool

View File

@@ -63,27 +63,27 @@ instance : Inhabited Char where
default := 'A'
/-- Is the character a space (U+0020) a tab (U+0009), a carriage return (U+000D) or a newline (U+000A)? -/
def isWhitespace (c : Char) : Bool :=
@[inline] def isWhitespace (c : Char) : Bool :=
c = ' ' || c = '\t' || c = '\r' || c = '\n'
/-- Is the character in `ABCDEFGHIJKLMNOPQRSTUVWXYZ`? -/
def isUpper (c : Char) : Bool :=
@[inline] def isUpper (c : Char) : Bool :=
c.val 65 && c.val 90
/-- Is the character in `abcdefghijklmnopqrstuvwxyz`? -/
def isLower (c : Char) : Bool :=
@[inline] def isLower (c : Char) : Bool :=
c.val 97 && c.val 122
/-- Is the character in `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`? -/
def isAlpha (c : Char) : Bool :=
@[inline] def isAlpha (c : Char) : Bool :=
c.isUpper || c.isLower
/-- Is the character in `0123456789`? -/
def isDigit (c : Char) : Bool :=
@[inline] def isDigit (c : Char) : Bool :=
c.val 48 && c.val 57
/-- Is the character in `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`? -/
def isAlphanum (c : Char) : Bool :=
@[inline] def isAlphanum (c : Char) : Bool :=
c.isAlpha || c.isDigit
/-- Convert an upper case character to its lower case character.

View File

@@ -11,9 +11,6 @@ import Init.ByCases
import Init.Conv
import Init.Omega
-- Remove after the next stage0 update
set_option allowUnsafeReducibility true
namespace Fin
/-- If you actually have an element of `Fin n`, then the `n` is always positive -/
@@ -57,9 +54,6 @@ theorem mk_val (i : Fin n) : (⟨i, i.isLt⟩ : Fin n) = i := Fin.eta ..
@[simp] theorem val_ofNat' (a : Nat) (is_pos : n > 0) :
(Fin.ofNat' a is_pos).val = a % n := rfl
@[deprecated ofNat'_zero_val (since := "2024-02-22")]
theorem ofNat'_zero_val : (Fin.ofNat' 0 h).val = 0 := Nat.zero_mod _
@[simp] theorem mod_val (a b : Fin n) : (a % b).val = a.val % b.val :=
rfl
@@ -141,6 +135,12 @@ theorem eq_zero_or_eq_succ {n : Nat} : ∀ i : Fin (n + 1), i = 0 ∃ j : Fi
theorem eq_succ_of_ne_zero {n : Nat} {i : Fin (n + 1)} (hi : i 0) : j : Fin n, i = j.succ :=
(eq_zero_or_eq_succ i).resolve_left hi
protected theorem le_antisymm_iff {x y : Fin n} : x = y x y y x :=
Fin.ext_iff.trans Nat.le_antisymm_iff
protected theorem le_antisymm {x y : Fin n} (h1 : x y) (h2 : y x) : x = y :=
Fin.le_antisymm_iff.2 h1, h2
@[simp] theorem val_rev (i : Fin n) : rev i = n - (i + 1) := rfl
@[simp] theorem rev_rev (i : Fin n) : rev (rev i) = i := Fin.ext <| by

View File

@@ -10,5 +10,6 @@ import Init.Data.Int.DivMod
import Init.Data.Int.DivModLemmas
import Init.Data.Int.Gcd
import Init.Data.Int.Lemmas
import Init.Data.Int.LemmasAux
import Init.Data.Int.Order
import Init.Data.Int.Pow

View File

@@ -14,9 +14,6 @@ import Init.RCases
# Lemmas about integer division needed to bootstrap `omega`.
-/
-- Remove after the next stage0 update
set_option allowUnsafeReducibility true
open Nat (succ)
namespace Int
@@ -57,7 +54,7 @@ protected theorem dvd_mul_right (a b : Int) : a a * b := ⟨_, rfl⟩
protected theorem dvd_mul_left (a b : Int) : b a * b := _, Int.mul_comm ..
protected theorem neg_dvd {a b : Int} : -a b a b := by
@[simp] protected theorem neg_dvd {a b : Int} : -a b a b := by
constructor <;> exact fun k, e =>
-k, by simp [e, Int.neg_mul, Int.mul_neg, Int.neg_neg]
@@ -357,6 +354,7 @@ theorem add_ediv_of_dvd_left {a b c : Int} (H : c a) : (a + b) / c = a / c +
@[simp] theorem mul_ediv_cancel_left (b : Int) (H : a 0) : (a * b) / a = b :=
Int.mul_comm .. Int.mul_ediv_cancel _ H
theorem div_nonneg_iff_of_pos {a b : Int} (h : 0 < b) : a / b 0 a 0 := by
rw [Int.div_def]
match b, h with
@@ -454,6 +452,12 @@ theorem lt_mul_ediv_self_add {x k : Int} (h : 0 < k) : x < k * (x / k) + k :=
@[simp] theorem add_mul_emod_self_left (a b c : Int) : (a + b * c) % b = a % b := by
rw [Int.mul_comm, Int.add_mul_emod_self]
@[simp] theorem add_neg_mul_emod_self {a b c : Int} : (a + -(b * c)) % c = a % c := by
rw [Int.neg_mul_eq_neg_mul, add_mul_emod_self]
@[simp] theorem add_neg_mul_emod_self_left {a b c : Int} : (a + -(b * c)) % b = a % b := by
rw [Int.neg_mul_eq_mul_neg, add_mul_emod_self_left]
@[simp] theorem add_emod_self {a b : Int} : (a + b) % b = a % b := by
have := add_mul_emod_self_left a b 1; rwa [Int.mul_one] at this
@@ -498,9 +502,12 @@ theorem mul_emod (a b n : Int) : (a * b) % n = (a % n) * (b % n) % n := by
Int.mul_assoc, Int.mul_assoc, Int.mul_add n _ _, add_mul_emod_self_left,
Int.mul_assoc, add_mul_emod_self]
@[local simp] theorem emod_self {a : Int} : a % a = 0 := by
@[simp] theorem emod_self {a : Int} : a % a = 0 := by
have := mul_emod_left 1 a; rwa [Int.one_mul] at this
@[simp] theorem neg_emod_self (a : Int) : -a % a = 0 := by
rw [neg_emod, Int.sub_self, zero_emod]
@[simp] theorem emod_emod_of_dvd (n : Int) {m k : Int}
(h : m k) : (n % k) % m = n % m := by
conv => rhs; rw [ emod_add_ediv n k]
@@ -596,6 +603,14 @@ theorem emod_eq_zero_of_dvd : ∀ {a b : Int}, a b → b % a = 0
theorem dvd_iff_emod_eq_zero (a b : Int) : a b b % a = 0 :=
emod_eq_zero_of_dvd, dvd_of_emod_eq_zero
@[simp] theorem neg_mul_emod_left (a b : Int) : -(a * b) % b = 0 := by
rw [ dvd_iff_emod_eq_zero, Int.dvd_neg]
exact Int.dvd_mul_left a b
@[simp] theorem neg_mul_emod_right (a b : Int) : -(a * b) % a = 0 := by
rw [ dvd_iff_emod_eq_zero, Int.dvd_neg]
exact Int.dvd_mul_right a b
instance decidableDvd : DecidableRel (α := Int) (· ·) := fun _ _ =>
decidable_of_decidable_of_iff (dvd_iff_emod_eq_zero ..).symm
@@ -620,6 +635,12 @@ theorem neg_ediv_of_dvd : ∀ {a b : Int}, b a → (-a) / b = -(a / b)
· simp [bz]
· rw [Int.neg_mul_eq_mul_neg, Int.mul_ediv_cancel_left _ bz, Int.mul_ediv_cancel_left _ bz]
@[simp] theorem neg_mul_ediv_cancel (a b : Int) (h : b 0) : -(a * b) / b = -a := by
rw [neg_ediv_of_dvd (Int.dvd_mul_left a b), mul_ediv_cancel _ h]
@[simp] theorem neg_mul_ediv_cancel_left (a b : Int) (h : a 0) : -(a * b) / a = -b := by
rw [neg_ediv_of_dvd (Int.dvd_mul_right a b), mul_ediv_cancel_left _ h]
theorem sub_ediv_of_dvd (a : Int) {b c : Int}
(hcb : c b) : (a - b) / c = a / c - b / c := by
rw [Int.sub_eq_add_neg, Int.sub_eq_add_neg, Int.add_ediv_of_dvd_right (Int.dvd_neg.2 hcb)]
@@ -635,13 +656,22 @@ theorem sub_ediv_of_dvd (a : Int) {b c : Int}
@[simp] protected theorem ediv_self {a : Int} (H : a 0) : a / a = 1 := by
have := Int.mul_ediv_cancel 1 H; rwa [Int.one_mul] at this
@[simp] protected theorem neg_ediv_self (a : Int) (h : a 0) : (-a) / a = -1 := by
rw [neg_ediv_of_dvd (Int.dvd_refl a), Int.ediv_self h]
@[simp]
theorem emod_sub_cancel (x y : Int): (x - y)%y = x%y := by
theorem emod_sub_cancel (x y : Int): (x - y) % y = x % y := by
by_cases h : y = 0
· simp [h]
· simp only [Int.emod_def, Int.sub_ediv_of_dvd, Int.dvd_refl, Int.ediv_self h, Int.mul_sub]
simp [Int.mul_one, Int.sub_sub, Int.add_comm y]
@[simp] theorem add_neg_emod_self (a b : Int) : (a + -b) % b = a % b := by
rw [ Int.sub_eq_add_neg, emod_sub_cancel]
@[simp] theorem neg_add_emod_self (a b : Int) : (-a + b) % a = b % a := by
rw [Int.add_comm, add_neg_emod_self]
/-- If `a % b = c` then `b` divides `a - c`. -/
theorem dvd_sub_of_emod_eq {a b c : Int} (h : a % b = c) : b a - c := by
have hx : (a % b) % b = c % b := by
@@ -891,6 +921,14 @@ theorem mod_eq_zero_of_dvd : ∀ {a b : Int}, a b → mod b a = 0
theorem dvd_iff_mod_eq_zero (a b : Int) : a b mod b a = 0 :=
mod_eq_zero_of_dvd, dvd_of_mod_eq_zero
@[simp] theorem neg_mul_mod_right (a b : Int) : (-(a * b)).mod a = 0 := by
rw [ dvd_iff_mod_eq_zero, Int.dvd_neg]
exact Int.dvd_mul_right a b
@[simp] theorem neg_mul_mod_left (a b : Int) : (-(a * b)).mod b = 0 := by
rw [ dvd_iff_mod_eq_zero, Int.dvd_neg]
exact Int.dvd_mul_left a b
protected theorem div_mul_cancel {a b : Int} (H : b a) : a.div b * b = a :=
div_mul_cancel_of_mod_eq_zero (mod_eq_zero_of_dvd H)
@@ -903,6 +941,10 @@ protected theorem eq_mul_of_div_eq_right {a b c : Int}
@[simp] theorem mod_self {a : Int} : a.mod a = 0 := by
have := mul_mod_left 1 a; rwa [Int.one_mul] at this
@[simp] theorem neg_mod_self (a : Int) : (-a).mod a = 0 := by
rw [ dvd_iff_mod_eq_zero, Int.dvd_neg]
exact Int.dvd_refl a
theorem lt_div_add_one_mul_self (a : Int) {b : Int} (H : 0 < b) : a < (a.div b + 1) * b := by
rw [Int.add_mul, Int.one_mul, Int.mul_comm]
exact Int.lt_add_of_sub_left_lt <| Int.mod_def .. mod_lt_of_pos _ H
@@ -1091,8 +1133,7 @@ theorem bmod_mul_bmod : Int.bmod (Int.bmod x n * y) n = Int.bmod (x * y) n := by
next p =>
simp
next p =>
rw [Int.sub_mul, Int.sub_eq_add_neg, Int.mul_neg]
simp
rw [Int.sub_mul, Int.sub_eq_add_neg, Int.mul_neg, bmod_add_mul_cancel, emod_mul_bmod_congr]
@[simp] theorem mul_bmod_bmod : Int.bmod (x * Int.bmod y n) n = Int.bmod (x * y) n := by
rw [Int.mul_comm x, bmod_mul_bmod, Int.mul_comm x]

View File

@@ -7,6 +7,7 @@ prelude
import Init.Data.Int.Basic
import Init.Conv
import Init.NotationExtra
import Init.PropLemmas
namespace Int
@@ -288,7 +289,7 @@ protected theorem neg_sub (a b : Int) : -(a - b) = b - a := by
protected theorem sub_sub_self (a b : Int) : a - (a - b) = b := by
simp [Int.sub_eq_add_neg, Int.add_assoc]
protected theorem sub_neg (a b : Int) : a - -b = a + b := by simp [Int.sub_eq_add_neg]
@[simp] protected theorem sub_neg (a b : Int) : a - -b = a + b := by simp [Int.sub_eq_add_neg]
@[simp] protected theorem sub_add_cancel (a b : Int) : a - b + b = a :=
Int.neg_add_cancel_right a b
@@ -444,10 +445,10 @@ protected theorem neg_mul_eq_neg_mul (a b : Int) : -(a * b) = -a * b :=
protected theorem neg_mul_eq_mul_neg (a b : Int) : -(a * b) = a * -b :=
Int.neg_eq_of_add_eq_zero <| by rw [ Int.mul_add, Int.add_right_neg, Int.mul_zero]
@[local simp] protected theorem neg_mul (a b : Int) : -a * b = -(a * b) :=
@[simp] protected theorem neg_mul (a b : Int) : -a * b = -(a * b) :=
(Int.neg_mul_eq_neg_mul a b).symm
@[local simp] protected theorem mul_neg (a b : Int) : a * -b = -(a * b) :=
@[simp] protected theorem mul_neg (a b : Int) : a * -b = -(a * b) :=
(Int.neg_mul_eq_mul_neg a b).symm
protected theorem neg_mul_neg (a b : Int) : -a * -b = a * b := by simp
@@ -486,6 +487,9 @@ protected theorem mul_eq_zero {a b : Int} : a * b = 0 ↔ a = 0 b = 0 := by
protected theorem mul_ne_zero {a b : Int} (a0 : a 0) (b0 : b 0) : a * b 0 :=
Or.rec a0 b0 Int.mul_eq_zero.mp
@[simp] protected theorem mul_ne_zero_iff (a b : Int) : a * b 0 a 0 b 0 := by
rw [ne_eq, Int.mul_eq_zero, not_or, ne_eq]
protected theorem eq_of_mul_eq_mul_right {a b c : Int} (ha : a 0) (h : b * a = c * a) : b = c :=
have : (b - c) * a = 0 := by rwa [Int.sub_mul, Int.sub_eq_zero]
Int.sub_eq_zero.1 <| (Int.mul_eq_zero.mp this).resolve_right ha

View File

@@ -0,0 +1,40 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison
-/
prelude
import Init.Data.Int.Order
import Init.Omega
/-!
# Further lemmas about `Int` relying on `omega` automation.
-/
namespace Int
@[simp] theorem toNat_sub' (a : Int) (b : Nat) : a.toNat - b = (a - b).toNat := by
simp only [Int.toNat]
split <;> rename_i x a
· simp only [Int.ofNat_eq_coe]
split <;> rename_i y b h
· simp at h
omega
· simp [Int.negSucc_eq] at h
omega
· simp only [Nat.zero_sub]
split <;> rename_i y b h
· simp [Int.negSucc_eq] at h
omega
· rfl
@[simp] theorem toNat_sub_max_self (a : Int) : (a - max a 0).toNat = 0 := by
simp [toNat]
split <;> simp_all <;> omega
@[simp] theorem toNat_sub_self_max (a : Int) : (a - max 0 a).toNat = 0 := by
simp [toNat]
split <;> simp_all <;> omega
end Int

View File

@@ -240,9 +240,24 @@ theorem le_natAbs {a : Int} : a ≤ natAbs a :=
theorem negSucc_lt_zero (n : Nat) : -[n+1] < 0 :=
Int.not_le.1 fun h => let _, h := eq_ofNat_of_zero_le h; nomatch h
theorem negSucc_le_zero (n : Nat) : -[n+1] 0 :=
Int.le_of_lt (negSucc_lt_zero n)
@[simp] theorem negSucc_not_nonneg (n : Nat) : 0 -[n+1] False := by
simp only [Int.not_le, iff_false]; exact Int.negSucc_lt_zero n
@[simp] theorem ofNat_max_zero (n : Nat) : (max (n : Int) 0) = n := by
rw [Int.max_eq_left (ofNat_zero_le n)]
@[simp] theorem zero_max_ofNat (n : Nat) : (max 0 (n : Int)) = n := by
rw [Int.max_eq_right (ofNat_zero_le n)]
@[simp] theorem negSucc_max_zero (n : Nat) : (max (Int.negSucc n) 0) = 0 := by
rw [Int.max_eq_right (negSucc_le_zero _)]
@[simp] theorem zero_max_negSucc (n : Nat) : (max 0 (Int.negSucc n)) = 0 := by
rw [Int.max_eq_left (negSucc_le_zero _)]
protected theorem add_le_add_left {a b : Int} (h : a b) (c : Int) : c + a c + b :=
let n, hn := le.dest h; le.intro n <| by rw [Int.add_assoc, hn]
@@ -470,8 +485,16 @@ theorem toNat_eq_max : ∀ a : Int, (toNat a : Int) = max a 0
@[simp] theorem toNat_ofNat (n : Nat) : toNat n = n := rfl
@[simp] theorem toNat_negSucc (n : Nat) : (Int.negSucc n).toNat = 0 := by
simp [toNat]
@[simp] theorem toNat_ofNat_add_one {n : Nat} : ((n : Int) + 1).toNat = n + 1 := rfl
@[simp] theorem ofNat_toNat (a : Int) : (a.toNat : Int) = max a 0 := by
match a with
| Int.ofNat n => simp
| Int.negSucc n => simp
theorem self_le_toNat (a : Int) : a toNat a := by rw [toNat_eq_max]; apply Int.le_max_left
@[simp] theorem le_toNat {n : Nat} {z : Int} (h : 0 z) : n z.toNat (n : Int) z := by
@@ -1006,7 +1029,7 @@ theorem natAbs_mul_self : ∀ {a : Int}, ↑(natAbs a * natAbs a) = a * a
theorem eq_nat_or_neg (a : Int) : n : Nat, a = n a = -n := _, natAbs_eq a
theorem natAbs_mul_natAbs_eq {a b : Int} {c : Nat}
(h : a * b = (c : Int)) : a.natAbs * b.natAbs = c := by rw [ natAbs_mul, h, natAbs]
(h : a * b = (c : Int)) : a.natAbs * b.natAbs = c := by rw [ natAbs_mul, h, natAbs.eq_def]
@[simp] theorem natAbs_mul_self' (a : Int) : (natAbs a * natAbs a : Int) = a * a := by
rw [ Int.ofNat_mul, natAbs_mul_self]

View File

@@ -73,6 +73,13 @@ theorem pmap_map {p : β → Prop} (g : ∀ b, p b → γ) (f : α → β) (l H)
· rfl
· simp only [*, pmap, map]
@[simp] theorem attach_cons (x : α) (xs : List α) :
(x :: xs).attach = x, mem_cons_self x xs :: xs.attach.map fun y, h => y, mem_cons_of_mem x h := by
simp only [attach, attachWith, pmap, map_pmap, cons.injEq, true_and]
apply pmap_congr
intros a _ m' _
rfl
theorem pmap_eq_map_attach {p : α Prop} (f : a, p a β) (l H) :
pmap f l H = l.attach.map fun x => f x.1 (H _ x.2) := by
rw [attach, attachWith, map_pmap]; exact pmap_congr l fun _ _ _ _ => rfl
@@ -121,23 +128,14 @@ theorem length_attach (L : List α) : L.attach.length = L.length :=
theorem pmap_eq_nil {p : α Prop} {f : a, p a β} {l H} : pmap f l H = [] l = [] := by
rw [ length_eq_zero, length_pmap, length_eq_zero]
theorem pmap_ne_nil {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) : xs.pmap f H [] xs [] := by
simp
@[simp]
theorem attach_eq_nil (l : List α) : l.attach = [] l = [] :=
pmap_eq_nil
theorem getLast_pmap (p : α Prop) (f : a, p a β) (l : List α)
(hl₁ : a l, p a) (hl₂ : l []) :
(l.pmap f hl₁).getLast (mt List.pmap_eq_nil.1 hl₂) =
f (l.getLast hl₂) (hl₁ _ (List.getLast_mem hl₂)) := by
induction l with
| nil => apply (hl₂ rfl).elim
| cons l_hd l_tl l_ih =>
by_cases hl_tl : l_tl = []
· simp [hl_tl]
· simp only [pmap]
rw [getLast_cons, l_ih _ hl_tl]
simp only [getLast_cons hl_tl]
theorem getElem?_pmap {p : α Prop} (f : a, p a β) {l : List α} (h : a l, p a) (n : Nat) :
(pmap f l h)[n]? = Option.pmap f l[n]? fun x H => h x (getElem?_mem H) := by
induction l generalizing n with
@@ -181,7 +179,22 @@ theorem get_pmap {p : α → Prop} (f : ∀ a, p a → β) {l : List α} (h :
simp only [get_eq_getElem]
simp [getElem_pmap]
theorem pmap_append {p : ι Prop} (f : a : ι, p a α) (l₁ l₂ : List ι)
@[simp] theorem head?_pmap {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) : (xs.pmap f H).head? = xs.attach.head?.map fun a, m => f a (H a m) := by
induction xs with
| nil => simp
| cons x xs ih =>
simp at ih
simp [head?_pmap, ih]
@[simp] theorem head_pmap {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) (h : xs.pmap f H []) :
(xs.pmap f H).head h = f (xs.head (by simpa using h)) (H _ (head_mem _)) := by
induction xs with
| nil => simp at h
| cons x xs ih => simp [head_pmap, ih]
@[simp] theorem pmap_append {p : ι Prop} (f : a : ι, p a α) (l₁ l₂ : List ι)
(h : a l₁ ++ l₂, p a) :
(l₁ ++ l₂).pmap f h =
(l₁.pmap f fun a ha => h a (mem_append_left l₂ ha)) ++
@@ -197,3 +210,63 @@ theorem pmap_append' {p : α → Prop} (f : ∀ a : α, p a → β) (l₁ l₂ :
((l₁ ++ l₂).pmap f fun a ha => (List.mem_append.1 ha).elim (h₁ a) (h₂ a)) =
l₁.pmap f h₁ ++ l₂.pmap f h₂ :=
pmap_append f l₁ l₂ _
@[simp] theorem pmap_reverse {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs.reverse P a) : xs.reverse.pmap f H = (xs.pmap f (fun a h => H a (by simpa using h))).reverse := by
induction xs <;> simp_all
theorem reverse_pmap {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) : (xs.pmap f H).reverse = xs.reverse.pmap f (fun a h => H a (by simpa using h)) := by
rw [pmap_reverse]
@[simp] theorem attach_append (xs ys : List α) :
(xs ++ ys).attach = xs.attach.map (fun x, h => x, mem_append_of_mem_left ys h) ++
ys.attach.map fun x, h => x, mem_append_of_mem_right xs h := by
simp only [attach, attachWith, pmap, map_pmap, pmap_append]
congr 1 <;>
exact pmap_congr _ fun _ _ _ _ => rfl
@[simp] theorem attach_reverse (xs : List α) : xs.reverse.attach = xs.attach.reverse.map fun x, h => x, by simpa using h := by
simp only [attach, attachWith, reverse_pmap, map_pmap]
apply pmap_congr
intros
rfl
theorem reverse_attach (xs : List α) : xs.attach.reverse = xs.reverse.attach.map fun x, h => x, by simpa using h := by
simp only [attach, attachWith, reverse_pmap, map_pmap]
apply pmap_congr
intros
rfl
theorem getLast?_attach {xs : List α} :
xs.attach.getLast? = match h : xs.getLast? with | none => none | some a => some a, mem_of_getLast?_eq_some h := by
rw [getLast?_eq_head?_reverse, reverse_attach, head?_map]
split <;> rename_i h
· simp only [getLast?_eq_none_iff] at h
subst h
simp
· obtain ys, rfl := getLast?_eq_some_iff.mp h
simp
@[simp] theorem getLast?_pmap {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) : (xs.pmap f H).getLast? = xs.attach.getLast?.map fun a, m => f a (H a m) := by
simp only [getLast?_eq_head?_reverse]
rw [reverse_pmap, reverse_attach, head?_map, pmap_eq_map_attach, head?_map]
simp only [Option.map_map]
congr
@[simp] theorem getLast_pmap {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) (h : xs.pmap f H []) :
(xs.pmap f H).getLast h = f (xs.getLast (by simpa using h)) (H _ (getLast_mem _)) := by
simp only [getLast_eq_iff_getLast_eq_some, getLast?_pmap, Option.map_eq_some', Subtype.exists]
refine xs.getLast (by simpa using h), by simp, ?_
simp only [getLast?_attach, and_true]
split <;> rename_i h'
· simp only [getLast?_eq_none_iff] at h'
subst h'
simp at h
· symm
simpa [getLast_eq_iff_getLast_eq_some]
end List

View File

@@ -96,7 +96,7 @@ namespace List
/-! ### concat -/
@[simp high] theorem length_concat (as : List α) (a : α) : (concat as a).length = as.length + 1 := by
theorem length_concat (as : List α) (a : α) : (concat as a).length = as.length + 1 := by
induction as with
| nil => rfl
| cons _ xs ih => simp [concat, ih]
@@ -278,8 +278,9 @@ def getLastD : (as : List α) → (fallback : α) → α
| [], a₀ => a₀
| a::as, _ => getLast (a::as) (fun h => List.noConfusion h)
@[simp] theorem getLastD_nil (a) : @getLastD α [] a = a := rfl
@[simp] theorem getLastD_cons (a b l) : @getLastD α (b::l) a = getLastD l b := by cases l <;> rfl
-- These aren't `simp` lemmas since we always simplify `getLastD` in terms of `getLast?`.
theorem getLastD_nil (a) : @getLastD α [] a = a := rfl
theorem getLastD_cons (a b l) : @getLastD α (b::l) a = getLastD l b := by cases l <;> rfl
/-! ## Head and tail -/
@@ -688,7 +689,7 @@ inductive Mem (a : α) : List α → Prop
| tail (b : α) {as : List α} : Mem a as Mem a (b::as)
instance : Membership α (List α) where
mem := Mem
mem l a := Mem a l
theorem mem_of_elem_eq_true [BEq α] [LawfulBEq α] {a : α} {as : List α} : elem a as = true a as := by
match as with

View File

@@ -222,7 +222,7 @@ theorem append_cancel_right {as bs cs : List α} (h : as ++ bs = cs ++ bs) : as
next => apply append_cancel_right
next => intro h; simp [h]
@[simp] theorem sizeOf_get [SizeOf α] (as : List α) (i : Fin as.length) : sizeOf (as.get i) < sizeOf as := by
theorem sizeOf_get [SizeOf α] (as : List α) (i : Fin as.length) : sizeOf (as.get i) < sizeOf as := by
match as, i with
| a::as, 0, _ => simp_arith [get]
| a::as, i+1, h =>

View File

@@ -47,11 +47,11 @@ theorem length_eq_countP_add_countP (l) : length l = countP p l + countP (fun a
if h : p x then
rw [countP_cons_of_pos _ _ h, countP_cons_of_neg _ _ _, length, ih]
· rw [Nat.add_assoc, Nat.add_comm _ 1, Nat.add_assoc]
· simp only [h, not_true_eq_false, decide_False, not_false_eq_true]
· simp [h]
else
rw [countP_cons_of_pos (fun a => ¬p a) _ _, countP_cons_of_neg _ _ h, length, ih]
· rfl
· simp only [h, not_false_eq_true, decide_True]
· simp [h]
theorem countP_eq_length_filter (l) : countP p l = length (filter p l) := by
induction l with
@@ -234,7 +234,7 @@ theorem count_erase (a b : α) :
rw [if_pos hc_beq, hc, count_cons, Nat.add_sub_cancel]
else
have hc_beq := beq_false_of_ne hc
simp only [hc_beq, if_false, count_cons, count_cons, count_erase a b l]
simp only [hc_beq, if_false, count_cons, count_cons, count_erase a b l, reduceCtorEq]
if ha : b = a then
rw [ha, eq_comm] at hc
rw [if_pos ((beq_iff_eq _ _).2 ha), if_neg (by simpa using Ne.symm hc), Nat.add_zero, Nat.add_zero]

View File

@@ -33,6 +33,25 @@ theorem eraseP_of_forall_not {l : List α} (h : ∀ a, a ∈ l → ¬p a) : l.er
| nil => rfl
| cons _ _ ih => simp [h _ (.head ..), ih (forall_mem_cons.1 h).2]
@[simp] theorem eraseP_eq_nil (xs : List α) (p : α Bool) : xs.eraseP p = [] xs = [] x, p x xs = [x] := by
induction xs with
| nil => simp
| cons x xs ih =>
simp only [eraseP_cons, cond_eq_if]
split <;> rename_i h
· simp only [reduceCtorEq, cons.injEq, false_or]
constructor
· rintro rfl
simpa
· rintro _, _, rfl, rfl
rfl
· simp only [reduceCtorEq, cons.injEq, false_or, false_iff, not_exists, not_and]
rintro x h' rfl
simp_all
theorem eraseP_ne_nil (xs : List α) (p : α Bool) : xs.eraseP p [] xs [] x, p x xs [x] := by
simp
theorem exists_of_eraseP : {l : List α} {a} (al : a l) (pa : p a),
a l₁ l₂, ( b l₁, ¬p b) p a l = l₁ ++ a :: l₂ l.eraseP p = l₁ ++ l₂
| b :: l, a, al, pa =>
@@ -159,6 +178,14 @@ theorem eraseP_append (l₁ l₂ : List α) :
rw [eraseP_append_right _]
simp_all
theorem eraseP_replicate (n : Nat) (a : α) (p : α Bool) :
(replicate n a).eraseP p = if p a then replicate (n - 1) a else replicate n a := by
induction n with
| zero => simp
| succ n ih =>
simp only [replicate_succ, eraseP_cons]
split <;> simp [*]
protected theorem IsPrefix.eraseP (h : l₁ <+: l₂) : l₁.eraseP p <+: l₂.eraseP p := by
rw [IsPrefix] at h
obtain t, rfl := h
@@ -213,8 +240,11 @@ theorem eraseP_eq_iff {p} {l : List α} :
(replicate n a).eraseP p = replicate n a := by
rw [eraseP_of_forall_not (by simp_all)]
theorem Pairwise.eraseP (q) : Pairwise p l Pairwise p (l.eraseP q) :=
Pairwise.sublist <| eraseP_sublist _
theorem Nodup.eraseP (p) : Nodup l Nodup (l.eraseP p) :=
Nodup.sublist <| eraseP_sublist _
Pairwise.eraseP p
theorem eraseP_comm {l : List α} (h : a l, ¬ p a ¬ q a) :
(l.eraseP p).eraseP q = (l.eraseP q).eraseP p := by
@@ -230,6 +260,12 @@ theorem eraseP_comm {l : List α} (h : ∀ a ∈ l, ¬ p a ¬ q a) :
· simp [h₁, h₂, ih (fun b m => h b (mem_cons_of_mem _ m))]
· simp [h₁, h₂, ih (fun b m => h b (mem_cons_of_mem _ m))]
theorem head_eraseP_mem (xs : List α) (p : α Bool) (h) : (xs.eraseP p).head h xs :=
(eraseP_sublist xs).head_mem h
theorem getLast_eraseP_mem (xs : List α) (p : α Bool) (h) : (xs.eraseP p).getLast h xs :=
(eraseP_sublist xs).getLast_mem h
/-! ### erase -/
section erase
variable [BEq α]
@@ -258,6 +294,16 @@ theorem erase_eq_eraseP [LawfulBEq α] (a : α) : ∀ l : List α, l.erase a =
| b :: l => by
if h : a = b then simp [h] else simp [h, Ne.symm h, erase_eq_eraseP a l]
@[simp] theorem erase_eq_nil [LawfulBEq α] (xs : List α) (a : α) :
xs.erase a = [] xs = [] xs = [a] := by
rw [erase_eq_eraseP]
simp
theorem erase_ne_nil [LawfulBEq α] (xs : List α) (a : α) :
xs.erase a [] xs [] xs [a] := by
rw [erase_eq_eraseP]
simp
theorem exists_erase_eq [LawfulBEq α] {a : α} {l : List α} (h : a l) :
l₁ l₂, a l₁ l = l₁ ++ a :: l₂ l.erase a = l₁ ++ l₂ := by
let _, l₁, l₂, h₁, e, h₂, h₃ := exists_of_eraseP h (beq_self_eq_true _)
@@ -294,7 +340,7 @@ theorem mem_of_mem_erase {a b : α} {l : List α} (h : a ∈ l.erase b) : a ∈
@[simp] theorem erase_eq_self_iff [LawfulBEq α] {l : List α} : l.erase a = l a l := by
rw [erase_eq_eraseP', eraseP_eq_self_iff]
simp
simp [forall_mem_ne']
theorem erase_filter [LawfulBEq α] (f : α Bool) (l : List α) :
(filter f l).erase a = filter f (l.erase a) := by
@@ -327,6 +373,11 @@ theorem erase_append [LawfulBEq α] {a : α} {l₁ l₂ : List α} :
(l₁ ++ l₂).erase a = if a l₁ then l₁.erase a ++ l₂ else l₁ ++ l₂.erase a := by
simp [erase_eq_eraseP, eraseP_append]
theorem erase_replicate [LawfulBEq α] (n : Nat) (a b : α) :
(replicate n a).erase b = if b == a then replicate (n - 1) a else replicate n a := by
rw [erase_eq_eraseP]
simp [eraseP_replicate]
theorem erase_comm [LawfulBEq α] (a b : α) (l : List α) :
(l.erase a).erase b = (l.erase b).erase a := by
if ab : a == b then rw [eq_of_beq ab] else ?_
@@ -366,6 +417,9 @@ theorem erase_eq_iff [LawfulBEq α] {a : α} {l : List α} :
rw [erase_of_not_mem]
simp_all
theorem Pairwise.erase [LawfulBEq α] {l : List α} (a) : Pairwise p l Pairwise p (l.erase a) :=
Pairwise.sublist <| erase_sublist _ _
theorem Nodup.erase_eq_filter [LawfulBEq α] {l} (d : Nodup l) (a : α) : l.erase a = l.filter (· != a) := by
induction d with
| nil => rfl
@@ -386,7 +440,13 @@ theorem Nodup.not_mem_erase [LawfulBEq α] {a : α} (h : Nodup l) : a ∉ l.eras
simpa using ((Nodup.mem_erase_iff h).mp H).left
theorem Nodup.erase [LawfulBEq α] (a : α) : Nodup l Nodup (l.erase a) :=
Nodup.sublist <| erase_sublist _ _
Pairwise.erase a
theorem head_erase_mem (xs : List α) (a : α) (h) : (xs.erase a).head h xs :=
(erase_sublist a xs).head_mem h
theorem getLast_erase_mem (xs : List α) (a : α) (h) : (xs.erase a).getLast h xs :=
(erase_sublist a xs).getLast_mem h
end erase
@@ -408,11 +468,26 @@ theorem eraseIdx_eq_take_drop_succ :
| a::l, 0 => by simp
| a::l, i + 1 => by simp [eraseIdx_eq_take_drop_succ l i]
@[simp] theorem eraseIdx_eq_nil {l : List α} {i : Nat} : eraseIdx l i = [] l = [] (length l = 1 i = 0) := by
match l, i with
| [], _
| a::l, 0
| a::l, i + 1 => simp [Nat.succ_inj']
theorem eraseIdx_ne_nil {l : List α} {i : Nat} : eraseIdx l i [] 2 l.length (l.length = 1 i 0) := by
match l with
| []
| [a]
| a::b::l => simp [Nat.succ_inj']
theorem eraseIdx_sublist : (l : List α) (k : Nat), eraseIdx l k <+ l
| [], _ => by simp
| a::l, 0 => by simp
| a::l, k + 1 => by simp [eraseIdx_sublist l k]
theorem mem_of_mem_eraseIdx {l : List α} {i : Nat} {a : α} (h : a l.eraseIdx i) : a l :=
(eraseIdx_sublist _ _).mem h
theorem eraseIdx_subset (l : List α) (k : Nat) : eraseIdx l k l := (eraseIdx_sublist l k).subset
@[simp]
@@ -442,6 +517,23 @@ theorem eraseIdx_append_of_length_le {l : List α} {k : Nat} (hk : length l ≤
| zero => simp_all
| succ k => simp_all [eraseIdx_cons_succ, Nat.succ_sub_succ]
theorem eraseIdx_replicate {n : Nat} {a : α} {k : Nat} :
(replicate n a).eraseIdx k = if k < n then replicate (n - 1) a else replicate n a := by
split <;> rename_i h
· rw [eq_replicate, length_eraseIdx (by simpa using h)]
simp only [length_replicate, true_and]
intro b m
replace m := mem_of_mem_eraseIdx m
simp only [mem_replicate] at m
exact m.2
· rw [eraseIdx_of_length_le (by simpa using h)]
theorem Pairwise.eraseIdx {l : List α} (k) : Pairwise p l Pairwise p (l.eraseIdx k) :=
Pairwise.sublist <| eraseIdx_sublist _ _
theorem Nodup.eraseIdx {l : List α} (k) : Nodup l Nodup (l.eraseIdx k) :=
Pairwise.eraseIdx k
protected theorem IsPrefix.eraseIdx {l l' : List α} (h : l <+: l') (k : Nat) :
eraseIdx l k <+: eraseIdx l' k := by
rcases h with t, rfl

View File

@@ -38,6 +38,45 @@ theorem exists_of_findSome?_eq_some {l : List α} {f : α → Option β} (w : l.
@[simp] theorem findSome?_eq_none : findSome? p l = none x l, p x = none := by
induction l <;> simp [findSome?_cons]; split <;> simp [*]
@[simp] theorem findSome?_isSome_iff (f : α Option β) (l : List α) :
(l.findSome? f).isSome x, x l (f x).isSome := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [findSome?_cons]
split <;> simp_all
@[simp] theorem findSome?_guard (l : List α) : findSome? (Option.guard fun x => p x) l = find? p l := by
induction l with
| nil => simp
| cons x xs ih =>
simp [guard, findSome?, find?]
split <;> rename_i h
· simp only [Option.guard_eq_some] at h
obtain rfl, h := h
simp [h]
· simp only [Option.guard_eq_none] at h
simp [ih, h]
@[simp] theorem filterMap_head? (f : α Option β) (l : List α) : (l.filterMap f).head? = l.findSome? f := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [filterMap_cons, findSome?_cons]
split <;> simp [*]
@[simp] theorem filterMap_head (f : α Option β) (l : List α) (h) :
(l.filterMap f).head h = (l.findSome? f).get (by simp_all [Option.isSome_iff_ne_none]) := by
simp [head_eq_iff_head?_eq_some]
@[simp] theorem filterMap_getLast? (f : α Option β) (l : List α) : (l.filterMap f).getLast? = l.reverse.findSome? f := by
rw [getLast?_eq_head?_reverse]
simp [ filterMap_reverse]
@[simp] theorem filterMap_getLast (f : α Option β) (l : List α) (h) :
(l.filterMap f).getLast h = (l.reverse.findSome? f).get (by simp_all [Option.isSome_iff_ne_none]) := by
simp [getLast_eq_iff_getLast_eq_some]
@[simp] theorem map_findSome? (f : α Option β) (g : β γ) (l : List α) :
(l.findSome? f).map g = l.findSome? (Option.map g f) := by
induction l <;> simp [findSome?_cons]; split <;> simp [*]
@@ -81,7 +120,9 @@ theorem Sublist.findSome?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) :
| cons a h ih
| cons₂ a h ih =>
simp only [findSome?]
split <;> simp_all
split
· simp_all
· exact ih
theorem Sublist.findSome?_eq_none {l₁ l₂ : List α} (h : l₁ <+ l₂) :
l₂.findSome? f = none l₁.findSome? f = none := by
@@ -200,8 +241,23 @@ theorem mem_of_find?_eq_some : ∀ {l}, find? p l = some a → a ∈ l
· simp only [find?_cons]
split <;> simp_all
@[simp] theorem filter_head? (p : α Bool) (l : List α) : (l.filter p).head? = l.find? p := by
rw [ filterMap_eq_filter, filterMap_head?, findSome?_guard]
@[simp] theorem filter_head (p : α Bool) (l : List α) (h) :
(l.filter p).head h = (l.find? p).get (by simp_all [Option.isSome_iff_ne_none]) := by
simp [head_eq_iff_head?_eq_some]
@[simp] theorem filter_getLast? (p : α Bool) (l : List α) : (l.filter p).getLast? = l.reverse.find? p := by
rw [getLast?_eq_head?_reverse]
simp [ filter_reverse]
@[simp] theorem filter_getLast (p : α Bool) (l : List α) (h) :
(l.filter p).getLast h = (l.reverse.find? p).get (by simp_all [Option.isSome_iff_ne_none]) := by
simp [getLast_eq_iff_getLast_eq_some]
@[simp] theorem find?_filterMap (xs : List α) (f : α Option β) (p : β Bool) :
(xs.filterMap f).find? p = (xs.find? (fun a => match f a with | none => false | some b => p b)).map f := by
(xs.filterMap f).find? p = (xs.find? (fun a => (f a).any p)).bind f := by
induction xs with
| nil => simp
| cons x xs ih =>
@@ -236,10 +292,54 @@ theorem find?_join_eq_none (xs : List (List α)) (p : α → Bool) :
xs.join.find? p = none ys xs, x ys, !p x := by
simp
/--
If `find? p` returns `some a` from `xs.join`, then `p a` holds, and
some list in `xs` contains `a`, and no earlier element of that list satisfies `p`.
Moreover, no earlier list in `xs` has an element satisfying `p`.
-/
theorem find?_join_eq_some (xs : List (List α)) (p : α Bool) (a : α) :
xs.join.find? p = some a
p a as ys zs bs, xs = as ++ (ys ++ a :: zs) :: bs
( a as, x a, !p x) ( x ys, !p x) := by
rw [find?_eq_some]
constructor
· rintro h, ys, zs, h₁, h₂
refine h, ?_
rw [join_eq_append] at h₁
obtain (as, bs, rfl, rfl, h₁ | as, bs, c, cs, ds, rfl, rfl, h₁) := h₁
· replace h₁ := h₁.symm
rw [join_eq_cons] at h₁
obtain bs, cs, ds, rfl, h₁, rfl := h₁
refine as ++ bs, [], cs, ds, by simp, ?_
simp
rintro a (ma | mb) x m
· simpa using h₂ x (by simpa using a, ma, m)
· specialize h₁ _ mb
simp_all
· simp [h₁]
refine as, bs, ?_
refine ?_, ?_, ?_
· simp_all
· intro l ml a m
simpa using h₂ a (by simpa using .inl l, ml, m)
· intro x m
simpa using h₂ x (by simpa using .inr m)
· rintro h, as, ys, zs, bs, rfl, h₁, h₂
refine h, as.join ++ ys, zs ++ bs.join, by simp, ?_
intro a m
simp at m
obtain l, ml, m | m := m
· exact h₁ l ml a m
· exact h₂ a m
@[simp] theorem find?_bind (xs : List α) (f : α List β) (p : β Bool) :
(xs.bind f).find? p = xs.findSome? (fun x => (f x).find? p) := by
simp [bind_def, findSome?_map]; rfl
theorem find?_bind_eq_none (xs : List α) (f : α List β) (p : β Bool) :
(xs.bind f).find? p = none x xs, y f x, !p y := by
simp
theorem find?_replicate : find? p (replicate n a) = if n = 0 then none else if p a then some a else none := by
cases n
· simp
@@ -254,7 +354,8 @@ theorem find?_replicate : find? p (replicate n a) = if n = 0 then none else if p
@[simp] theorem find?_replicate_of_neg (h : ¬ p a) : find? p (replicate n a) = none := by
simp [find?_replicate, h]
@[simp] theorem find?_replicate_eq_none (n : Nat) (a : α) (p : α Bool) :
-- This isn't a `@[simp]` lemma since there is already a lemma for `l.find? p = none` for any `l`.
theorem find?_replicate_eq_none (n : Nat) (a : α) (p : α Bool) :
(replicate n a).find? p = none n = 0 !p a := by
simp [Classical.or_iff_not_imp_left]
@@ -297,6 +398,12 @@ theorem IsInfix.find?_eq_none {l₁ l₂ : List α} {p : α → Bool} (h : l₁
List.find? p l₂ = none List.find? p l₁ = none :=
h.sublist.find?_eq_none
theorem find?_pmap {P : α Prop} (f : (a : α) P a β) (xs : List α)
(H : (a : α), a xs P a) (p : β Bool) :
(xs.pmap f H).find? p = (xs.attach.find? (fun a, m => p (f a (H a m)))).map fun a, m => f a (H a m) := by
simp only [pmap_eq_map_attach, find?_map]
rfl
/-! ### findIdx -/
theorem findIdx_cons (p : α Bool) (b : α) (l : List α) :
@@ -632,7 +739,7 @@ theorem findIdx?_eq_enum_findSome? {xs : List α} {p : α → Bool} :
simp only [findIdx?_cons, Nat.zero_add, findIdx?_succ, enum]
split
· simp_all
· simp_all only [enumFrom_cons, ite_false, Option.isNone_none, findSome?_cons_of_isNone]
· simp_all only [enumFrom_cons, ite_false, Option.isNone_none, findSome?_cons_of_isNone, reduceCtorEq]
simp [Function.comp_def, map_fst_add_enum_eq_enumFrom, findSome?_map]
theorem Sublist.findIdx?_isSome {l₁ l₂ : List α} (h : l₁ <+ l₂) :

View File

@@ -79,6 +79,11 @@ open Nat
/-! ## Preliminaries -/
/-! ### nil -/
@[simp] theorem nil_eq {α} (xs : List α) : [] = xs xs = [] := by
cases xs <;> simp
/-! ### cons -/
theorem cons_ne_nil (a : α) (l : List α) : a :: l [] := nofun
@@ -86,6 +91,10 @@ theorem cons_ne_nil (a : α) (l : List α) : a :: l ≠ [] := nofun
@[simp]
theorem cons_ne_self (a : α) (l : List α) : a :: l l := mt (congrArg length) (Nat.succ_ne_self _)
@[simp] theorem ne_cons_self {a : α} {l : List α} : l a :: l := by
rw [ne_eq, eq_comm]
simp
theorem head_eq_of_cons_eq (H : h₁ :: t₁ = h₂ :: t₂) : h₁ = h₂ := (cons.inj H).1
theorem tail_eq_of_cons_eq (H : h₁ :: t₁ = h₂ :: t₂) : t₁ = t₂ := (cons.inj H).2
@@ -260,6 +269,14 @@ theorem getElem?_eq (l : List α) (i : Nat) :
l[i]? = if h : i < l.length then some l[i] else none := by
split <;> simp_all
@[simp] theorem some_getElem_eq_getElem? {α} (xs : List α) (i : Nat) (h : i < xs.length) :
(some xs[i] = xs[i]?) True := by
simp [h]
@[simp] theorem getElem?_eq_some_getElem {α} (xs : List α) (i : Nat) (h : i < xs.length) :
(xs[i]? = some xs[i]) True := by
simp [h]
theorem getElem_eq_iff {l : List α} {n : Nat} {h : n < l.length} : l[n] = x l[n]? = some x := by
simp only [getElem?_eq_some]
exact fun w => h, w, fun h => h.2
@@ -347,6 +364,11 @@ theorem get?_concat_length (l : List α) (a : α) : (l ++ [a]).get? l.length = s
theorem mem_cons_self (a : α) (l : List α) : a a :: l := .head ..
theorem mem_concat_self (xs : List α) (a : α) : a xs ++ [a] :=
mem_append_of_mem_right xs (mem_cons_self a _)
theorem mem_append_cons_self : a xs ++ a :: ys := mem_append_of_mem_right _ (mem_cons_self _ _)
theorem mem_cons_of_mem (y : α) {a : α} {l : List α} : a l a y :: l := .tail _
theorem exists_mem_of_ne_nil (l : List α) (h : l []) : x, x l :=
@@ -366,27 +388,21 @@ theorem forall_mem_cons {p : α → Prop} {a : α} {l : List α} :
fun H => H _ (.head ..), fun _ h => H _ (.tail _ h),
fun H₁, H₂ _ => fun | .head .. => H₁ | .tail _ h => H₂ _ h
@[simp]
theorem forall_mem_ne {a : α} {l : List α} : ( a' : α, a' l ¬a = a') a l :=
fun h m => h _ m rfl, fun h _ m e => h (e.symm m)
@[simp]
theorem forall_mem_ne' {a : α} {l : List α} : ( a' : α, a' l ¬a' = a) a l :=
fun h m => h _ m rfl, fun h _ m e => h (e.symm m)
@[simp]
theorem any_beq [BEq α] [LawfulBEq α] {l : List α} : (l.any fun x => a == x) a l := by
induction l <;> simp_all
@[simp]
theorem any_beq' [BEq α] [LawfulBEq α] {l : List α} : (l.any fun x => x == a) a l := by
induction l <;> simp_all [eq_comm (a := a)]
@[simp]
theorem all_bne [BEq α] [LawfulBEq α] {l : List α} : (l.all fun x => a != x) a l := by
induction l <;> simp_all
@[simp]
theorem all_bne' [BEq α] [LawfulBEq α] {l : List α} : (l.all fun x => x != a) a l := by
induction l <;> simp_all [eq_comm (a := a)]
@@ -510,7 +526,7 @@ theorem isEmpty_iff_length_eq_zero {l : List α} : l.isEmpty ↔ l.length = 0 :=
@[simp] theorem isEmpty_eq_true {l : List α} : l.isEmpty l = [] := by
cases l <;> simp
@[simp] theorem isEmpty_eq_false {l : List α} : ¬ l.isEmpty l [] := by
@[simp] theorem isEmpty_eq_false {l : List α} : l.isEmpty = false l [] := by
cases l <;> simp
/-! ### any / all -/
@@ -549,8 +565,7 @@ theorem get_set_eq {l : List α} {i : Nat} {a : α} (h : i < (l.set i a).length)
(l.set i a)[i]? = some a := by
simp_all [getElem?_eq_some]
@[simp]
theorem getElem?_set_eq' {l : List α} {i : Nat} {a : α} : (set l i a)[i]? = (fun _ => a) <$> l[i]? := by
theorem getElem?_set_eq' {l : List α} {i : Nat} {a : α} : (set l i a)[i]? = Function.const _ a <$> l[i]? := by
by_cases h : i < l.length
· simp [getElem?_set_eq h, getElem?_eq_getElem h]
· simp only [Nat.not_lt] at h
@@ -607,7 +622,7 @@ theorem getElem?_set {l : List α} {i j : Nat} {a : α} :
theorem getElem?_set' {l : List α} {i j : Nat} {a : α} :
(set l i a)[j]? = if i = j then (fun _ => a) <$> l[j]? else l[j]? := by
by_cases i = j
· simp only [getElem?_set_eq', Option.map_eq_map, reduceIte, *]
· simp only [getElem?_set_eq', Option.map_eq_map, reduceIte, *]; rfl
· simp only [ne_eq, not_false_eq_true, getElem?_set_ne, reduceIte, *]
theorem set_eq_of_length_le {l : List α} {n : Nat} (h : l.length n) {a : α} :
@@ -622,7 +637,7 @@ theorem set_eq_of_length_le {l : List α} {n : Nat} (h : l.length ≤ n) {a : α
exact Nat.succ_le_succ_iff.mp h
@[simp] theorem set_eq_nil (l : List α) (n : Nat) (a : α) : l.set n a = [] l = [] := by
cases l <;> cases n <;> simp only [set]
cases l <;> cases n <;> simp [set]
theorem set_comm (a b : α) : {n m : Nat} (l : List α), n m
(l.set n a).set m b = (l.set m b).set n a
@@ -884,10 +899,10 @@ theorem getLast?_eq_getElem? : ∀ (l : List α), getLast? l = l[l.length - 1]?
theorem getLast?_eq_get? (l : List α) : getLast? l = l.get? (l.length - 1) := by
simp [getLast?_eq_getElem?]
@[simp] theorem getLast?_concat (l : List α) : getLast? (l ++ [a]) = some a := by
theorem getLast?_concat (l : List α) : getLast? (l ++ [a]) = some a := by
simp [getLast?_eq_getElem?, Nat.succ_sub_succ]
@[simp] theorem getLastD_concat (a b l) : @getLastD α (l ++ [b]) a = b := by
theorem getLastD_concat (a b l) : @getLastD α (l ++ [b]) a = b := by
rw [getLastD_eq_getLast?, getLast?_concat]; rfl
/-! ## Head and tail -/
@@ -900,6 +915,11 @@ theorem head!_of_head? [Inhabited α] : ∀ {l : List α}, head? l = some a →
theorem head?_eq_head : {l} h, @head? α l = some (head l h)
| _::_, _ => rfl
theorem head_eq_iff_head?_eq_some {xs : List α} (h) : xs.head h = a xs.head? = some a := by
cases xs with
| nil => simp at h
| cons x xs => simp
theorem head?_eq_getElem? : l : List α, head? l = l[0]?
| [] => rfl
| a::l => by simp
@@ -907,6 +927,9 @@ theorem head?_eq_getElem? : ∀ l : List α, head? l = l[0]?
@[simp] theorem head?_eq_none_iff : l.head? = none l = [] := by
cases l <;> simp
theorem head?_eq_some_iff {xs : List α} {a : α} : xs.head? = some a ys, xs = a :: ys := by
cases xs <;> simp_all
@[simp] theorem head_mem : {l : List α} (h : l []), head l h l
| [], h => absurd rfl h
| _::_, _ => .head ..
@@ -1442,10 +1465,22 @@ theorem append_right_inj {t₁ t₂ : List α} (s) : s ++ t₁ = s ++ t₂ ↔ t
theorem append_left_inj {s₁ s₂ : List α} (t) : s₁ ++ t = s₂ ++ t s₁ = s₂ :=
fun h => append_inj_left' h rfl, congrArg (· ++ _)
@[simp] theorem append_left_eq_self {x y : List α} : x ++ y = y x = [] := by
rw [ append_left_inj (s₁ := x), nil_append]
@[simp] theorem self_eq_append_left {x y : List α} : y = x ++ y x = [] := by
rw [eq_comm, append_left_eq_self]
@[simp] theorem append_right_eq_self {x y : List α} : x ++ y = x y = [] := by
rw [ append_right_inj (t₁ := y), append_nil]
@[simp] theorem self_eq_append_right {x y : List α} : x = x ++ y y = [] := by
rw [eq_comm, append_right_eq_self]
@[simp] theorem append_eq_nil : p ++ q = [] p = [] q = [] := by
cases p <;> simp
@[simp] theorem getLast_concat {a : α} : (l : List α), getLast (l ++ [a]) (by simp) = a
theorem getLast_concat {a : α} : (l : List α), getLast (l ++ [a]) (by simp) = a
| [] => rfl
| a::t => by
simp [getLast_cons _, getLast_concat t]
@@ -1487,9 +1522,9 @@ theorem getElem?_append {l₁ l₂ : List α} {n : Nat} :
· exact getElem?_append_left h
· exact getElem?_append_right (by simpa using h)
@[simp] theorem head_append_of_ne_nil {l : List α} (w : l []) :
head (l ++ l') (by simp_all) = head l w := by
match l, w with
@[simp] theorem head_append_of_ne_nil {l : List α} {w} (w₂) :
head (l ++ l') w₁ = head l w := by
match l, w with
| a :: l, _ => rfl
theorem head_append {l₁ l₂ : List α} (w : l₁ ++ l₂ []) :
@@ -1512,7 +1547,7 @@ theorem head_append {l₁ l₂ : List α} (w : l₁ ++ l₂ ≠ []) :
-- `getLast_append_of_ne_nil`, `getLast_append` and `getLast?_append`
-- are stated and proved later in the `reverse` section.
@[simp] theorem nil_eq_append : [] = a ++ b a = [] b = [] := by
theorem nil_eq_append : [] = a ++ b a = [] b = [] := by
rw [eq_comm, append_eq_nil]
theorem append_ne_nil_of_left_ne_nil {s : List α} (h : s []) (t : List α) : s ++ t [] := by simp_all
@@ -1523,6 +1558,14 @@ theorem append_ne_nil_of_ne_nil_left {s : List α} (h : s ≠ []) (t : List α)
@[deprecated append_ne_nil_of_right_ne_nil (since := "2024-07-24")]
theorem append_ne_nil_of_ne_nil_right (s : List α) : t [] s ++ t [] := by simp_all
theorem tail_append (xs ys : List α) :
(xs ++ ys).tail = if xs.isEmpty then ys.tail else xs.tail ++ ys := by
cases xs <;> simp
@[simp] theorem tail_append_of_ne_nil (xs ys : List α) (h : xs []) :
(xs ++ ys).tail = xs.tail ++ ys := by
simp_all [tail_append]
theorem append_eq_cons :
a ++ b = x :: c (a = [] b = x :: c) ( a', a = x :: a' c = a' ++ b) := by
cases a with simp | cons a as => ?_
@@ -1779,11 +1822,10 @@ theorem join_filter_ne_nil [DecidablePred fun l : List α => l ≠ []] {L : List
simp only [ne_eq, isEmpty_iff, Bool.not_eq_true, Bool.decide_eq_false,
join_filter_not_isEmpty]
@[simp] theorem join_map_filter (p : α Bool) (l : List (List α)) : (l.map (filter p)).join = (l.join).filter p := by
induction l with
| nil => simp
| cons x xs ih =>
simp only [ih, map_cons, join_cons, filter_append]
@[deprecated filter_join (since := "2024-08-26")]
theorem join_map_filter (p : α Bool) (l : List (List α)) :
(l.map (filter p)).join = (l.join).filter p := by
rw [filter_join]
@[simp] theorem join_append (L₁ L₂ : List (List α)) : join (L₁ ++ L₂) = join L₁ ++ join L₂ := by
induction L₁ <;> simp_all
@@ -1794,6 +1836,55 @@ theorem join_concat (L : List (List α)) (l : List α) : join (L ++ [l]) = join
theorem join_join {L : List (List (List α))} : join (join L) = join (map join L) := by
induction L <;> simp_all
theorem join_eq_cons (xs : List (List α)) (y : α) (ys : List α) :
xs.join = y :: ys
as bs cs, xs = as ++ (y :: bs) :: cs ( l, l as l = []) ys = bs ++ cs.join := by
constructor
· induction xs with
| nil => simp
| cons x xs ih =>
intro h
simp only [join_cons] at h
replace h := h.symm
rw [cons_eq_append] at h
obtain (rfl, h | z) := h
· obtain as, bs, cs, rfl, _, rfl := ih h
refine [] :: as, bs, cs, ?_
simpa
· obtain a', rfl, rfl := z
refine [], a', xs, ?_
simp
· rintro as, bs, cs, rfl, h₁, rfl
simp [join_eq_nil.mpr h₁]
theorem join_eq_append (xs : List (List α)) (ys zs : List α) :
xs.join = ys ++ zs
( as bs, xs = as ++ bs ys = as.join zs = bs.join)
as bs c cs ds, xs = as ++ (bs ++ c :: cs) :: ds ys = as.join ++ bs
zs = c :: cs ++ ds.join := by
constructor
· induction xs generalizing ys with
| nil =>
simp only [join_nil, nil_eq, append_eq_nil, and_false, cons_append, false_and, exists_const,
exists_false, or_false, and_imp, List.cons_ne_nil]
rintro rfl rfl
exact [], [], by simp
| cons x xs ih =>
intro h
simp only [join_cons] at h
rw [append_eq_append_iff] at h
obtain (ys, rfl, h | c', rfl, h) := h
· obtain (as, bs, rfl, rfl, rfl | as, bs, c, cs, ds, rfl, rfl, rfl) := ih _ h
· exact .inl x :: as, bs, by simp
· exact .inr x :: as, bs, c, cs, ds, by simp
· simp only [h]
cases c' with
| nil => exact .inl [ys], xs, by simp
| cons x c' => exact .inr [], ys, x, c', xs, by simp
· rintro (as, bs, rfl, rfl, rfl | as, bs, c, cs, ds, rfl, rfl, rfl)
· simp
· simp
/-- Two lists of sublists are equal iff their joins coincide, as well as the lengths of the
sublists. -/
theorem eq_iff_join_eq : (L L' : List (List α)),
@@ -2069,18 +2160,19 @@ theorem bind_replicate {β} (f : α → List β) : (replicate n a).bind f = (rep
| nil => rfl
| cons a as ih => simp [ih]
@[simp] theorem mem_reverseAux {x : α} : {as bs}, x reverseAux as bs x as x bs
theorem mem_reverseAux {x : α} : {as bs}, x reverseAux as bs x as x bs
| [], _ => .inr, fun | .inr h => h
| a :: _, _ => by rw [reverseAux, mem_cons, or_assoc, or_left_comm, mem_reverseAux, mem_cons]
@[simp] theorem mem_reverse {x : α} {as : List α} : x reverse as x as := by simp [reverse]
@[simp] theorem mem_reverse {x : α} {as : List α} : x reverse as x as := by
simp [reverse, mem_reverseAux]
@[simp] theorem reverse_eq_nil_iff {xs : List α} : xs.reverse = [] xs = [] := by
match xs with
| [] => simp
| x :: xs => simp
@[simp] theorem reverse_ne_nil_iff {xs : List α} : xs.reverse [] xs [] :=
theorem reverse_ne_nil_iff {xs : List α} : xs.reverse [] xs [] :=
not_congr reverse_eq_nil_iff
theorem getElem?_reverse' : {l : List α} (i j), i + j + 1 = length l
@@ -2126,7 +2218,15 @@ theorem reverseAux_reverseAux_nil (as bs : List α) : reverseAux (reverseAux as
theorem reverse_eq_iff {as bs : List α} : as.reverse = bs as = bs.reverse := by
constructor <;> (rintro rfl; simp)
@[simp] theorem getLast?_reverse (l : List α) : l.reverse.getLast? = l.head? := by cases l <;> simp
@[simp] theorem reverse_inj {xs ys : List α} : xs.reverse = ys.reverse xs = ys := by
simp [reverse_eq_iff]
@[simp] theorem reverse_eq_cons {xs : List α} {a : α} {ys : List α} :
xs.reverse = a :: ys xs = ys.reverse ++ [a] := by
rw [reverse_eq_iff, reverse_cons]
@[simp] theorem getLast?_reverse (l : List α) : l.reverse.getLast? = l.head? := by
cases l <;> simp [getLast?_concat]
@[simp] theorem head?_reverse (l : List α) : l.reverse.head? = l.getLast? := by
rw [ getLast?_reverse, reverse_reverse]
@@ -2161,8 +2261,16 @@ theorem reverse_map (f : α → β) (l : List α) : (l.map f).reverse = l.revers
@[simp] theorem reverse_append (as bs : List α) : (as ++ bs).reverse = bs.reverse ++ as.reverse := by
induction as <;> simp_all
theorem reverse_concat (l : List α) (a : α) : (l.concat a).reverse = a :: l.reverse := by
rw [concat_eq_append, reverse_append]; rfl
@[simp] theorem reverse_eq_append {xs ys zs : List α} :
xs.reverse = ys ++ zs xs = zs.reverse ++ ys.reverse := by
rw [reverse_eq_iff, reverse_append]
theorem reverse_concat (l : List α) (a : α) : (l ++ [a]).reverse = a :: l.reverse := by
rw [reverse_append]; rfl
theorem reverse_eq_concat {xs ys : List α} {a : α} :
xs.reverse = ys ++ [a] xs = a :: ys.reverse := by
rw [reverse_eq_iff, reverse_concat]
/-- Reversing a join is the same as reversing the order of parts and reversing all parts. -/
theorem reverse_join (L : List (List α)) :
@@ -2206,16 +2314,32 @@ theorem bind_reverse {β} (l : List α) (f : α → List β) : (l.reverse.bind f
induction l with
| nil => contradiction
| cons a l ih =>
simp
simp only [reverse_cons]
by_cases h' : l = []
· simp_all
· rw [getLast_cons, head_append_of_ne_nil, ih]
simp_all
· simp only [head_eq_iff_head?_eq_some, head?_reverse] at ih
simp [ih, h, h', getLast_cons, head_eq_iff_head?_eq_some]
theorem getLast_eq_head_reverse {l : List α} (h : l []) :
l.getLast h = l.reverse.head (by simp_all) := by
rw [ head_reverse]
theorem getLast_eq_iff_getLast_eq_some {xs : List α} (h) : xs.getLast h = a xs.getLast? = some a := by
rw [getLast_eq_head_reverse, head_eq_iff_head?_eq_some]
simp
@[simp] theorem getLast?_eq_none_iff {xs : List α} : xs.getLast? = none xs = [] := by
rw [getLast?_eq_head?_reverse, head?_eq_none_iff, reverse_eq_nil_iff]
theorem getLast?_eq_some_iff {xs : List α} {a : α} : xs.getLast? = some a ys, xs = ys ++ [a] := by
rw [getLast?_eq_head?_reverse, head?_eq_some_iff]
simp only [reverse_eq_cons]
exact fun ys, h => ys.reverse, by simpa using h, fun ys, h => ys.reverse, by simpa using h
theorem mem_of_getLast?_eq_some {xs : List α} {a : α} (h : xs.getLast? = some a) : a xs := by
obtain ys, rfl := getLast?_eq_some_iff.1 h
exact mem_concat_self ys a
@[simp] theorem getLast_reverse {l : List α} (h : l.reverse []) :
l.reverse.getLast h = l.head (by simp_all) := by
simp [getLast_eq_head_reverse]
@@ -2224,8 +2348,8 @@ theorem head_eq_getLast_reverse {l : List α} (h : l ≠ []) :
l.head h = l.reverse.getLast (by simp_all) := by
rw [ getLast_reverse]
@[simp] theorem getLast_append_of_ne_nil {l : List α} (h : l' []) :
(l ++ l').getLast (append_ne_nil_of_right_ne_nil l h) = l'.getLast (by simp_all) := by
@[simp] theorem getLast_append_of_ne_nil {l : List α} {h₁} (h : l' []) :
(l ++ l').getLast h₁ = l'.getLast h₂ := by
simp only [getLast_eq_head_reverse, reverse_append]
rw [head_append_of_ne_nil]
@@ -2397,8 +2521,8 @@ theorem dropLast_append {l₁ l₂ : List α} :
(l₁ ++ l₂).dropLast = if l₂.isEmpty then l₁.dropLast else l₁ ++ l₂.dropLast := by
split <;> simp_all
@[simp] theorem dropLast_append_cons : dropLast (l₁ ++ b::l₂) = l₁ ++ dropLast (b::l₂) := by
simp only [ne_eq, not_false_eq_true, dropLast_append_of_ne_nil]
theorem dropLast_append_cons : dropLast (l₁ ++ b::l₂) = l₁ ++ dropLast (b::l₂) := by
simp
@[simp 1100] theorem dropLast_concat : dropLast (l₁ ++ [b]) = l₁ := by simp

View File

@@ -7,6 +7,7 @@ prelude
import Init.Data.List.Nat.TakeDrop
import Init.Data.List.Range
import Init.Data.List.Pairwise
import Init.Data.List.Find
/-!
# Lemmas about `List.range` and `List.enum`
@@ -38,6 +39,19 @@ theorem range'_ne_nil (s n : Nat) : range' s n ≠ [] ↔ n ≠ 0 := by
@[simp] theorem range'_one {s step : Nat} : range' s 1 step = [s] := rfl
@[simp] theorem range'_inj : range' s n = range' s' n' n = n' (n = 0 s = s') := by
constructor
· intro h
have h' := congrArg List.length h
simp at h'
subst h'
cases n with
| zero => simp
| succ n =>
simp only [range'_succ] at h
simp_all
· rintro rfl, rfl | rfl <;> simp
theorem mem_range' : {n}, m range' s n step i < n, m = s + step * i
| 0 => by simp [range', Nat.not_lt_zero]
| n + 1 => by
@@ -54,6 +68,9 @@ theorem mem_range' : ∀{n}, m ∈ range' s n step ↔ ∃ i < n, m = s + step *
theorem head?_range' (n : Nat) : (range' s n).head? = if n = 0 then none else some s := by
induction n <;> simp_all [range'_succ, head?_append]
@[simp] theorem head_range' (n : Nat) (h) : (range' s n).head h = s := by
repeat simp_all [head?_range', head_eq_iff_head?_eq_some]
theorem getLast?_range' (n : Nat) : (range' s n).getLast? = if n = 0 then none else some (s + n - 1) := by
induction n generalizing s with
| zero => simp
@@ -66,6 +83,11 @@ theorem getLast?_range' (n : Nat) : (range' s n).getLast? = if n = 0 then none e
simp
omega
@[simp] theorem getLast_range' (n : Nat) (h) : (range' s n).getLast h = s + n - 1 := by
cases n with
| zero => simp at h
| succ n => simp [getLast?_range', getLast_eq_iff_getLast_eq_some]
theorem pairwise_lt_range' s n (step := 1) (pos : 0 < step := by simp) :
Pairwise (· < ·) (range' s n step) :=
match s, n, step, pos with
@@ -145,6 +167,67 @@ theorem range'_concat (s n : Nat) : range' s (n + 1) step = range' s n step ++ [
theorem range'_1_concat (s n : Nat) : range' s (n + 1) = range' s n ++ [s + n] := by
simp [range'_concat]
theorem range'_eq_cons_iff : range' s n = a :: xs s = a 0 < n xs = range' (a + 1) (n - 1) := by
induction n generalizing s with
| zero => simp
| succ n ih =>
simp only [range'_succ]
simp only [cons.injEq, and_congr_right_iff]
rintro rfl
simp [eq_comm]
@[simp] theorem range'_eq_singleton {s n a : Nat} : range' s n = [a] s = a n = 1 := by
rw [range'_eq_cons_iff]
simp only [nil_eq, range'_eq_nil, and_congr_right_iff]
rintro rfl
omega
theorem range'_eq_append_iff : range' s n = xs ++ ys k, k n xs = range' s k ys = range' (s + k) (n - k) := by
induction n generalizing s xs ys with
| zero => simp
| succ n ih =>
simp only [range'_succ]
rw [cons_eq_append]
constructor
· rintro (rfl, rfl | a, rfl, h)
· exact 0, by simp [range'_succ]
· simp only [ih] at h
obtain k, h, rfl, rfl := h
refine k + 1, ?_
simp_all [range'_succ]
omega
· rintro k, h, rfl, rfl
cases k with
| zero => simp [range'_succ]
| succ k =>
simp only [range'_succ, reduceCtorEq, false_and, cons.injEq, true_and, ih, range'_inj, exists_eq_left', or_true, and_true, false_or]
refine k, ?_
simp_all
omega
@[simp] theorem find?_range'_eq_some (s n : Nat) (i : Nat) (p : Nat Bool) :
(range' s n).find? p = some i p i i range' s n j, s j j < i !p j := by
rw [find?_eq_some]
simp only [Bool.not_eq_true', exists_and_right, mem_range'_1, and_congr_right_iff]
simp only [range'_eq_append_iff, eq_comm (a := i :: _), range'_eq_cons_iff]
intro h
constructor
· rintro as, x, k, h₁, rfl, rfl, h₂, rfl, h₃
constructor
· omega
· simpa using h₃
· rintro h₁, h₂, h₃
refine range' s (i - s), range' (i + 1) (n - (i - s) - 1), i - s, ?_ , ?_
· simp; omega
· simp only [mem_range'_1, and_imp]
intro a a₁ a₂
exact h₃ a a₁ (by omega)
@[simp] theorem find?_range'_eq_none (s n : Nat) (p : Nat Bool) :
(range' s n).find? p = none i, s i i < s + n !p i := by
rw [find?_eq_none]
simp
/-! ### range -/
theorem range_loop_range' : s n : Nat, range.loop s (range' s n) = range' 0 (n + s)
@@ -219,6 +302,23 @@ theorem head?_range (n : Nat) : (range n).head? = if n = 0 then none else some 0
simp only [range_succ, head?_append, ih]
split <;> simp_all
@[simp] theorem head_range (n : Nat) (h) : (range n).head h = 0 := by
cases n with
| zero => simp at h
| succ n => simp [head?_range, head_eq_iff_head?_eq_some]
theorem getLast?_range (n : Nat) : (range n).getLast? = if n = 0 then none else some (n - 1) := by
induction n with
| zero => simp
| succ n ih =>
simp only [range_succ, getLast?_append, ih]
split <;> simp_all
@[simp] theorem getLast_range (n : Nat) (h) : (range n).getLast h = n - 1 := by
cases n with
| zero => simp at h
| succ n => simp [getLast?_range, getLast_eq_iff_getLast_eq_some]
theorem take_range (m n : Nat) : take m (range n) = range (min m n) := by
apply List.ext_getElem
· simp
@@ -227,6 +327,14 @@ theorem take_range (m n : Nat) : take m (range n) = range (min m n) := by
theorem nodup_range (n : Nat) : Nodup (range n) := by
simp (config := {decide := true}) only [range_eq_range', nodup_range']
@[simp] theorem find?_range_eq_some (n : Nat) (i : Nat) (p : Nat Bool) :
(range n).find? p = some i p i i range n j, j < i !p j := by
simp [range_eq_range']
@[simp] theorem find?_range_eq_none (n : Nat) (p : Nat Bool) :
(range n).find? p = none i, i < n !p i := by
simp [range_eq_range']
/-! ### iota -/
theorem iota_eq_reverse_range' : n : Nat, iota n = reverse (range' 1 n)
@@ -242,8 +350,42 @@ theorem iota_ne_nil (n : Nat) : iota n ≠ [] ↔ n ≠ 0 := by
cases n <;> simp
@[simp]
theorem mem_iota {m n : Nat} : m iota n 1 m m n := by
theorem mem_iota {m n : Nat} : m iota n 0 < m m n := by
simp [iota_eq_reverse_range', Nat.add_comm, Nat.lt_succ]
omega
@[simp] theorem iota_inj : iota n = iota n' n = n' := by
constructor
· intro h
have h' := congrArg List.length h
simp at h'
exact h'
· rintro rfl
simp
theorem iota_eq_cons_iff : iota n = a :: xs n = a 0 < n xs = iota (n - 1) := by
simp [iota_eq_reverse_range']
simp [range'_eq_append_iff, reverse_eq_iff]
constructor
· rintro k, h, rfl, h'
rw [eq_comm, range'_eq_singleton] at h'
simp only [reverse_inj, range'_inj, or_true, and_true]
omega
· rintro rfl, h, rfl
refine n - 1, by simp, rfl, ?_
rw [eq_comm, range'_eq_singleton]
omega
theorem iota_eq_append_iff : iota n = xs ++ ys k, k n xs = (range' (k + 1) (n - k)).reverse ys = iota k := by
simp only [iota_eq_reverse_range']
rw [reverse_eq_append]
rw [range'_eq_append_iff]
simp only [reverse_eq_iff]
constructor
· rintro k, h, rfl, rfl
simp; omega
· rintro k, h, rfl, rfl
exact k, by simp; omega
theorem pairwise_gt_iota (n : Nat) : Pairwise (· > ·) (iota n) := by
simpa only [iota_eq_reverse_range', pairwise_reverse] using pairwise_lt_range' 1 n
@@ -251,7 +393,6 @@ theorem pairwise_gt_iota (n : Nat) : Pairwise (· > ·) (iota n) := by
theorem nodup_iota (n : Nat) : Nodup (iota n) :=
(pairwise_gt_iota n).imp Nat.ne_of_gt
@[simp] theorem head?_iota (n : Nat) : (iota n).head? = if n = 0 then none else some n := by
cases n <;> simp
@@ -270,12 +411,67 @@ theorem nodup_iota (n : Nat) : Nodup (iota n) :=
rw [getLast?_eq_head?_reverse]
simp [head?_range']
@[simp] theorem getLast_iota (n : Nat) (h) : (iota n).getLast h = 1 := by
rw [getLast_eq_head_reverse]
simp
@[simp] theorem find?_iota_eq_none (n : Nat) (p : Nat Bool) :
(iota n).find? p = none i, 0 < i i n !p i := by
rw [find?_eq_none]
simp
@[simp] theorem find?_iota_eq_some (n : Nat) (i : Nat) (p : Nat Bool) :
(iota n).find? p = some i p i i iota n j, i < j j n !p j := by
rw [find?_eq_some]
simp only [iota_eq_reverse_range', reverse_eq_append, reverse_cons, append_assoc,
singleton_append, Bool.not_eq_true', exists_and_right, mem_reverse, mem_range'_1,
and_congr_right_iff]
intro h
constructor
· rintro as, xs, h, h'
constructor
· replace h : i range' 1 n := by
rw [h]
exact mem_append_cons_self
simpa using h
· rw [range'_eq_append_iff] at h
simp [reverse_eq_iff] at h
obtain k, h₁, rfl, h₂ := h
rw [eq_comm, range'_eq_cons_iff, reverse_eq_iff] at h₂
obtain rfl, -, rfl := h₂
intro j j₁ j₂
apply h'
simp; omega
· rintro i₁, i₂, h
refine (range' (i+1) (n-i)).reverse, (range' 1 (i-1)).reverse, ?_, ?_
· simp [ range'_succ]
rw [range'_eq_append_iff]
refine i-1, ?_
constructor
· omega
· simp
omega
· simp
intros a a₁ a₂
apply h
· omega
· omega
/-! ### enumFrom -/
@[simp]
theorem enumFrom_singleton (x : α) (n : Nat) : enumFrom n [x] = [(n, x)] :=
rfl
@[simp] theorem head?_enumFrom (n : Nat) (l : List α) :
(enumFrom n l).head? = l.head?.map fun a => (n, a) := by
simp [head?_eq_getElem?]
@[simp] theorem getLast?_enumFrom (n : Nat) (l : List α) :
(enumFrom n l).getLast? = l.getLast?.map fun a => (n + l.length - 1, a) := by
simp [getLast?_eq_getElem?]
cases l <;> simp; omega
theorem mk_add_mem_enumFrom_iff_getElem? {n i : Nat} {x : α} {l : List α} :
(n + i, x) enumFrom n l l[i]? = some x := by
simp [mem_iff_get?]
@@ -388,6 +584,14 @@ theorem getElem_enum (l : List α) (i : Nat) (h : i < l.enum.length) :
l.enum[i] = (i, l[i]'(by simpa [enum_length] using h)) := by
simp [enum]
@[simp] theorem head?_enum (l : List α) :
l.enum.head? = l.head?.map fun a => (0, a) := by
simp [head?_eq_getElem?]
@[simp] theorem getLast?_enum (l : List α) :
l.enum.getLast? = l.getLast?.map fun a => (l.length - 1, a) := by
simp [getLast?_eq_getElem?]
theorem mk_mem_enum_iff_getElem? {i : Nat} {x : α} {l : List α} : (i, x) enum l l[i]? = x := by
simp [enum, mk_mem_enumFrom_iff_le_and_getElem?_sub]

View File

@@ -126,4 +126,49 @@ theorem prefix_take_le_iff {L : List α} (hm : m < L.length) :
simp only [length_cons, Nat.succ_eq_add_one, Nat.add_lt_add_iff_right] at hm
simp [ @IH n ls hm, Nat.min_eq_left, Nat.le_of_lt hm]
@[simp] theorem append_left_sublist_self (xs ys : List α) : xs ++ ys <+ ys xs = [] := by
constructor
· intro h
replace h := h.length_le
simp only [length_append] at h
have : xs.length = 0 := by omega
simp_all
· rintro rfl
simp
@[simp] theorem append_right_sublist_self (xs ys : List α) : xs ++ ys <+ xs ys = [] := by
constructor
· intro h
replace h := h.length_le
simp only [length_append] at h
have : ys.length = 0 := by omega
simp_all
· rintro rfl
simp
theorem append_sublist_of_sublist_left (xs ys zs : List α) (h : zs <+ xs) :
xs ++ ys <+ zs ys = [] xs = zs := by
constructor
· intro h'
have hl := h.length_le
have hl' := h'.length_le
simp only [length_append] at hl'
have : ys.length = 0 := by omega
simp_all only [Nat.add_zero, length_eq_zero, true_and, append_nil]
exact Sublist.eq_of_length_le h' hl
· rintro rfl, rfl
simp
theorem append_sublist_of_sublist_right (xs ys zs : List α) (h : zs <+ ys) :
xs ++ ys <+ zs xs = [] ys = zs := by
constructor
· intro h'
have hl := h.length_le
have hl' := h'.length_le
simp only [length_append] at hl'
have : xs.length = 0 := by omega
simp_all only [Nat.zero_add, length_eq_zero, true_and, append_nil]
exact Sublist.eq_of_length_le h' hl
· rintro rfl, rfl
simp
end List

View File

@@ -275,7 +275,7 @@ theorem head?_drop (l : List α) (n : Nat) :
theorem head_drop {l : List α} {n : Nat} (h : l.drop n []) :
(l.drop n).head h = l[n]'(by simp_all) := by
have w : n < l.length := length_lt_of_drop_ne_nil h
simpa [head?_eq_head, getElem?_eq_getElem, h, w] using head?_drop l n
simpa [getElem?_eq_getElem, h, w, head_eq_iff_head?_eq_some] using head?_drop l n
theorem getLast?_drop {l : List α} : (l.drop n).getLast? = if l.length n then none else l.getLast? := by
rw [getLast?_eq_getElem?, getElem?_drop]

View File

@@ -123,7 +123,7 @@ theorem pairwise_filterMap (f : β → Option α) {l : List β} :
match e : f a with
| none =>
rw [filterMap_cons_none e, pairwise_cons]
simp only [e, false_implies, implies_true, true_and, IH]
simp only [e, false_implies, implies_true, true_and, IH, reduceCtorEq]
| some b =>
rw [filterMap_cons_some e]
simpa [IH, e] using fun _ =>

View File

@@ -123,10 +123,8 @@ theorem Perm.nil_eq {l : List α} (p : [] ~ l) : [] = l := p.symm.eq_nil.symm
@[simp] theorem nil_perm {l₁ : List α} : [] ~ l₁ l₁ = [] := perm_comm.trans perm_nil
@[simp]
theorem not_perm_nil_cons (x : α) (l : List α) : ¬[] ~ x :: l := (nomatch ·.symm.eq_nil)
@[simp]
theorem not_perm_cons_nil {l : List α} {a : α} : ¬(Perm (a::l) []) :=
fun h => by simpa using h.length_eq

View File

@@ -136,7 +136,7 @@ theorem merge_stable : ∀ (xs ys) (_ : ∀ x y, x ∈ xs → y ∈ ys → x.1
simp only [map_cons, cons.injEq, true_and]
rw [merge_stable, map_cons]
exact fun x' y' mx my => h x' y' (mem_cons_of_mem (i, x) mx) my
· simp only [reduceIte, map_cons, cons.injEq, true_and]
· simp only [reduceIte, map_cons, cons.injEq, true_and, reduceCtorEq]
rw [merge_stable, map_cons]
exact fun x' y' mx my => h x' y' mx (mem_cons_of_mem (j, y) my)

View File

@@ -62,8 +62,8 @@ theorem subset_def {l₁ l₂ : List α} : l₁ ⊆ l₂ ↔ ∀ {a : α}, a ∈
theorem Subset.trans {l₁ l₂ l₃ : List α} (h₁ : l₁ l₂) (h₂ : l₂ l₃) : l₁ l₃ :=
fun _ i => h₂ (h₁ i)
instance : Trans (Membership.mem : α List α Prop) Subset Membership.mem :=
fun h₁ h₂ => h h
instance : Trans (fun l₁ l₂ => Subset l₂ l₁) (Membership.mem : List α α Prop) Membership.mem :=
fun h₁ h₂ => h h
instance : Trans (Subset : List α List α Prop) Subset Subset :=
Subset.trans
@@ -185,14 +185,20 @@ theorem Sublist.subset : l₁ <+ l₂ → l₁ ⊆ l₂
protected theorem Sublist.mem (hx : a l₁) (hl : l₁ <+ l₂) : a l₂ :=
hl.subset hx
theorem Sublist.head_mem (s : ys <+ xs) (h) : ys.head h xs :=
s.mem (List.head_mem h)
theorem Sublist.getLast_mem (s : ys <+ xs) (h) : ys.getLast h xs :=
s.mem (List.getLast_mem h)
instance : Trans (@Sublist α) Subset Subset :=
fun h₁ h₂ => trans h₁.subset h₂
instance : Trans Subset (@Sublist α) Subset :=
fun h₁ h₂ => trans h₁ h₂.subset
instance : Trans (Membership.mem : α List α Prop) Sublist Membership.mem :=
fun h₁ h₂ => h.subset h
instance : Trans (fun l₁ l₂ => Sublist l₂ l₁) (Membership.mem : List α α Prop) Membership.mem :=
fun h₁ h₂ => h.subset h
theorem mem_of_cons_sublist {a : α} {l₁ l₂ : List α} (s : a :: l₁ <+ l₂) : a l₂ :=
(cons_subset.1 s.subset).1
@@ -246,6 +252,12 @@ protected theorem Sublist.filterMap (f : α → Option β) (s : l₁ <+ l₂) :
protected theorem Sublist.filter (p : α Bool) {l₁ l₂} (s : l₁ <+ l₂) : filter p l₁ <+ filter p l₂ := by
rw [ filterMap_eq_filter]; apply s.filterMap
theorem head_filter_mem (xs : List α) (p : α Bool) (h) : (xs.filter p).head h xs :=
(filter_sublist xs).head_mem h
theorem getLast_filter_mem (xs : List α) (p : α Bool) (h) : (xs.filter p).getLast h xs :=
(filter_sublist xs).getLast_mem h
theorem sublist_filterMap_iff {l₁ : List β} {f : α Option β} :
l₁ <+ l₂.filterMap f l', l' <+ l₂ l₁ = l'.filterMap f := by
induction l₂ generalizing l₁ with
@@ -755,7 +767,7 @@ theorem prefix_cons_iff : l₁ <+: a :: l₂ ↔ l₁ = [] ∃ t, l₁ = a :
refine s, by simp [h']
@[simp] theorem cons_prefix_cons : a :: l₁ <+: b :: l₂ a = b l₁ <+: l₂ := by
simp only [prefix_cons_iff, cons.injEq, false_or]
simp only [prefix_cons_iff, cons.injEq, false_or, List.cons_ne_nil]
constructor
· rintro t, rfl, rfl, h
exact rfl, h
@@ -786,12 +798,12 @@ theorem infix_cons_iff : l₁ <:+: a :: l₂ ↔ l₁ <+: a :: l₂ l₁ <:+
theorem prefix_concat_iff {l₁ l₂ : List α} {a : α} :
l₁ <+: l₂ ++ [a] l₁ = l₂ ++ [a] l₁ <+: l₂ := by
simp only [ concat_eq_append, reverse_suffix, reverse_concat, suffix_cons_iff]
simp only [ reverse_suffix, reverse_concat, suffix_cons_iff]
simp only [concat_eq_append, reverse_concat, reverse_eq_iff, reverse_reverse]
theorem suffix_concat_iff {l₁ l₂ : List α} {a : α} :
l₁ <:+ l₂ ++ [a] l₁ = [] t, l₁ = t ++ [a] t <:+ l₂ := by
rw [ reverse_prefix, concat_eq_append, reverse_concat, prefix_cons_iff]
rw [ reverse_prefix, reverse_concat, prefix_cons_iff]
simp only [reverse_eq_nil_iff]
apply or_congr_right
constructor
@@ -802,7 +814,7 @@ theorem suffix_concat_iff {l₁ l₂ : List α} {a : α} :
theorem infix_concat_iff {l₁ l₂ : List α} {a : α} :
l₁ <:+: l₂ ++ [a] l₁ <:+ l₂ ++ [a] l₁ <:+: l₂ := by
rw [ reverse_infix, concat_eq_append, reverse_concat, infix_cons_iff, reverse_infix,
rw [ reverse_infix, reverse_concat, infix_cons_iff, reverse_infix,
reverse_prefix, reverse_concat]
theorem isPrefix_iff : l₁ <+: l₂ i (h : i < l₁.length), l₂[i]? = some l₁[i] := by
@@ -909,7 +921,6 @@ theorem infix_of_mem_join : ∀ {L : List (List α)}, l ∈ L → l <:+: join L
theorem prefix_append_right_inj (l) : l ++ l₁ <+: l ++ l₂ l₁ <+: l₂ :=
exists_congr fun r => by rw [append_assoc, append_right_inj]
@[simp]
theorem prefix_cons_inj (a) : a :: l₁ <+: a :: l₂ l₁ <+: l₂ :=
prefix_append_right_inj [a]

View File

@@ -95,9 +95,7 @@ theorem getElem?_take_of_lt {l : List α} {n m : Nat} (h : m < n) : (l.take n)[m
theorem get?_take {l : List α} {n m : Nat} (h : m < n) : (l.take n).get? m = l.get? m := by
simp [getElem?_take_of_lt, h]
@[simp]
theorem getElem?_take_of_succ {l : List α} {n : Nat} : (l.take (n + 1))[n]? = l[n]? :=
getElem?_take_of_lt (Nat.lt_succ_self n)
theorem getElem?_take_of_succ {l : List α} {n : Nat} : (l.take (n + 1))[n]? = l[n]? := by simp
@[simp] theorem drop_drop (n : Nat) : (m) (l : List α), drop n (drop m l) = drop (n + m) l
| m, [] => by simp

View File

@@ -158,7 +158,7 @@ theorem add_one (n : Nat) : n + 1 = succ n :=
rfl
@[simp] theorem add_one_ne_zero (n : Nat) : n + 1 0 := nofun
@[simp] theorem zero_ne_add_one (n : Nat) : 0 n + 1 := nofun
theorem zero_ne_add_one (n : Nat) : 0 n + 1 := by simp
protected theorem add_comm : (n m : Nat), n + m = m + n
| n, 0 => Eq.symm (Nat.zero_add n)
@@ -779,6 +779,11 @@ theorem pow_le_pow_of_le_right {n : Nat} (hx : n > 0) {i : Nat} : ∀ {j}, i ≤
theorem pos_pow_of_pos {n : Nat} (m : Nat) (h : 0 < n) : 0 < n^m :=
pow_le_pow_of_le_right h (Nat.zero_le _)
@[simp] theorem zero_pow_of_pos (n : Nat) (h : 0 < n) : 0 ^ n = 0 := by
cases n with
| zero => cases h
| succ n => simp [Nat.pow_succ]
/-! # min/max -/
/--
@@ -887,7 +892,7 @@ theorem sub_succ_lt_self (a i : Nat) (h : i < a) : a - (i + 1) < a - i := by
theorem sub_ne_zero_of_lt : {a b : Nat} a < b b - a 0
| 0, 0, h => absurd h (Nat.lt_irrefl 0)
| 0, succ b, _ => by simp only [Nat.sub_zero, ne_eq, not_false_eq_true]
| 0, succ b, _ => by simp only [Nat.sub_zero, ne_eq, not_false_eq_true, Nat.succ_ne_zero]
| succ a, 0, h => absurd h (Nat.not_lt_zero a.succ)
| succ a, succ b, h => by rw [Nat.succ_sub_succ]; exact sub_ne_zero_of_lt (Nat.lt_of_succ_lt_succ h)

View File

@@ -40,7 +40,7 @@ An induction principal that works on divison by two.
-/
noncomputable def div2Induction {motive : Nat Sort u}
(n : Nat) (ind : (n : Nat), (n > 0 motive (n/2)) motive n) : motive n := by
induction n using Nat.strongInductionOn with
induction n using Nat.strongRecOn with
| ind n hyp =>
apply ind
intro n_pos
@@ -86,6 +86,12 @@ noncomputable def div2Induction {motive : Nat → Sort u}
@[simp] theorem testBit_zero (x : Nat) : testBit x 0 = decide (x % 2 = 1) := by
cases mod_two_eq_zero_or_one x with | _ p => simp [testBit, p]
theorem mod_two_eq_one_iff_testBit_zero : (x % 2 = 1) x.testBit 0 = true := by
cases mod_two_eq_zero_or_one x <;> simp_all
theorem mod_two_eq_zero_iff_testBit_zero : (x % 2 = 0) x.testBit 0 = false := by
cases mod_two_eq_zero_or_one x <;> simp_all
theorem testBit_succ (x i : Nat) : testBit x (succ i) = testBit (x/2) i := by
unfold testBit
simp [shiftRight_succ_inside]
@@ -94,6 +100,9 @@ theorem testBit_succ (x i : Nat) : testBit x (succ i) = testBit (x/2) i := by
unfold testBit
simp [shiftRight_succ_inside]
theorem testBit_div_two (x i : Nat) : testBit (x / 2) i = testBit x (i + 1) := by
simp
theorem testBit_to_div_mod {x : Nat} : testBit x i = decide (x / 2^i % 2 = 1) := by
induction i generalizing x with
| zero =>
@@ -114,7 +123,7 @@ theorem ne_zero_implies_bit_true {x : Nat} (xnz : x ≠ 0) : ∃ i, testBit x i
match mod_two_eq_zero_or_one x with
| Or.inl mod2_eq =>
rw [div_add_mod x 2] at xnz
simp only [mod2_eq, ne_eq, Nat.mul_eq_zero, Nat.add_zero, false_or] at xnz
simp only [mod2_eq, ne_eq, Nat.mul_eq_zero, Nat.add_zero, false_or, reduceCtorEq] at xnz
have d, dif := hyp x_pos xnz
apply Exists.intro (d+1)
simp_all
@@ -200,7 +209,7 @@ theorem lt_pow_two_of_testBit (x : Nat) (p : ∀i, i ≥ n → testBit x i = fal
have x_ge_n := Nat.ge_of_not_lt not_lt
have i, i_ge_n, test_true := ge_two_pow_implies_high_bit_true x_ge_n
have test_false := p _ i_ge_n
simp only [test_true] at test_false
simp [test_true] at test_false
private theorem succ_mod_two : succ x % 2 = 1 - x % 2 := by
induction x with
@@ -249,7 +258,7 @@ theorem testBit_two_pow_add_gt {i j : Nat} (j_lt_i : j < i) (x : Nat) :
@[simp] theorem testBit_mod_two_pow (x j i : Nat) :
testBit (x % 2^j) i = (decide (i < j) && testBit x i) := by
induction x using Nat.strongInductionOn generalizing j i with
induction x using Nat.strongRecOn generalizing j i with
| ind x hyp =>
rw [mod_eq]
rcases Nat.lt_or_ge x (2^j) with x_lt_j | x_ge_j
@@ -315,12 +324,44 @@ theorem testBit_one_eq_true_iff_self_eq_zero {i : Nat} :
Nat.testBit 1 i = true i = 0 := by
cases i <;> simp
theorem testBit_two_pow {n m : Nat} : testBit (2 ^ n) m = decide (n = m) := by
rw [testBit, shiftRight_eq_div_pow]
by_cases h : n = m
· simp [h, Nat.div_self (Nat.pow_pos Nat.zero_lt_two)]
· simp only [h]
cases Nat.lt_or_lt_of_ne h
· rw [div_eq_of_lt (Nat.pow_lt_pow_of_lt (by omega) (by omega))]
simp
· rw [Nat.pow_div _ Nat.two_pos,
Nat.sub_add_cancel (succ_le_of_lt <| Nat.sub_pos_of_lt (by omega))]
simp [Nat.pow_succ, and_one_is_mod, mul_mod_left]
omega
@[simp]
theorem testBit_two_pow_self {n : Nat} : testBit (2 ^ n) n = true := by
simp [testBit_two_pow]
@[simp]
theorem testBit_two_pow_of_ne {n m : Nat} (hm : n m) : testBit (2 ^ n) m = false := by
simp [testBit_two_pow]
omega
@[simp] theorem two_pow_sub_one_mod_two : (2 ^ n - 1) % 2 = 1 % 2 ^ n := by
cases n with
| zero => simp
| succ n =>
rw [mod_eq_of_lt (a := 1) (Nat.one_lt_two_pow (by omega)), mod_two_eq_one_iff_testBit_zero, testBit_two_pow_sub_one ]
simp only [zero_lt_succ, decide_True]
@[simp] theorem mod_two_pos_mod_two_eq_one : x % 2 ^ j % 2 = 1 (0 < j) x % 2 = 1 := by
rw [mod_two_eq_one_iff_testBit_zero, testBit_mod_two_pow]
simp
/-! ### bitwise -/
theorem testBit_bitwise
(false_false_axiom : f false false = false) (x y i : Nat)
: (bitwise f x y).testBit i = f (x.testBit i) (y.testBit i) := by
induction i using Nat.strongInductionOn generalizing x y with
theorem testBit_bitwise (false_false_axiom : f false false = false) (x y i : Nat) :
(bitwise f x y).testBit i = f (x.testBit i) (y.testBit i) := by
induction i using Nat.strongRecOn generalizing x y with
| ind i hyp =>
unfold bitwise
if x_zero : x = 0 then
@@ -417,6 +458,11 @@ theorem and_pow_two_identity {x : Nat} (lt : x < 2^n) : x &&& 2^n-1 = x := by
rw [and_pow_two_is_mod]
apply Nat.mod_eq_of_lt lt
@[simp] theorem and_mod_two_eq_one : (a &&& b) % 2 = 1 a % 2 = 1 b % 2 = 1 := by
simp only [mod_two_eq_one_iff_testBit_zero]
rw [testBit_and]
simp
/-! ### lor -/
@[simp] theorem zero_or (x : Nat) : 0 ||| x = x := by
@@ -435,6 +481,11 @@ theorem and_pow_two_identity {x : Nat} (lt : x < 2^n) : x &&& 2^n-1 = x := by
theorem or_lt_two_pow {x y n : Nat} (left : x < 2^n) (right : y < 2^n) : x ||| y < 2^n :=
bitwise_lt_two_pow left right
@[simp] theorem or_mod_two_eq_one : (a ||| b) % 2 = 1 a % 2 = 1 b % 2 = 1 := by
simp only [mod_two_eq_one_iff_testBit_zero]
rw [testBit_or]
simp
/-! ### xor -/
@[simp] theorem testBit_xor (x y i : Nat) :
@@ -444,6 +495,19 @@ theorem or_lt_two_pow {x y n : Nat} (left : x < 2^n) (right : y < 2^n) : x ||| y
theorem xor_lt_two_pow {x y n : Nat} (left : x < 2^n) (right : y < 2^n) : x ^^^ y < 2^n :=
bitwise_lt_two_pow left right
theorem and_xor_distrib_right {a b c : Nat} : (a ^^^ b) &&& c = (a &&& c) ^^^ (b &&& c) := by
apply Nat.eq_of_testBit_eq
simp [Bool.and_xor_distrib_right]
theorem and_xor_distrib_left {a b c : Nat} : a &&& (b ^^^ c) = (a &&& b) ^^^ (a &&& c) := by
apply Nat.eq_of_testBit_eq
simp [Bool.and_xor_distrib_left]
@[simp] theorem xor_mod_two_eq_one : ((a ^^^ b) % 2 = 1) ¬ ((a % 2 = 1) (b % 2 = 1)) := by
simp only [mod_two_eq_one_iff_testBit_zero]
rw [testBit_xor]
simp
/-! ### Arithmetic -/
theorem testBit_mul_pow_two_add (a : Nat) {b i : Nat} (b_lt : b < 2^i) (j : Nat) :
@@ -505,6 +569,15 @@ theorem mul_add_lt_is_or {b : Nat} (b_lt : b < 2^i) (a : Nat) : 2^i * a + b = 2^
@[simp] theorem testBit_shiftRight (x : Nat) : testBit (x >>> i) j = testBit x (i+j) := by
simp [testBit, shiftRight_add]
@[simp] theorem shiftLeft_mod_two_eq_one : x <<< i % 2 = 1 i = 0 x % 2 = 1 := by
rw [mod_two_eq_one_iff_testBit_zero, testBit_shiftLeft]
simp
@[simp] theorem decide_shiftRight_mod_two_eq_one :
decide (x >>> i % 2 = 1) = x.testBit i := by
simp only [testBit, one_and_eq_mod_two, mod_two_bne_zero]
exact (Bool.beq_eq_decide_eq _ _).symm
/-! ### le -/
theorem le_of_testBit {n m : Nat} (h : i, n.testBit i = true m.testBit i = true) : n m := by

View File

@@ -48,7 +48,7 @@ def div.inductionOn.{u}
decreasing_by apply div_rec_lemma; assumption
theorem div_le_self (n k : Nat) : n / k n := by
induction n using Nat.strongInductionOn with
induction n using Nat.strongRecOn with
| ind n ih =>
rw [div_eq]
-- Note: manual split to avoid Classical.em which is not yet defined
@@ -221,7 +221,7 @@ theorem le_div_iff_mul_le (k0 : 0 < k) : x ≤ y / k ↔ x * k ≤ y := by
induction y, k using mod.inductionOn generalizing x with
(rw [div_eq]; simp [h]; cases x with | zero => simp [zero_le] | succ x => ?_)
| base y k h =>
simp only [add_one, succ_mul, false_iff, Nat.not_le]
simp only [add_one, succ_mul, false_iff, Nat.not_le, Nat.succ_ne_zero]
refine Nat.lt_of_lt_of_le ?_ (Nat.le_add_left ..)
exact Nat.not_le.1 fun h' => h k0, h'
| ind y k h IH =>
@@ -334,7 +334,7 @@ theorem mul_mod_mul_left (z x y : Nat) : (z * x) % (z * y) = z * (x % y) :=
else if z0 : z = 0 then by
rw [z0, Nat.zero_mul, Nat.zero_mul, Nat.zero_mul, mod_zero]
else by
induction x using Nat.strongInductionOn with
induction x using Nat.strongRecOn with
| _ n IH =>
have y0 : y > 0 := Nat.pos_of_ne_zero y0
have z0 : z > 0 := Nat.pos_of_ne_zero z0

View File

@@ -75,7 +75,7 @@ theorem gcd_rec (m n : Nat) : gcd m n = gcd (n % m) m :=
@[elab_as_elim] theorem gcd.induction {P : Nat Nat Prop} (m n : Nat)
(H0 : n, P 0 n) (H1 : m n, 0 < m P (n % m) m P m n) : P m n :=
Nat.strongInductionOn (motive := fun m => n, P m n) m
Nat.strongRecOn (motive := fun m => n, P m n) m
(fun
| 0, _ => H0
| _+1, IH => fun _ => H1 _ _ (succ_pos _) (IH _ (mod_lt _ (succ_pos _)) _) )

View File

@@ -27,6 +27,11 @@ namespace Nat
fun n, h, w => by cases n with | zero => simp at h | succ n => exact n, w,
fun n, w => n + 1, by simp, w
@[simp] theorem exists_eq_add_one : ( n, a = n + 1) 0 < a :=
fun n, h => by omega, fun h => a - 1, by omega
@[simp] theorem exists_add_one_eq : ( n, n + 1 = a) 0 < a :=
fun n, h => by omega, fun h => a - 1, by omega
/-! ## add -/
protected theorem add_add_add_comm (a b c d : Nat) : (a + b) + (c + d) = (a + c) + (b + d) := by
@@ -152,17 +157,9 @@ protected theorem sub_le_iff_le_add' {a b c : Nat} : a - b ≤ c ↔ a ≤ b + c
protected theorem le_sub_iff_add_le {n : Nat} (h : k m) : n m - k n + k m :=
Nat.add_le_of_le_sub h, Nat.le_sub_of_add_le
@[deprecated Nat.le_sub_iff_add_le (since := "2024-02-19")]
protected theorem add_le_to_le_sub (n : Nat) (h : m k) : n + m k n k - m :=
(Nat.le_sub_iff_add_le h).symm
protected theorem add_le_of_le_sub' {n k m : Nat} (h : m k) : n k - m m + n k :=
Nat.add_comm .. Nat.add_le_of_le_sub h
@[deprecated Nat.add_le_of_le_sub' (since := "2024-02-19")]
protected theorem add_le_of_le_sub_left {n k m : Nat} (h : m k) : n k - m m + n k :=
Nat.add_le_of_le_sub' h
protected theorem le_sub_of_add_le' {n k m : Nat} : m + n k n k - m :=
Nat.add_comm .. Nat.le_sub_of_add_le
@@ -424,14 +421,6 @@ protected theorem mul_min_mul_left (a b c : Nat) : min (a * b) (a * c) = a * min
/-! ### mul -/
@[deprecated Nat.mul_le_mul_left (since := "2024-02-19")]
protected theorem mul_le_mul_of_nonneg_left {a b c : Nat} : a b c * a c * b :=
Nat.mul_le_mul_left c
@[deprecated Nat.mul_le_mul_right (since := "2024-02-19")]
protected theorem mul_le_mul_of_nonneg_right {a b c : Nat} : a b a * c b * c :=
Nat.mul_le_mul_right c
protected theorem mul_right_comm (n m k : Nat) : n * m * k = n * k * m := by
rw [Nat.mul_assoc, Nat.mul_comm m, Nat.mul_assoc]
@@ -544,6 +533,11 @@ theorem mod_two_eq_zero_or_one (n : Nat) : n % 2 = 0 n % 2 = 1 :=
| 0, _ => .inl rfl
| 1, _ => .inr rfl
@[simp] theorem mod_two_bne_zero : ((a % 2) != 0) = (a % 2 == 1) := by
cases mod_two_eq_zero_or_one a <;> simp_all
@[simp] theorem mod_two_bne_one : ((a % 2) != 1) = (a % 2 == 0) := by
cases mod_two_eq_zero_or_one a <;> simp_all
theorem le_of_mod_lt {a b : Nat} (h : a % b < a) : b a :=
Nat.not_lt.1 fun hf => (ne_of_lt h).elim (Nat.mod_eq_of_lt hf)
@@ -654,6 +648,16 @@ protected theorem one_le_two_pow : 1 ≤ 2 ^ n :=
else
Nat.le_of_lt (Nat.one_lt_two_pow h)
@[simp] theorem one_mod_two_pow_eq_one : 1 % 2 ^ n = 1 0 < n := by
cases n with
| zero => simp
| succ n =>
rw [mod_eq_of_lt (a := 1) (Nat.one_lt_two_pow (by omega))]
simp
@[simp] theorem one_mod_two_pow (h : 0 < n) : 1 % 2 ^ n = 1 :=
one_mod_two_pow_eq_one.mpr h
protected theorem pow_pos (h : 0 < a) : 0 < a^n :=
match n with
| 0 => Nat.zero_lt_one
@@ -705,6 +709,36 @@ protected theorem pow_lt_pow_iff_right {a n m : Nat} (h : 1 < a) :
· intro w
exact Nat.pow_lt_pow_of_lt h w
@[simp]
protected theorem pow_pred_mul {x w : Nat} (h : 0 < w) :
x ^ (w - 1) * x = x ^ w := by
simp [ Nat.pow_succ, succ_eq_add_one, Nat.sub_add_cancel h]
protected theorem pow_pred_lt_pow {x w : Nat} (h₁ : 1 < x) (h₂ : 0 < w) :
x ^ (w - 1) < x ^ w :=
Nat.pow_lt_pow_of_lt h₁ (by omega)
protected theorem two_pow_pred_lt_two_pow {w : Nat} (h : 0 < w) :
2 ^ (w - 1) < 2 ^ w :=
Nat.pow_pred_lt_pow (by omega) h
@[simp]
protected theorem two_pow_pred_add_two_pow_pred (h : 0 < w) :
2 ^ (w - 1) + 2 ^ (w - 1) = 2 ^ w := by
rw [ Nat.pow_pred_mul h]
omega
@[simp]
protected theorem two_pow_sub_two_pow_pred (h : 0 < w) :
2 ^ w - 2 ^ (w - 1) = 2 ^ (w - 1) := by
simp [ Nat.two_pow_pred_add_two_pow_pred h]
@[simp]
protected theorem two_pow_pred_mod_two_pow (h : 0 < w) :
2 ^ (w - 1) % 2 ^ w = 2 ^ (w - 1) := by
rw [mod_eq_of_lt]
apply Nat.pow_pred_lt_pow (by omega) h
/-! ### log2 -/
@[simp]

View File

@@ -73,4 +73,10 @@ theorem mod_pow_succ {x b k : Nat} :
x % b ^ (k + 1) = x % b ^ k + b ^ k * ((x / b ^ k) % b) := by
rw [Nat.pow_succ, Nat.mod_mul]
@[simp] theorem two_pow_mod_two_eq_zero (n : Nat) : 2 ^ n % 2 = 0 0 < n := by
cases n <;> simp [Nat.pow_succ]
@[simp] theorem two_pow_mod_two_eq_one (n : Nat) : 2 ^ n % 2 = 1 n = 0 := by
cases n <;> simp [Nat.pow_succ]
end Nat

View File

@@ -19,7 +19,7 @@ theorem eq_of_eq_some {α : Type u} : ∀ {x y : Option α}, (∀z, x = some z
theorem eq_none_of_isNone {α : Type u} : {o : Option α}, o.isNone o = none
| none, _ => rfl
instance : Membership α (Option α) := fun a b => b = some a
instance : Membership α (Option α) := fun b a => b = some a
@[simp] theorem mem_def {a : α} {b : Option α} : a b b = some a := .rfl

View File

@@ -87,6 +87,9 @@ theorem eq_some_iff_get_eq : o = some a ↔ ∃ h : o.isSome, o.get h = a := by
theorem eq_some_of_isSome : {o : Option α} (h : o.isSome), o = some (o.get h)
| some _, _ => rfl
theorem isSome_iff_ne_none : o.isSome o none := by
cases o <;> simp
theorem not_isSome_iff_eq_none : ¬o.isSome o = none := by
cases o <;> simp
@@ -159,7 +162,7 @@ theorem map_some : f <$> some a = some (f a) := rfl
theorem map_eq_some : f <$> x = some b a, x = some a f a = b := map_eq_some'
@[simp] theorem map_eq_none' : x.map f = none x = none := by
cases x <;> simp only [map_none', map_some', eq_self_iff_true]
cases x <;> simp [map_none', map_some', eq_self_iff_true]
theorem isSome_map {x : Option α} : (f <$> x).isSome = x.isSome := by
cases x <;> simp
@@ -178,8 +181,19 @@ theorem map_eq_bind {x : Option α} : x.map f = x.bind (some ∘ f) := by
theorem map_congr {x : Option α} (h : a, a x f a = g a) : x.map f = x.map g := by
cases x <;> simp only [map_none', map_some', h, mem_def]
@[simp] theorem map_id' : Option.map (@id α) = id := map_id
@[simp] theorem map_id'' {x : Option α} : (x.map fun a => a) = x := congrFun map_id x
@[simp] theorem map_id_fun {α : Type u} : Option.map (id : α α) = id := by
funext; simp [map_id]
theorem map_id' {x : Option α} : (x.map fun a => a) = x := congrFun map_id x
@[simp] theorem map_id_fun' {α : Type u} : Option.map (fun (a : α) => a) = id := by
funext; simp [map_id']
theorem get_map {f : α β} {o : Option α} {h : (o.map f).isSome} :
(o.map f).get h = f (o.get (by simpa using h)) := by
cases o with
| none => simp at h
| some a => simp
@[simp] theorem map_map (h : β γ) (g : α β) (x : Option α) :
(x.map g).map h = x.map (h g) := by
@@ -238,6 +252,15 @@ theorem map_orElse {x y : Option α} : (x <|> y).map f = (x.map f <|> y.map f) :
@[simp] theorem guard_eq_some [DecidablePred p] : guard p a = some b a = b p a :=
if h : p a then by simp [Option.guard, h] else by simp [Option.guard, h]
@[simp] theorem guard_isSome [DecidablePred p] : (Option.guard p a).isSome p a :=
if h : p a then by simp [Option.guard, h] else by simp [Option.guard, h]
@[simp] theorem guard_eq_none [DecidablePred p] : Option.guard p a = none ¬ p a :=
if h : p a then by simp [Option.guard, h] else by simp [Option.guard, h]
@[simp] theorem guard_pos [DecidablePred p] (h : p a) : Option.guard p a = some a := by
simp [Option.guard, h]
theorem liftOrGet_eq_or_eq {f : α α α} (h : a b, f a b = a f a b = b) :
o₁ o₂, liftOrGet f o₁ o₂ = o₁ liftOrGet f o₁ o₂ = o₂
| none, none => .inl rfl
@@ -298,7 +321,7 @@ theorem or_eq_bif : or o o' = bif o.isSome then o else o' := by
@[simp] theorem or_eq_none : or o o' = none o = none o' = none := by
cases o <;> simp
theorem or_eq_some : or o o' = some a o = some a (o = none o' = some a) := by
@[simp] theorem or_eq_some : or o o' = some a o = some a (o = none o' = some a) := by
cases o <;> simp
theorem or_assoc : or (or o₁ o₂) o₃ = or o₁ (or o₂ o₃) := by

View File

@@ -15,7 +15,7 @@ structure Range where
step : Nat := 1
instance : Membership Nat Range where
mem i r := r.start i i < r.stop
mem r i := r.start i i < r.stop
namespace Range
universe u v

View File

@@ -227,7 +227,7 @@ Examples:
* `"abc".front = 'a'`
* `"".front = (default : Char)`
-/
def front (s : String) : Char :=
@[inline] def front (s : String) : Char :=
get s 0
/--
@@ -237,7 +237,7 @@ Examples:
* `"abc".back = 'c'`
* `"".back = (default : Char)`
-/
def back (s : String) : Char :=
@[inline] def back (s : String) : Char :=
get s (prev s s.endPos)
/--
@@ -374,7 +374,7 @@ Examples:
* `"abba".posOf 'z' = none`
* `"L∃∀N".posOf '∀' = some ⟨4⟩`
-/
def revPosOf (s : String) (c : Char) : Option Pos :=
@[inline] def revPosOf (s : String) (c : Char) : Option Pos :=
revPosOfAux s c s.endPos
def findAux (s : String) (p : Char Bool) (stopPos : Pos) (pos : Pos) : Pos :=
@@ -398,7 +398,7 @@ def revFindAux (s : String) (p : Char → Bool) (pos : Pos) : Option Pos :=
else revFindAux s p pos
termination_by pos.1
def revFind (s : String) (p : Char Bool) : Option Pos :=
@[inline] def revFind (s : String) (p : Char Bool) : Option Pos :=
revFindAux s p s.endPos
abbrev Pos.min (p₁ p₂ : Pos) : Pos :=
@@ -505,7 +505,7 @@ The default separator is `" "`. The separators are not included in the returned
"ababacabac".splitOn "aba" = ["", "bac", "c"]
```
-/
def splitOn (s : String) (sep : String := " ") : List String :=
@[inline] def splitOn (s : String) (sep : String := " ") : List String :=
if sep == "" then [s] else splitOnAux s sep 0 0 0 []
instance : Inhabited String := ""
@@ -515,16 +515,16 @@ instance : Append String := ⟨String.append⟩
@[deprecated push (since := "2024-04-06")]
def str : String Char String := push
def pushn (s : String) (c : Char) (n : Nat) : String :=
@[inline] def pushn (s : String) (c : Char) (n : Nat) : String :=
n.repeat (fun s => s.push c) s
def isEmpty (s : String) : Bool :=
@[inline] def isEmpty (s : String) : Bool :=
s.endPos == 0
def join (l : List String) : String :=
@[inline] def join (l : List String) : String :=
l.foldl (fun r s => r ++ s) ""
def singleton (c : Char) : String :=
@[inline] def singleton (c : Char) : String :=
"".push c
def intercalate (s : String) : List String String
@@ -561,7 +561,7 @@ structure Iterator where
deriving DecidableEq, Inhabited
/-- Creates an iterator at the beginning of a string. -/
def mkIterator (s : String) : Iterator :=
@[inline] def mkIterator (s : String) : Iterator :=
s, 0
@[inherit_doc mkIterator]
@@ -575,66 +575,74 @@ theorem Iterator.sizeOf_eq (i : String.Iterator) : sizeOf i = i.1.utf8ByteSize -
rfl
namespace Iterator
@[inherit_doc Iterator.s]
@[inline, inherit_doc Iterator.s]
def toString := Iterator.s
/-- Number of bytes remaining in the iterator. -/
def remainingBytes : Iterator Nat
@[inline] def remainingBytes : Iterator Nat
| s, i => s.endPos.byteIdx - i.byteIdx
@[inherit_doc Iterator.i]
@[inline, inherit_doc Iterator.i]
def pos := Iterator.i
/-- The character at the current position.
On an invalid position, returns `(default : Char)`. -/
def curr : Iterator Char
@[inline] def curr : Iterator Char
| s, i => get s i
/-- Moves the iterator's position forward by one character, unconditionally.
It is only valid to call this function if the iterator is not at the end of the string, *i.e.*
`Iterator.atEnd` is `false`; otherwise, the resulting iterator will be invalid. -/
def next : Iterator Iterator
@[inline] def next : Iterator Iterator
| s, i => s, s.next i
/-- Decreases the iterator's position.
If the position is zero, this function is the identity. -/
def prev : Iterator Iterator
@[inline] def prev : Iterator Iterator
| s, i => s, s.prev i
/-- True if the iterator is past the string's last character. -/
def atEnd : Iterator Bool
@[inline] def atEnd : Iterator Bool
| s, i => i.byteIdx s.endPos.byteIdx
/-- True if the iterator is not past the string's last character. -/
def hasNext : Iterator Bool
@[inline] def hasNext : Iterator Bool
| s, i => i.byteIdx < s.endPos.byteIdx
/-- True if the position is not zero. -/
def hasPrev : Iterator Bool
@[inline] def hasPrev : Iterator Bool
| _, i => i.byteIdx > 0
@[inline] def curr' (it : Iterator) (h : it.hasNext) : Char :=
match it with
| s, i => get' s i (by simpa only [hasNext, endPos, decide_eq_true_eq, String.atEnd, ge_iff_le, Nat.not_le] using h)
@[inline] def next' (it : Iterator) (h : it.hasNext) : Iterator :=
match it with
| s, i => s, s.next' i (by simpa only [hasNext, endPos, decide_eq_true_eq, String.atEnd, ge_iff_le, Nat.not_le] using h)
/-- Replaces the current character in the string.
Does nothing if the iterator is at the end of the string. If the iterator contains the only
reference to its string, this function will mutate the string in-place instead of allocating a new
one. -/
def setCurr : Iterator Char Iterator
@[inline] def setCurr : Iterator Char Iterator
| s, i, c => s.set i c, i
/-- Moves the iterator's position to the end of the string.
Note that `i.toEnd.atEnd` is always `true`. -/
def toEnd : Iterator Iterator
@[inline] def toEnd : Iterator Iterator
| s, _ => s, s.endPos
/-- Extracts the substring between the positions of two iterators.
Returns the empty string if the iterators are for different strings, or if the position of the first
iterator is past the position of the second iterator. -/
def extract : Iterator Iterator String
@[inline] def extract : Iterator Iterator String
| s₁, b, s₂, e =>
if s₁ s₂ || b > e then ""
else s₁.extract b e
@@ -648,7 +656,7 @@ def forward : Iterator → Nat → Iterator
| it, n+1 => forward it.next n
/-- The remaining characters in an iterator, as a string. -/
def remainingToString : Iterator String
@[inline] def remainingToString : Iterator String
| s, i => s.extract i s.endPos
@[inherit_doc forward]
@@ -673,7 +681,7 @@ def offsetOfPosAux (s : String) (pos : Pos) (i : Pos) (offset : Nat) : Nat :=
offsetOfPosAux s pos (s.next i) (offset+1)
termination_by s.endPos.1 - i.1
def offsetOfPos (s : String) (pos : Pos) : Nat :=
@[inline] def offsetOfPos (s : String) (pos : Pos) : Nat :=
offsetOfPosAux s pos 0 0
@[specialize] def foldlAux {α : Type u} (f : α Char α) (s : String) (stopPos : Pos) (i : Pos) (a : α) : α :=
@@ -714,7 +722,7 @@ termination_by stopPos.1 - i.1
@[inline] def all (s : String) (p : Char Bool) : Bool :=
!s.any (fun c => !p c)
def contains (s : String) (c : Char) : Bool :=
@[inline] def contains (s : String) (c : Char) : Bool :=
s.any (fun a => a == c)
theorem utf8SetAux_of_gt (c' : Char) : (cs : List Char) {i p : Pos}, i > p utf8SetAux c' cs i p = cs
@@ -770,7 +778,7 @@ termination_by s.endPos.1 - i.1
@[inline] def map (f : Char Char) (s : String) : String :=
mapAux f 0 s
def isNat (s : String) : Bool :=
@[inline] def isNat (s : String) : Bool :=
!s.isEmpty && s.all (·.isDigit)
def toNat? (s : String) : Option Nat :=
@@ -940,7 +948,7 @@ def splitOn (s : Substring) (sep : String := " ") : List Substring :=
@[inline] def all (s : Substring) (p : Char Bool) : Bool :=
!s.any (fun c => !p c)
def contains (s : Substring) (c : Char) : Bool :=
@[inline] def contains (s : Substring) (c : Char) : Bool :=
s.any (fun a => a == c)
@[specialize] def takeWhileAux (s : String) (stopPos : String.Pos) (p : Char Bool) (i : String.Pos) : String.Pos :=
@@ -995,7 +1003,7 @@ termination_by i.1
let e := takeRightWhileAux s b Char.isWhitespace e
s, b, e
def isNat (s : Substring) : Bool :=
@[inline] def isNat (s : Substring) : Bool :=
s.all fun c => c.isDigit
def toNat? (s : Substring) : Option Nat :=
@@ -1017,43 +1025,43 @@ end Substring
namespace String
def drop (s : String) (n : Nat) : String :=
@[inline] def drop (s : String) (n : Nat) : String :=
(s.toSubstring.drop n).toString
def dropRight (s : String) (n : Nat) : String :=
@[inline] def dropRight (s : String) (n : Nat) : String :=
(s.toSubstring.dropRight n).toString
def take (s : String) (n : Nat) : String :=
@[inline] def take (s : String) (n : Nat) : String :=
(s.toSubstring.take n).toString
def takeRight (s : String) (n : Nat) : String :=
@[inline] def takeRight (s : String) (n : Nat) : String :=
(s.toSubstring.takeRight n).toString
def takeWhile (s : String) (p : Char Bool) : String :=
@[inline] def takeWhile (s : String) (p : Char Bool) : String :=
(s.toSubstring.takeWhile p).toString
def dropWhile (s : String) (p : Char Bool) : String :=
@[inline] def dropWhile (s : String) (p : Char Bool) : String :=
(s.toSubstring.dropWhile p).toString
def takeRightWhile (s : String) (p : Char Bool) : String :=
@[inline] def takeRightWhile (s : String) (p : Char Bool) : String :=
(s.toSubstring.takeRightWhile p).toString
def dropRightWhile (s : String) (p : Char Bool) : String :=
@[inline] def dropRightWhile (s : String) (p : Char Bool) : String :=
(s.toSubstring.dropRightWhile p).toString
def startsWith (s pre : String) : Bool :=
@[inline] def startsWith (s pre : String) : Bool :=
s.toSubstring.take pre.length == pre.toSubstring
def endsWith (s post : String) : Bool :=
@[inline] def endsWith (s post : String) : Bool :=
s.toSubstring.takeRight post.length == post.toSubstring
def trimRight (s : String) : String :=
@[inline] def trimRight (s : String) : String :=
s.toSubstring.trimRight.toString
def trimLeft (s : String) : String :=
@[inline] def trimLeft (s : String) : String :=
s.toSubstring.trimLeft.toString
def trim (s : String) : String :=
@[inline] def trim (s : String) : String :=
s.toSubstring.trim.toString
@[inline] def nextWhile (s : String) (p : Char Bool) (i : String.Pos) : String.Pos :=
@@ -1062,23 +1070,23 @@ def trim (s : String) : String :=
@[inline] def nextUntil (s : String) (p : Char Bool) (i : String.Pos) : String.Pos :=
nextWhile s (fun c => !p c) i
def toUpper (s : String) : String :=
@[inline] def toUpper (s : String) : String :=
s.map Char.toUpper
def toLower (s : String) : String :=
@[inline] def toLower (s : String) : String :=
s.map Char.toLower
def capitalize (s : String) :=
@[inline] def capitalize (s : String) :=
s.set 0 <| s.get 0 |>.toUpper
def decapitalize (s : String) :=
@[inline] def decapitalize (s : String) :=
s.set 0 <| s.get 0 |>.toLower
end String
namespace Char
protected def toString (c : Char) : String :=
@[inline] protected def toString (c : Char) : String :=
String.singleton c
@[simp] theorem length_toString (c : Char) : c.toString.length = 1 := rfl

View File

@@ -75,7 +75,7 @@ See #2572.
opaque Internal.hasLLVMBackend (u : Unit) : Bool
/-- Valid identifier names -/
def isGreek (c : Char) : Bool :=
@[inline] def isGreek (c : Char) : Bool :=
0x391 c.val && c.val 0x3dd
def isLetterLike (c : Char) : Bool :=
@@ -86,7 +86,7 @@ def isLetterLike (c : Char) : Bool :=
(0x2100 c.val && c.val 0x214f) || -- Letter like block
(0x1d49c c.val && c.val 0x1d59f) -- Latin letters, Script, Double-struck, Fractur
def isNumericSubscript (c : Char) : Bool :=
@[inline] def isNumericSubscript (c : Char) : Bool :=
0x2080 c.val && c.val 0x2089
def isSubScriptAlnum (c : Char) : Bool :=
@@ -94,16 +94,16 @@ def isSubScriptAlnum (c : Char) : Bool :=
(0x2090 c.val && c.val 0x209c) ||
(0x1d62 c.val && c.val 0x1d6a)
def isIdFirst (c : Char) : Bool :=
@[inline] def isIdFirst (c : Char) : Bool :=
c.isAlpha || c = '_' || isLetterLike c
def isIdRest (c : Char) : Bool :=
@[inline] def isIdRest (c : Char) : Bool :=
c.isAlphanum || c = '_' || c = '\'' || c == '!' || c == '?' || isLetterLike c || isSubScriptAlnum c
def idBeginEscape := '«'
def idEndEscape := '»'
def isIdBeginEscape (c : Char) : Bool := c = idBeginEscape
def isIdEndEscape (c : Char) : Bool := c = idEndEscape
@[inline] def isIdBeginEscape (c : Char) : Bool := c = idBeginEscape
@[inline] def isIdEndEscape (c : Char) : Bool := c = idEndEscape
namespace Name
def getRoot : Name Name
@@ -388,9 +388,9 @@ def getSubstring? (stx : Syntax) (withLeading := true) (withTrailing := true) :
partial def setTailInfoAux (info : SourceInfo) : Syntax Option Syntax
| atom _ val => some <| atom info val
| ident _ rawVal val pre => some <| ident info rawVal val pre
| node info k args =>
| node info' k args =>
match updateLast args (setTailInfoAux info) args.size with
| some args => some <| node info k args
| some args => some <| node info' k args
| none => none
| _ => none

View File

@@ -336,7 +336,7 @@ macro_rules | `($x == $y) => `(binrel_no_prop% BEq.beq $x $y)
@[inherit_doc] infixl:30 " || " => or
@[inherit_doc] notation:max "!" b:40 => not b
@[inherit_doc] infix:50 "" => Membership.mem
@[inherit_doc] notation:50 a:50 "" b:50 => Membership.mem b a
/-- `a ∉ b` is negated elementhood. It is notation for `¬ (a ∈ b)`. -/
notation:50 a:50 "" b:50 => ¬ (a b)

View File

@@ -300,6 +300,8 @@ theorem normalize_sat {s x v} (w : s.sat' x v) :
· split
· simp
· dsimp [Constraint.sat'] at w
simp only [IntList.gcd_eq_zero] at h
simp only [IntList.dot_eq_zero_of_left_eq_zero h] at w
simp_all
· split
· exact w

View File

@@ -116,7 +116,7 @@ theorem ofNat_max (a b : Nat) : ((max a b : Nat) : Int) = max (a : Int) (b : Int
split <;> rfl
theorem ofNat_natAbs (a : Int) : (a.natAbs : Int) = if 0 a then a else -a := by
rw [Int.natAbs]
rw [Int.natAbs.eq_def]
split <;> rename_i n
· simp only [Int.ofNat_eq_coe]
rw [if_pos (Int.ofNat_nonneg n)]

View File

@@ -352,7 +352,6 @@ attribute [simp] Int.zero_dvd
theorem gcd_dvd_dot_left (xs ys : IntList) : (xs.gcd : Int) dot xs ys :=
Int.dvd_of_emod_eq_zero (dot_mod_gcd_left xs ys)
@[simp]
theorem dot_eq_zero_of_left_eq_zero {xs ys : IntList} (h : x, x xs x = 0) : dot xs ys = 0 := by
induction xs generalizing ys with
| nil => rfl
@@ -363,6 +362,8 @@ theorem dot_eq_zero_of_left_eq_zero {xs ys : IntList} (h : ∀ x, x ∈ xs → x
rw [dot_cons₂, h x (List.mem_cons_self _ _), ih (fun x m => h x (List.mem_cons_of_mem _ m)),
Int.zero_mul, Int.add_zero]
@[simp] theorem nil_dot (xs : IntList) : dot [] xs = 0 := rfl
theorem dot_sdiv_left (xs ys : IntList) {d : Int} (h : d xs.gcd) :
dot (xs.sdiv d) ys = (dot xs ys) / d := by
induction xs generalizing ys with

View File

@@ -1515,7 +1515,7 @@ of the elements of the container.
-/
class Membership (α : outParam (Type u)) (γ : Type v) where
/-- The membership relation `a ∈ s : Prop` where `a : α`, `s : γ`. -/
mem : α γ Prop
mem : γ α Prop
set_option bootstrap.genMatcherCode false in
/--

View File

@@ -169,12 +169,23 @@ theorem if_true_right [h : Decidable p] :
@[simp] theorem ite_not (p : Prop) [Decidable p] (x y : α) : ite (¬p) x y = ite p y x :=
dite_not (fun _ => x) (fun _ => y)
@[simp] theorem ite_true_same (p q : Prop) [h : Decidable p] : (if p then p else q) = (¬p q) := by
@[simp] theorem ite_then_self (p q : Prop) [h : Decidable p] : (if p then p else q) = (¬p q) := by
cases h <;> (rename_i g; simp [g])
@[simp] theorem ite_false_same (p q : Prop) [h : Decidable p] : (if p then q else p) = (p q) := by
@[simp] theorem ite_else_self (p q : Prop) [h : Decidable p] : (if p then q else p) = (p q) := by
cases h <;> (rename_i g; simp [g])
@[simp] theorem ite_then_not_self (p : Prop) [Decidable p] (q : Prop) : (if p then ¬p else q) ¬p q := by
split <;> simp_all
@[simp] theorem ite_else_not_self (p : Prop) [Decidable p] (q : Prop) : (if p then q else ¬p) p q := by
split <;> simp_all
@[deprecated ite_then_self (since := "2024-08-28")]
theorem ite_true_same (p q : Prop) [Decidable p] : (if p then p else q) = (¬p q) := ite_then_self p q
@[deprecated ite_else_self (since := "2024-08-28")]
theorem ite_false_same (p q : Prop) [Decidable p] : (if p then q else p) = (p q) := ite_else_self p q
/-! ## exists and forall -/
section quantifiers
@@ -326,6 +337,9 @@ theorem not_forall_of_exists_not {p : α → Prop} : (∃ x, ¬p x) → ¬∀ x,
@[simp] theorem exists_or_eq_left' (y : α) (p : α Prop) : x : α, y = x p x := y, .inl rfl
@[simp] theorem exists_or_eq_right' (y : α) (p : α Prop) : x : α, p x y = x := y, .inr rfl
@[simp] theorem exists_prop' (p : Prop) : ( _ : α, p) Nonempty α p :=
fun a, h => a, h, fun a, h => a, h
@[simp] theorem exists_prop : ( _h : a, b) a b :=
fun hp, hq => hp, hq, fun hp, hq => hp, hq
@@ -355,6 +369,11 @@ theorem forall_prop_of_false {p : Prop} {q : p → Prop} (hn : ¬p) : (∀ h' :
end quantifiers
/-! ## Nonempty -/
@[simp] theorem nonempty_prop (p : Prop) : Nonempty p p :=
fun h => h, fun h => h
/-! ## decidable -/
@[simp] theorem Decidable.not_not [Decidable p] : ¬¬p p := of_not_not, not_not_intro
@@ -390,7 +409,7 @@ else isTrue fun h2 => absurd h2 h
theorem decide_eq_true_iff (p : Prop) [Decidable p] : (decide p = true) p := by simp
@[simp] theorem decide_eq_decide {p q : Prop} {_ : Decidable p} {_ : Decidable q} :
@[simp, boolToPropSimps] theorem decide_eq_decide {p q : Prop} {_ : Decidable p} {_ : Decidable q} :
decide p = decide q (p q) :=
fun h => by rw [ decide_eq_true_iff p, h, decide_eq_true_iff], fun h => by simp [h]
@@ -403,7 +422,7 @@ theorem Decidable.not_imp_symm [Decidable a] (h : ¬a → b) (hb : ¬b) : a :=
theorem Decidable.not_imp_comm [Decidable a] [Decidable b] : (¬a b) (¬b a) :=
not_imp_symm, not_imp_symm
@[simp] theorem Decidable.not_imp_self [Decidable a] : (¬a a) a := by
theorem Decidable.not_imp_self [Decidable a] : (¬a a) a := by
have := @imp_not_self (¬a); rwa [not_not] at this
theorem Decidable.or_iff_not_imp_left [Decidable a] : a b (¬a b) :=
@@ -486,7 +505,7 @@ theorem Decidable.imp_iff_right_iff [Decidable a] : (a → b ↔ b) ↔ a b
(fun h => (Decidable.em a).imp_right fun ha' => h.mp fun ha => (ha' ha).elim)
(fun ab => ab.elim imp_iff_right fun hb => iff_of_true (fun _ => hb) hb)
theorem Decidable.imp_iff_left_iff [Decidable a] : (b a b) a b :=
theorem Decidable.imp_iff_left_iff [Decidable a] : (b a b) a b :=
propext (@Iff.comm (a b) b) (@Decidable.imp_iff_right_iff a b _)
theorem Decidable.and_or_imp [Decidable a] : a b (a c) a b c :=
@@ -573,12 +592,66 @@ theorem decide_ite (u : Prop) [du : Decidable u] (p q : Prop)
decide (ite u p q) = ite u (decide p) (decide q) := by
cases du <;> simp [*]
/- Confluence for `ite_true_same` and `decide_ite`. -/
@[simp] theorem ite_true_decide_same (p : Prop) [h : Decidable p] (b : Bool) :
(if p then decide p else b) = (decide p || b) := by
cases h <;> (rename_i pt; simp [pt])
/- Confluence for `ite_then_self` and `decide_ite`. -/
@[simp] theorem ite_then_decide_self (p : Prop) [h : Decidable p] {w : Decidable p} (q : Bool) :
(@ite _ p h (decide p) q) = (decide p || q) := by
split <;> simp_all
/- Confluence for `ite_false_same` and `decide_ite`. -/
@[simp] theorem ite_false_decide_same (p : Prop) [h : Decidable p] (b : Bool) :
(if p then b else decide p) = (decide p && b) := by
cases h <;> (rename_i pt; simp [pt])
/- Confluence for `ite_else_self` and `decide_ite`. -/
@[simp] theorem ite_else_decide_self (p : Prop) [h : Decidable p] {w : Decidable p} (q : Bool) :
(@ite _ p h q (decide p)) = (decide p && q) := by
split <;> simp_all
@[deprecated ite_then_decide_self]
theorem ite_true_decide_same (p : Prop) [Decidable p] (b : Bool) :
(if p then decide p else b) = (decide p || b) := ite_then_decide_self p b
@[deprecated ite_false_decide_same]
theorem ite_false_decide_same (p : Prop) [Decidable p] (b : Bool) :
(if p then b else decide p) = (decide p && b) := ite_else_decide_self p b
@[simp] theorem ite_then_decide_not_self (p : Prop) [h : Decidable p] {w : Decidable p} (q : Bool) :
(@ite _ p h (!decide p) q) = (!decide p && q) := by
split <;> simp_all
@[simp] theorem ite_else_decide_not_self (p : Prop) [h : Decidable p] {w : Decidable p} (q : Bool) :
(@ite _ p h q (!decide p)) = (!decide p || q) := by
split <;> simp_all
attribute [local simp] Decidable.imp_iff_left_iff
@[simp] theorem dite_eq_then (p : Prop) [Decidable p] {x : α} {y : ¬ p α} : (if h : p then x else y h) = x h : ¬ p, y h = x := by
split <;> simp_all
@[simp] theorem dite_eq_else (p : Prop) [Decidable p] {x : p α} {y : α} : (if h : p then x h else y) = y h : p, x h = y := by
split <;> simp_all
@[simp] theorem dite_iff_then (p : Prop) [Decidable p] {x : Prop} {y : ¬ p Prop} : ((if h : p then x else y h) x) h : ¬ p, y h x := by
split <;> simp_all
@[simp] theorem dite_iff_else (p : Prop) [Decidable p] {x : p Prop} {y : Prop} : ((if h : p then x h else y) y) h : p, x h y := by
split <;> simp_all
@[simp] theorem ite_eq_then (p : Prop) [Decidable p] (x y : α) : (if p then x else y) = x ¬ p y = x := by
split <;> simp_all
@[simp] theorem ite_eq_else (p : Prop) [Decidable p] (x y : α) : (if p then x else y) = y p x = y := by
split <;> simp_all
@[simp] theorem ite_iff_then (p : Prop) [Decidable p] (x y : Prop) : ((if p then x else y) x) ¬ p y = x := by
split <;> simp_all
@[simp] theorem ite_iff_else (p : Prop) [Decidable p] (x y : Prop) : ((if p then x else y) y) p x = y := by
split <;> simp_all
@[simp] theorem dite_then_false (p : Prop) [Decidable p] {x : ¬ p Prop} : (if h : p then False else x h) h : ¬ p, x h := by
split <;> simp_all
@[simp] theorem dite_else_false (p : Prop) [Decidable p] {x : p Prop} : (if h : p then x h else False) h : p, x h := by
split <;> simp_all
@[simp] theorem dite_then_true (p : Prop) [Decidable p] {x : ¬ p Prop} : (if h : p then True else x h) h : ¬ p, x h := by
split <;> simp_all
@[simp] theorem dite_else_true (p : Prop) [Decidable p] {x : p Prop} : (if h : p then x h else True) h : p, x h := by
split <;> simp_all

View File

@@ -244,7 +244,7 @@ instance : Std.Associative (· || ·) := ⟨Bool.or_assoc⟩
@[simp] theorem decide_not [g : Decidable p] [h : Decidable (Not p)] : decide (Not p) = !(decide p) := by
cases g <;> (rename_i gp; simp [gp]; rfl)
@[simp] theorem not_decide_eq_true [h : Decidable p] : ((!decide p) = true) = ¬ p := by simp
theorem not_decide_eq_true [h : Decidable p] : ((!decide p) = true) = ¬ p := by simp
@[simp] theorem heq_eq_eq (a b : α) : HEq a b = (a = b) := propext <| Iff.intro eq_of_heq heq_of_eq

View File

@@ -447,6 +447,7 @@ see there for more information.
@[extern "lean_io_remove_dir"] opaque removeDir : @& FilePath IO Unit
@[extern "lean_io_create_dir"] opaque createDir : @& FilePath IO Unit
/--
Moves a file or directory `old` to the new location `new`.
@@ -455,6 +456,16 @@ see there for more information.
-/
@[extern "lean_io_rename"] opaque rename (old new : @& FilePath) : IO Unit
/--
Creates a temporary file in the most secure manner possible. There are no race conditions in the
files creation. The file is readable and writable only by the creating user ID. Additionally
on UNIX style platforms the file is executable by nobody. The function returns both a `Handle`
to the already opened file as well as its `FilePath`.
Note that it is the caller's job to remove the file after use.
-/
@[extern "lean_io_create_tempfile"] opaque createTempFile : IO (Handle × FilePath)
end FS
@[extern "lean_io_getenv"] opaque getEnv (var : @& String) : BaseIO (Option String)
@@ -467,6 +478,17 @@ namespace FS
def withFile (fn : FilePath) (mode : Mode) (f : Handle IO α) : IO α :=
Handle.mk fn mode >>= f
/--
Like `createTempFile` but also takes care of removing the file after usage.
-/
def withTempFile [Monad m] [MonadFinally m] [MonadLiftT IO m] (f : Handle FilePath m α) :
m α := do
let (handle, path) createTempFile
try
f handle path
finally
removeFile path
def Handle.putStrLn (h : Handle) (s : String) : IO Unit :=
h.putStr (s.push '\n')

View File

@@ -552,9 +552,9 @@ The `simp` tactic uses lemmas and hypotheses to simplify the main goal target or
non-dependent hypotheses. It has many variants:
- `simp` simplifies the main goal target using lemmas tagged with the attribute `[simp]`.
- `simp [h₁, h₂, ..., hₙ]` simplifies the main goal target using the lemmas tagged
with the attribute `[simp]` and the given `hᵢ`'s, where the `hᵢ`'s are expressions.
If an `hᵢ` is a defined constant `f`, then the equational lemmas associated with
`f` are used. This provides a convenient way to unfold `f`.
with the attribute `[simp]` and the given `hᵢ`'s, where the `hᵢ`'s are expressions.-
- If an `hᵢ` is a defined constant `f`, then `f` is unfolded. If `f` has equational lemmas associated
with it (and is not a projection or a `reducible` definition), these are used to rewrite with `f`.
- `simp [*]` simplifies the main goal target using the lemmas tagged with the
attribute `[simp]` and all hypotheses.
- `simp only [h₁, h₂, ..., hₙ]` is like `simp [h₁, h₂, ..., hₙ]` but does not use `[simp]` lemmas.
@@ -679,9 +679,9 @@ syntax (name := delta) "delta" (ppSpace colGt ident)+ (location)? : tactic
* `unfold id` unfolds definition `id`.
* `unfold id1 id2 ...` is equivalent to `unfold id1; unfold id2; ...`.
For non-recursive definitions, this tactic is identical to `delta`.
For definitions by pattern matching, it uses "equation lemmas" which are
autogenerated for each match arm.
For non-recursive definitions, this tactic is identical to `delta`. For recursive definitions,
it uses the "unfolding lemma" `id.eq_def`, which is generated for each recursive definition,
to unfold according to the recursive definition given by the user.
-/
syntax (name := unfold) "unfold" (ppSpace colGt ident)+ (location)? : tactic

View File

@@ -190,21 +190,36 @@ def lt_wfRel : WellFoundedRelation Nat where
| Or.inl e => subst e; assumption
| Or.inr e => exact Acc.inv ih e
protected noncomputable def strongInductionOn
@[elab_as_elim] protected noncomputable def strongRecOn
{motive : Nat Sort u}
(n : Nat)
(ind : n, ( m, m < n motive m) motive n) : motive n :=
Nat.lt_wfRel.wf.fix ind n
@[deprecated Nat.strongRecOn (since := "2024-08-27")]
protected noncomputable def strongInductionOn
{motive : Nat Sort u}
(n : Nat)
(ind : n, ( m, m < n motive m) motive n) : motive n :=
Nat.strongRecOn n ind
@[elab_as_elim] protected noncomputable def caseStrongRecOn
{motive : Nat Sort u}
(a : Nat)
(zero : motive 0)
(ind : n, ( m, m n motive m) motive (succ n)) : motive a :=
Nat.strongRecOn a fun n =>
match n with
| 0 => fun _ => zero
| n+1 => fun h₁ => ind n (λ _ h₂ => h₁ _ (lt_succ_of_le h₂))
@[deprecated Nat.caseStrongRecOn (since := "2024-08-27")]
protected noncomputable def caseStrongInductionOn
{motive : Nat Sort u}
(a : Nat)
(zero : motive 0)
(ind : n, ( m, m n motive m) motive (succ n)) : motive a :=
Nat.strongInductionOn a fun n =>
match n with
| 0 => fun _ => zero
| n+1 => fun h₁ => ind n (λ _ h₂ => h₁ _ (lt_succ_of_le h₂))
Nat.caseStrongRecOn a zero ind
end Nat

View File

@@ -26,7 +26,7 @@ macro "clean_wf" : tactic =>
`(tactic| simp
(config := { unfoldPartialApp := true, zetaDelta := true, failIfUnchanged := false })
only [invImage, InvImage, Prod.lex, sizeOfWFRel, measure, Nat.lt_wfRel,
WellFoundedRelation.rel, sizeOf_nat])
WellFoundedRelation.rel, sizeOf_nat, reduceCtorEq])
/-- Extensible helper tactic for `decreasing_tactic`. This handles the "base case"
reasoning after applying lexicographic order lemmas.

View File

@@ -9,20 +9,19 @@ import Lean.Data.Json.Basic
import Lean.Data.RBMap
import Std.Internal.Parsec
namespace Lean.Json.Parser
open Std.Internal.Parsec
open Std.Internal.Parsec.String
@[inline]
namespace Lean.Json.Parser
def hexChar : Parser Nat := do
let c any
if '0' c c '9' then
pure $ c.val.toNat - '0'.val.toNat
else if 'a' c c 'f' then
pure $ c.val.toNat - 'a'.val.toNat + 10
else if 'A' c c 'F' then
pure $ c.val.toNat - 'A'.val.toNat + 10
if '0' <= c && c <= '9' then
pure $ (c.val - '0'.val).toNat
else if 'a' <= c && c <= 'f' then
pure $ (c.val - 'a'.val + 10).toNat
else if 'A' <= c && c <= 'F' then
pure $ (c.val - 'A'.val + 10).toNat
else
fail "invalid hex character"
@@ -44,31 +43,46 @@ def escapedChar : Parser Char := do
partial def strCore (acc : String) : Parser String := do
let c peek!
if c = '"' then -- "
if c == '"' then
skip
return acc
else
let c ← any
if c = '\\' then
if c == '\\' then
strCore (acc.push (← escapedChar))
-- as to whether c.val > 0xffff should be split up and encoded with multiple \u,
-- the JSON standard is not definite: both directly printing the character
-- and encoding it with multiple \u is allowed. we choose the former.
else if 0x0020 c.val c.val 0x10ffff then
else if 0x0020 <= c.val && c.val <= 0x10ffff then
strCore (acc.push c)
else
fail "unexpected character in string"
def str : Parser String := strCore ""
@[inline] def str : Parser String := strCore ""
partial def natCore (acc digits : Nat) : Parser (Nat × Nat) := do
let some c peek? | return (acc, digits)
if '0' c c '9' then
skip
let acc' := 10*acc + (c.val.toNat - '0'.val.toNat)
natCore acc' (digits+1)
partial def natCore (acc : Nat) : Parser Nat := do
if ← isEof then
return acc
else
let c ← peek!
if '0' <= c && c <= '9' then
skip
let acc' := 10*acc + (c.val - '0'.val).toNat
natCore acc'
else
return acc
partial def natCoreNumDigits (acc digits : Nat) : Parser (Nat × Nat) := do
if ← isEof then
return (acc, digits)
else
let c ← peek!
if '0' <= c && c <= '9' then
skip
let acc' := 10*acc + (c.val - '0'.val).toNat
natCoreNumDigits acc' (digits+1)
else
return (acc, digits)
@[inline]
def lookahead (p : Char → Prop) (desc : String) [DecidablePred p] : Parser Unit := do
@@ -80,129 +94,152 @@ def lookahead (p : Char → Prop) (desc : String) [DecidablePred p] : Parser Uni
@[inline]
def natNonZero : Parser Nat := do
lookahead (fun c => '1' c c '9') "1-9"
let (n, _) natCore 0 0
return n
lookahead (fun c => '1' <= c && c <= '9') "1-9"
natCore 0
@[inline]
def natNumDigits : Parser (Nat × Nat) := do
lookahead (fun c => '0' c c '9') "digit"
natCore 0 0
lookahead (fun c => '0' <= c && c <= '9') "digit"
natCoreNumDigits 0 0
@[inline]
def natMaybeZero : Parser Nat := do
let (n, _) natNumDigits
return n
lookahead (fun c => '0' <= c && c <= '9') "0-9"
natCore 0
def num : Parser JsonNumber := do
@[inline]
def numSign : Parser Int := do
let c ← peek!
let sign if c = '-' then
let sign ← if c == '-' then
skip
pure (-1 : Int)
return (-1 : Int)
else
pure 1
return 1
@[inline]
def nat : Parser Nat := do
let c ← peek!
let res if c = '0' then
if c == '0' then
skip
pure 0
return 0
else
natNonZero
let c? peek?
let res : JsonNumber if c? = some '.' then
skip
let (n, d) natNumDigits
if d > USize.size then fail "too many decimals"
let mantissa' := sign * (res * (10^d : Nat) + n)
let exponent' := d
pure <| JsonNumber.mk mantissa' exponent'
@[inline]
def numWithDecimals : Parser JsonNumber := do
let sign ← numSign
let whole ← nat
if ← isEof then
pure <| JsonNumber.fromInt (sign * whole)
else
pure <| JsonNumber.fromInt (sign * res)
let c? peek?
if c? = some 'e' c? = some 'E' then
skip
let c ← peek!
if c = '-' then
if c == '.' then
skip
let n natMaybeZero
return res.shiftr n
let (n, d) ← natNumDigits
if d > USize.size then fail "too many decimals"
let mantissa' := sign * (whole * (10^d : Nat) + n)
let exponent' := d
pure <| JsonNumber.mk mantissa' exponent'
else
if c = '+' then skip
let n natMaybeZero
if n > USize.size then fail "exp too large"
return res.shiftl n
else
return res
pure <| JsonNumber.fromInt (sign * whole)
partial def arrayCore (anyCore : Parser Json) (acc : Array Json) : Parser (Array Json) := do
let hd anyCore
let acc' := acc.push hd
let c any
if c = ']' then
ws
return acc'
else if c = ',' then
ws
arrayCore anyCore acc'
@[inline]
def exponent (value : JsonNumber) : Parser JsonNumber := do
if ← isEof then
return value
else
fail "unexpected character in array"
partial def objectCore (anyCore : Parser Json) : Parser (RBNode String (fun _ => Json)) := do
lookahead (fun c => c = '"') "\""; skip; -- "
let k strCore ""; ws
lookahead (fun c => c = ':') ":"; skip; ws
let v anyCore
let c any
if c = '}' then
ws
return RBNode.singleton k v
else if c = ',' then
ws
let kvs objectCore anyCore
return kvs.insert compare k v
else
fail "unexpected character in object"
partial def anyCore : Parser Json := do
let c peek!
if c = '[' then
skip; ws
let c ← peek!
if c = ']' then
skip; ws
return Json.arr (Array.mkEmpty 0)
if c == 'e' || c == 'E' then
skip
let c ← peek!
if c == '-' then
skip
let n ← natMaybeZero
return value.shiftr n
else
if c = '+' then skip
let n ← natMaybeZero
if n > USize.size then fail "exp too large"
return value.shiftl n
else
let a arrayCore anyCore (Array.mkEmpty 4)
return Json.arr a
else if c = '{' then
skip; ws
let c peek!
if c = '}' then
skip; ws
return Json.obj (RBNode.leaf)
else
let kvs objectCore anyCore
return Json.obj kvs
else if c = '\"' then
skip
let s ← strCore ""
ws
return Json.str s
else if c = 'f' then
skipString "false"; ws
return Json.bool false
else if c = 't' then
skipString "true"; ws
return Json.bool true
else if c = 'n' then
skipString "null"; ws
return Json.null
else if c = '-' ('0' ≤ c ∧ c ≤ '9') then
let n ← num
ws
return Json.num n
else
fail "unexpected input"
return value
def num : Parser JsonNumber := do
let res : JsonNumber ← numWithDecimals
exponent res
mutual
partial def arrayCore (acc : Array Json) : Parser (Array Json) := do
let hd ← anyCore
let acc' := acc.push hd
let c ← any
if c == ']' then
ws
return acc'
else if c == ',' then
ws
arrayCore acc'
else
fail "unexpected character in array"
partial def objectCore (kvs : RBNode String (fun _ => Json)) : Parser (RBNode String (fun _ => Json)) := do
lookahead (fun c => c == '"') "\""; skip;
let k str; ws
lookahead (fun c => c == ':') ":"; skip; ws
let v anyCore
let c any
if c == '}' then
ws
return kvs.insert compare k v
else if c == ',' then
ws
objectCore (kvs.insert compare k v)
else
fail "unexpected character in object"
partial def anyCore : Parser Json := do
let c peek!
if c == '[' then
skip; ws
let c peek!
if c == ']' then
skip; ws
return Json.arr (Array.mkEmpty 0)
else
let a arrayCore (Array.mkEmpty 4)
return Json.arr a
else if c == '{' then
skip; ws
let c peek!
if c == '}' then
skip; ws
return Json.obj (RBNode.leaf)
else
let kvs objectCore RBNode.leaf
return Json.obj kvs
else if c == '\"' then
skip
let s ← str
ws
return Json.str s
else if c == 'f' then
skipString "false"; ws
return Json.bool false
else if c == 't' then
skipString "true"; ws
return Json.bool true
else if c == 'n' then
skipString "null"; ws
return Json.null
else if c == '-' || ('0' <= c && c <= '9') then
let n ← num
ws
return Json.num n
else
fail "unexpected input"
end
def any : Parser Json := do
ws
@@ -215,9 +252,7 @@ end Json.Parser
namespace Json
def parse (s : String) : Except String Lean.Json :=
match Json.Parser.any s.mkIterator with
| .success _ res => Except.ok res
| .error it err => Except.error s!"offset {repr it.i.byteIdx}: {err}"
Parser.run Json.Parser.any s
end Json

View File

@@ -6,6 +6,7 @@ Authors: Gabriel Ebner, Marc Huisinga, Wojciech Nawrocki
prelude
import Lean.Data.Format
import Lean.Data.Json.Basic
import Init.Data.List.Impl
namespace Lean
namespace Json

View File

@@ -22,27 +22,23 @@ reduce the size of the resulting JSON. -/
/--
Identifier of a reference.
-/
-- Names are represented by strings to avoid having to parse them to `Name`,
-- which is relatively expensive. Most uses of these names only need equality, anyways.
inductive RefIdent where
/-- Named identifier. These are used in all references that are globally available. -/
| const (moduleName : Name) (identName : Name) : RefIdent
| const (moduleName : String) (identName : String) : RefIdent
/-- Unnamed identifier. These are used for all local references. -/
| fvar (moduleName : Name) (id : FVarId) : RefIdent
| fvar (moduleName : String) (id : String) : RefIdent
deriving BEq, Hashable, Inhabited
namespace RefIdent
instance : ToJson FVarId where
toJson id := toJson id.name
instance : FromJson FVarId where
fromJson? s := return fromJson? s
/-- Shortened representation of `RefIdent` for more compact serialization. -/
inductive RefIdentJsonRepr
/-- Shortened representation of `RefIdent.const` for more compact serialization. -/
| c (m n : Name)
| c (m n : String)
/-- Shortened representation of `RefIdent.fvar` for more compact serialization. -/
| f (m : Name) (i : FVarId)
| f (m : String) (i : String)
deriving FromJson, ToJson
/-- Converts `id` to its compact serialization representation. -/
@@ -74,7 +70,7 @@ end RefIdent
/-- Information about the declaration surrounding a reference. -/
structure RefInfo.ParentDecl where
/-- Name of the declaration surrounding a reference. -/
name : Name
name : String
/-- Range of the declaration surrounding a reference. -/
range : Lsp.Range
/-- Selection range of the declaration surrounding a reference. -/
@@ -104,7 +100,7 @@ instance : ToJson RefInfo where
let rangeToList (r : Lsp.Range) : List Nat :=
[r.start.line, r.start.character, r.end.line, r.end.character]
let parentDeclToList (d : RefInfo.ParentDecl) : List Json :=
let name := d.name.toString |> toJson
let name := d.name |> toJson
let range := rangeToList d.range |>.map toJson
let selectionRange := rangeToList d.selectionRange |>.map toJson
[name] ++ range ++ selectionRange
@@ -118,34 +114,42 @@ instance : ToJson RefInfo where
]
instance : FromJson RefInfo where
-- This implementation is optimized to prevent redundant intermediate allocations.
fromJson? j := do
let toRange : List Nat Except String Lsp.Range
| [sLine, sChar, eLine, eChar] => pure sLine, sChar, eLine, eChar
| l => throw s!"Expected list of length 4, not {l.length}"
let toParentDecl (a : Array Json) : Except String RefInfo.ParentDecl := do
let name := String.toName <| fromJson? a[0]!
let range a[1:5].toArray.toList |>.mapM fromJson?
let range toRange range
let selectionRange a[5:].toArray.toList |>.mapM fromJson?
let selectionRange toRange selectionRange
let toRange (a : Array Json) (i : Nat) : Except String Lsp.Range :=
if h : a.size < i + 4 then
throw s!"Expected list of length 4, not {a.size}"
else
return {
start := {
line := fromJson? a[i]
character := fromJson? a[i+1]
}
«end» := {
line := fromJson? a[i+2]
character := fromJson? a[i+3]
}
}
let toParentDecl (a : Array Json) (i : Nat) : Except String RefInfo.ParentDecl := do
let name fromJson? a[i]!
let range toRange a (i + 1)
let selectionRange toRange a (i + 5)
return name, range, selectionRange
let toLocation (l : List Json) : Except String RefInfo.Location := do
let l := l.toArray
if l.size != 4 && l.size != 13 then
let toLocation (a : Array Json) : Except String RefInfo.Location := do
if a.size != 4 && a.size != 13 then
.error "Expected list of length 4 or 13, not {l.size}"
let range l[:4].toArray.toList |>.mapM fromJson?
let range toRange range
if l.size == 13 then
let parentDecl toParentDecl l[4:].toArray
let range toRange a 0
if a.size == 13 then
let parentDecl toParentDecl a 4
return range, parentDecl
else
return range, none
let definition? j.getObjValAs? (Option $ List Json) "definition"
let definition? j.getObjValAs? (Option $ Array Json) "definition"
let definition? match definition? with
| none => pure none
| some list => some <$> toLocation list
let usages j.getObjValAs? (Array $ List Json) "usages"
| some array => some <$> toLocation array
let usages j.getObjValAs? (Array $ Array Json) "usages"
let usages usages.mapM toLocation
pure { definition?, usages }

View File

@@ -13,11 +13,11 @@ open Lean
namespace Lean
namespace Xml
namespace Parser
open Std.Internal.Parsec
open Std.Internal.Parsec.String
namespace Parser
abbrev LeanChar := Char
/-- consume a newline character sequence pretending, that we read '\n'. As per spec:
@@ -482,8 +482,6 @@ def document : Parser Element := prolog *> element <* many Misc <* eof
end Parser
def parse (s : String) : Except String Element :=
match Xml.Parser.document s.mkIterator with
| .success _ res => Except.ok res
| .error it err => Except.error s!"offset {it.i.byteIdx.repr}: {err}\n{(it.prevn 10).extract it}"
Parser.run Xml.Parser.document s
end Xml

View File

@@ -17,7 +17,7 @@ register_builtin_option autoImplicit : Bool := {
register_builtin_option relaxedAutoImplicit : Bool := {
defValue := true
descr := "When \"relaxed\" mode is enabled, any atomic nonempty identifier is eligible for auto bound implicit locals (see optin `autoBoundImplicitLocal`."
descr := "When \"relaxed\" mode is enabled, any atomic nonempty identifier is eligible for auto bound implicit locals (see option `autoImplicit`)."
}

View File

@@ -176,7 +176,8 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
let mut binderIds := binderIds
let mut binderIdsIniSize := binderIds.size
let mut modifiedVarDecls := false
for varDecl in varDecls do
-- Go through declarations in reverse to respect shadowing
for varDecl in varDecls.reverse do
let (ids, ty?, explicit') match varDecl with
| `(bracketedBinderF|($ids* $[: $ty?]? $(annot?)?)) =>
if annot?.isSome then
@@ -208,7 +209,7 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
`(bracketedBinderF| ($id $[: $ty?]?))
else
`(bracketedBinderF| {$id $[: $ty?]?})
for id in ids do
for id in ids.reverse do
if let some idx := binderIds.findIdx? fun binderId => binderId.raw.isIdent && binderId.raw.getId == id.raw.getId then
binderIds := binderIds.eraseIdx idx
modifiedVarDecls := true
@@ -216,7 +217,7 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
else
varDeclsNew := varDeclsNew.push ( mkBinder id explicit')
if modifiedVarDecls then
modifyScope fun scope => { scope with varDecls := varDeclsNew }
modifyScope fun scope => { scope with varDecls := varDeclsNew.reverse }
if binderIds.size != binderIdsIniSize then
binderIds.mapM fun binderId =>
if explicit then
@@ -228,15 +229,14 @@ private def replaceBinderAnnotation (binder : TSyntax ``Parser.Term.bracketedBin
@[builtin_command_elab «variable»] def elabVariable : CommandElab
| `(variable $binders*) => do
let binders binders.concatMapM replaceBinderAnnotation
-- Try to elaborate `binders` for sanity checking
runTermElabM fun _ => Term.withSynthesize <| Term.withAutoBoundImplicit <|
Term.elabBinders binders fun _ => pure ()
-- Remark: if we want to produce error messages when variables shadow existing ones, here is the place to do it.
for binder in binders do
let binders replaceBinderAnnotation binder
-- Remark: if we want to produce error messages when variables shadow existing ones, here is the place to do it.
for binder in binders do
let varUIds getBracketedBinderIds binder |>.mapM (withFreshMacroScope MonadQuotation.addMacroScope)
modifyScope fun scope => { scope with varDecls := scope.varDecls.push binder, varUIds := scope.varUIds ++ varUIds }
let varUIds ( getBracketedBinderIds binder) |>.mapM (withFreshMacroScope MonadQuotation.addMacroScope)
modifyScope fun scope => { scope with varDecls := scope.varDecls.push binder, varUIds := scope.varUIds ++ varUIds }
| _ => throwUnsupportedSyntax
open Meta
@@ -505,7 +505,7 @@ def elabRunMeta : CommandElab := fun stx =>
@[builtin_command_elab Lean.Parser.Command.include] def elabInclude : CommandElab
| `(Lean.Parser.Command.include| include $ids*) => do
let sc getScope
let vars := sc.varDecls.concatMap getBracketedBinderIds
let vars sc.varDecls.concatMapM getBracketedBinderIds
let mut uids := #[]
for id in ids do
if let some idx := vars.findIdx? (· == id.getId) then

View File

@@ -557,19 +557,21 @@ private def mkMetaContext : Meta.Context := {
open Lean.Parser.Term in
/-- Return identifier names in the given bracketed binder. -/
def getBracketedBinderIds : Syntax Array Name
| `(bracketedBinderF|($ids* $[: $ty?]? $(_annot?)?)) => ids.map Syntax.getId
| `(bracketedBinderF|{$ids* $[: $ty?]?}) => ids.map Syntax.getId
| `(bracketedBinderF|[$id : $_]) => #[id.getId]
| `(bracketedBinderF|[$_]) => #[Name.anonymous]
| _ => #[]
def getBracketedBinderIds : Syntax CommandElabM (Array Name)
| `(bracketedBinderF|($ids* $[: $ty?]? $(_annot?)?)) => return ids.map Syntax.getId
| `(bracketedBinderF|{$ids* $[: $ty?]?}) => return ids.map Syntax.getId
| `(bracketedBinderF|$ids* : $_) => return ids.map Syntax.getId
| `(bracketedBinderF|[$id : $_]) => return #[id.getId]
| `(bracketedBinderF|[$_]) => return #[Name.anonymous]
| _ => throwUnsupportedSyntax
private def mkTermContext (ctx : Context) (s : State) : Term.Context := Id.run do
private def mkTermContext (ctx : Context) (s : State) : CommandElabM Term.Context := do
let scope := s.scopes.head!
let mut sectionVars := {}
for id in scope.varDecls.concatMap getBracketedBinderIds, uid in scope.varUIds do
for id in ( scope.varDecls.concatMapM getBracketedBinderIds), uid in scope.varUIds do
sectionVars := sectionVars.insert id uid
{ macroStack := ctx.macroStack
return {
macroStack := ctx.macroStack
sectionVars := sectionVars
isNoncomputableSection := scope.isNoncomputable
tacticCache? := ctx.tacticCache? }
@@ -609,7 +611,7 @@ def liftTermElabM (x : TermElabM α) : CommandElabM α := do
-- make sure `observing` below also catches runtime exceptions (like we do by default in
-- `CommandElabM`)
let _ := MonadAlwaysExcept.except (m := TermElabM)
let x : MetaM _ := (observing (try x finally Meta.reportDiag)).run (mkTermContext ctx s) { levelNames := scope.levelNames }
let x : MetaM _ := (observing (try x finally Meta.reportDiag)).run ( mkTermContext ctx s) { levelNames := scope.levelNames }
let x : CoreM _ := x.run mkMetaContext {}
let ((ea, _), _) runCore x
MonadExcept.ofExcept ea
@@ -706,7 +708,7 @@ def expandDeclId (declId : Syntax) (modifiers : Modifiers) : CommandElabM Expand
let currNamespace getCurrNamespace
let currLevelNames getLevelNames
let r Elab.expandDeclId currNamespace currLevelNames declId modifiers
for id in ( getScope).varDecls.concatMap getBracketedBinderIds do
for id in ( ( getScope).varDecls.concatMapM getBracketedBinderIds) do
if id == r.shortName then
throwError "invalid declaration name '{r.shortName}', there is a section variable with the same name"
return r

View File

@@ -163,7 +163,7 @@ def MessageOrdering.apply (mode : MessageOrdering) (msgs : List String) : List S
let diff := Diff.diff (expected.split (· == '\n')).toArray (res.split (· == '\n')).toArray
Diff.linesToString diff
else res
logErrorAt tk m!"❌ Docstring on `#guard_msgs` does not match generated message:\n\n{feedback}"
logErrorAt tk m!" Docstring on `#guard_msgs` does not match generated message:\n\n{feedback}"
pushInfoLeaf (.ofCustomInfo { stx := getRef, value := Dynamic.mk (GuardMsgFailure.mk res) })
| _ => throwUnsupportedSyntax

View File

@@ -383,6 +383,11 @@ register_builtin_option deprecated.oldSectionVars : Bool := {
descr := "re-enable deprecated behavior of including exactly the section variables used in a declaration"
}
register_builtin_option linter.unusedSectionVars : Bool := {
defValue := true
descr := "enable the 'unused section variables in theorem body' linter"
}
private def elabFunValues (headers : Array DefViewElabHeader) (vars : Array Expr) (sc : Command.Scope) : TermElabM (Array Expr) :=
headers.mapM fun header => do
let mut reusableResult? := none
@@ -411,18 +416,25 @@ private def elabFunValues (headers : Array DefViewElabHeader) (vars : Array Expr
-- leads to more section variables being included than necessary
let val instantiateMVarsProfiling val
let val mkLambdaFVars xs val
unless header.type.hasSorry || val.hasSorry do
for var in vars do
unless header.type.containsFVar var.fvarId! ||
val.containsFVar var.fvarId! ||
( vars.anyM (fun v => return ( v.fvarId!.getType).containsFVar var.fvarId!)) do
let varDecl var.fvarId!.getDecl
let var := if varDecl.userName.hasMacroScopes && varDecl.binderInfo.isInstImplicit then
m!"[{varDecl.type}]".group
if linter.unusedSectionVars.get ( getOptions) && !header.type.hasSorry && !val.hasSorry then
let unusedVars vars.filterMapM fun var => do
let varDecl var.fvarId!.getDecl
return if sc.includedVars.contains varDecl.userName ||
header.type.containsFVar var.fvarId! || val.containsFVar var.fvarId! ||
( vars.anyM (fun v => return ( v.fvarId!.getType).containsFVar var.fvarId!)) then
none
else
if varDecl.userName.hasMacroScopes && varDecl.binderInfo.isInstImplicit then
some m!"[{varDecl.type}]"
else
var
logWarningAt header.ref m!"included section variable '{var}' is not used in \
'{header.declName}', consider excluding it"
some m!"{var}"
if unusedVars.size > 0 then
Linter.logLint linter.unusedSectionVars header.ref
m!"automatically included section variable(s) unused in theorem '{header.declName}':\
\n {MessageData.joinSep unusedVars.toList "\n "}\
\nconsider restructuring your `variable` declarations so that the variables are not \
in scope or explicitly omit them:\
\n omit {MessageData.joinSep unusedVars.toList " "} in theorem ..."
return val
if let some snap := header.bodySnap? then
snap.new.resolve <| some {

View File

@@ -10,3 +10,5 @@ import Lean.Elab.PreDefinition.Main
import Lean.Elab.PreDefinition.MkInhabitant
import Lean.Elab.PreDefinition.WF
import Lean.Elab.PreDefinition.Eqns
import Lean.Elab.PreDefinition.Nonrec.Eqns
import Lean.Elab.PreDefinition.EqUnfold

View File

@@ -12,6 +12,7 @@ import Lean.Util.NumApps
import Lean.PrettyPrinter
import Lean.Meta.AbstractNestedProofs
import Lean.Meta.ForEachExpr
import Lean.Meta.Eqns
import Lean.Elab.RecAppSyntax
import Lean.Elab.DefView
import Lean.Elab.PreDefinition.TerminationHint
@@ -153,6 +154,7 @@ private def addNonRecAux (preDef : PreDefinition) (compile : Bool) (all : List N
if compile && shouldGenCodeFor preDef then
discard <| compileDecl decl
if applyAttrAfterCompilation then
generateEagerEqns preDef.declName
applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation
def addAndCompileNonRec (preDef : PreDefinition) (all : List Name := [preDef.declName]) : TermElabM Unit := do

View File

@@ -0,0 +1,62 @@
/-
Copyright (c) 2024 Lean FRO. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joachim Breitner
-/
prelude
import Lean.Meta.Eqns
import Lean.Meta.Tactic.Util
import Lean.Meta.Tactic.Rfl
import Lean.Meta.Tactic.Intro
import Lean.Meta.Tactic.Apply
namespace Lean.Meta
/-- Try to close goal using `rfl` with smart unfolding turned off. -/
def tryURefl (mvarId : MVarId) : MetaM Bool :=
withOptions (smartUnfolding.set · false) do
try mvarId.refl; return true catch _ => return false
/--
Returns the "const unfold" theorem (`f.eq_unfold`) for the given declaration.
This is not extensible, and always builds on the unfold theorem (`f.eq_def`).
-/
def getConstUnfoldEqnFor? (declName : Name) : MetaM (Option Name) := do
let some unfoldEqnName getUnfoldEqnFor? (nonRec := true) declName | return none
let info getConstInfo unfoldEqnName
let type forallTelescope info.type fun xs eq => do
let some (_, lhs, rhs) := eq.eq? | throwError "Unexpected unfold theorem type {info.type}"
unless lhs.getAppFn.isConstOf declName do
throwError "Unexpected unfold theorem type {info.type}"
unless lhs.getAppArgs == xs do
throwError "Unexpected unfold theorem type {info.type}"
let type mkEq lhs.getAppFn ( mkLambdaFVars xs rhs)
return type
let value withNewMCtxDepth do
let main mkFreshExprSyntheticOpaqueMVar type
if ( tryURefl main.mvarId!) then -- try to make a rfl lemma if possible
instantiateMVars main
else forallTelescope info.type fun xs _eq => do
let mut proof mkConstWithLevelParams unfoldEqnName
proof := mkAppN proof xs
for x in xs.reverse do
proof mkLambdaFVars #[x] proof
proof mkAppM ``funext #[proof]
return proof
let name := .str declName eqUnfoldThmSuffix
addDecl <| Declaration.thmDecl {
name, type, value
levelParams := info.levelParams
}
return some name
builtin_initialize
registerReservedNameAction fun name => do
let .str p s := name | return false
unless ( getEnv).isSafeDefinition p do return false
if s == eqUnfoldThmSuffix then
return ( MetaM.run' <| getConstUnfoldEqnFor? p).isSome
return false
end Lean.Meta

View File

@@ -43,15 +43,6 @@ def expandRHS? (mvarId : MVarId) : MetaM (Option MVarId) := do
let (true, rhs') := expand false rhs | return none
return some ( mvarId.replaceTargetDefEq ( mkEq lhs rhs'))
def funext? (mvarId : MVarId) : MetaM (Option MVarId) := do
let target mvarId.getType'
let some (_, _, rhs) := target.eq? | return none
unless rhs.isLambda do return none
commitWhenSome? do
let [mvarId] mvarId.apply ( mkConstWithFreshMVarLevels ``funext) | return none
let (_, mvarId) mvarId.intro1
return some mvarId
def simpMatch? (mvarId : MVarId) : MetaM (Option MVarId) := do
let mvarId' Split.simpMatchTarget mvarId
if mvarId != mvarId' then return some mvarId' else return none
@@ -60,7 +51,8 @@ def simpIf? (mvarId : MVarId) : MetaM (Option MVarId) := do
let mvarId' simpIfTarget mvarId (useDecide := true)
if mvarId != mvarId' then return some mvarId' else return none
private def findMatchToSplit? (env : Environment) (e : Expr) (declNames : Array Name) (exceptionSet : ExprSet) : Option Expr :=
private def findMatchToSplit? (deepRecursiveSplit : Bool) (env : Environment) (e : Expr)
(declNames : Array Name) (exceptionSet : ExprSet) : Option Expr :=
e.findExt? fun e => Id.run do
if e.hasLooseBVars || exceptionSet.contains e then
return Expr.FindStep.visit
@@ -75,7 +67,14 @@ private def findMatchToSplit? (env : Environment) (e : Expr) (declNames : Array
break
unless hasFVarDiscr do
return Expr.FindStep.visit
-- At least one alternative must contain a `declNames` application with loose bound variables.
-- For non-recursive functions (`declNames` empty), we split here
if declNames.isEmpty then
return Expr.FindStep.found
-- For recursive functions, the “new” behavior is to likewise split
if deepRecursiveSplit then
return Expr.FindStep.found
-- Else, the “old” behavior is split only when at least one alternative contains a `declNames`
-- application with loose bound variables.
for i in [info.getFirstAltPos : info.getFirstAltPos + info.numAlts] do
let alt := args[i]!
if Option.isSome <| alt.find? fun e => declNames.any e.isAppOf && e.hasLooseBVars then
@@ -92,7 +91,8 @@ private def findMatchToSplit? (env : Environment) (e : Expr) (declNames : Array
partial def splitMatch? (mvarId : MVarId) (declNames : Array Name) : MetaM (Option (List MVarId)) := commitWhenSome? do
let target mvarId.getType'
let rec go (badCases : ExprSet) : MetaM (Option (List MVarId)) := do
if let some e := findMatchToSplit? ( getEnv) target declNames badCases then
if let some e := findMatchToSplit? (eqns.deepRecursiveSplit.get ( getOptions)) ( getEnv)
target declNames badCases then
try
Meta.Split.splitMatch mvarId e
catch _ =>
@@ -102,9 +102,6 @@ partial def splitMatch? (mvarId : MVarId) (declNames : Array Name) : MetaM (Opti
return none
go {}
structure Context where
declNames : Array Name
private def lhsDependsOn (type : Expr) (fvarId : FVarId) : MetaM Bool :=
forallTelescope type fun _ type => do
if let some (_, lhs, _) matchEq? type then
@@ -229,20 +226,15 @@ private def shouldUseSimpMatch (e : Expr) : MetaM Bool := do
return ( (find e).run) matches .error _
partial def mkEqnTypes (declNames : Array Name) (mvarId : MVarId) : MetaM (Array Expr) := do
let (_, eqnTypes) go mvarId |>.run { declNames } |>.run #[]
let (_, eqnTypes) go mvarId |>.run #[]
return eqnTypes
where
go (mvarId : MVarId) : ReaderT Context (StateRefT (Array Expr) MetaM) Unit := do
go (mvarId : MVarId) : StateRefT (Array Expr) MetaM Unit := do
trace[Elab.definition.eqns] "mkEqnTypes step\n{MessageData.ofGoal mvarId}"
if let some mvarId expandRHS? mvarId then
return ( go mvarId)
-- The following `funext?` was producing an overapplied `lhs`. Possible refinement: only do it
-- if we want to apply `splitMatch` on the body of the lambda
/- if let some mvarId ← funext? mvarId then
return (← go mvarId) -/
if ( shouldUseSimpMatch ( mvarId.getType')) then
if let some mvarId simpMatch? mvarId then
return ( go mvarId)
@@ -342,9 +334,6 @@ partial def mkUnfoldProof (declName : Name) (mvarId : MVarId) : MetaM Unit := do
let rec go (mvarId : MVarId) : MetaM Unit := do
if ( tryEqns mvarId) then
return ()
-- Remark: we removed funext? from `mkEqnTypes`
-- else if let some mvarId ← funext? mvarId then
-- go mvarId
if ( shouldUseSimpMatch ( mvarId.getType')) then
if let some mvarId simpMatch? mvarId then

View File

@@ -0,0 +1,108 @@
/-
Copyright (c) 2022 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Joachim Breitner
-/
prelude
import Lean.Meta.Tactic.Rewrite
import Lean.Meta.Tactic.Split
import Lean.Elab.PreDefinition.Basic
import Lean.Elab.PreDefinition.Eqns
import Lean.Meta.ArgsPacker.Basic
import Init.Data.Array.Basic
namespace Lean.Elab.Nonrec
open Meta
open Eqns
/--
Simple, coarse-grained equation theorem for nonrecursive definitions.
-/
private def mkSimpleEqThm (declName : Name) (suffix := Name.mkSimple unfoldThmSuffix) : MetaM (Option Name) := do
if let some (.defnInfo info) := ( getEnv).find? declName then
lambdaTelescope (cleanupAnnotations := true) info.value fun xs body => do
let lhs := mkAppN (mkConst info.name <| info.levelParams.map mkLevelParam) xs
let type mkForallFVars xs ( mkEq lhs body)
let value mkLambdaFVars xs ( mkEqRefl lhs)
let name := declName ++ suffix
addDecl <| Declaration.thmDecl {
name, type, value
levelParams := info.levelParams
}
return some name
else
return none
private partial def mkProof (declName : Name) (type : Expr) : MetaM Expr := do
trace[Elab.definition.eqns] "proving: {type}"
withNewMCtxDepth do
let main mkFreshExprSyntheticOpaqueMVar type
let (_, mvarId) main.mvarId!.intros
let rec go (mvarId : MVarId) : MetaM Unit := do
trace[Elab.definition.eqns] "step\n{MessageData.ofGoal mvarId}"
if withAtLeastTransparency .all (tryURefl mvarId) then
return ()
else if ( tryContradiction mvarId) then
return ()
else if let some mvarId simpMatch? mvarId then
go mvarId
else if let some mvarId simpIf? mvarId then
go mvarId
else if let some mvarId whnfReducibleLHS? mvarId then
go mvarId
else match ( simpTargetStar mvarId { config.dsimp := false } (simprocs := {})).1 with
| TacticResultCNM.closed => return ()
| TacticResultCNM.modified mvarId => go mvarId
| TacticResultCNM.noChange =>
if let some mvarIds casesOnStuckLHS? mvarId then
mvarIds.forM go
else if let some mvarIds splitTarget? mvarId then
mvarIds.forM go
else
throwError "failed to generate equational theorem for '{declName}'\n{MessageData.ofGoal mvarId}"
-- Try rfl before deltaLHS to avoid `id` checkpoints in the proof, which would make
-- the lemma ineligible for dsimp
unless withAtLeastTransparency .all (tryURefl mvarId) do
go ( deltaLHS mvarId)
instantiateMVars main
def mkEqns (declName : Name) (info : DefinitionVal) : MetaM (Array Name) :=
withOptions (tactic.hygienic.set · false) do
let baseName := declName
let eqnTypes withNewMCtxDepth <| lambdaTelescope (cleanupAnnotations := true) info.value fun xs body => do
let us := info.levelParams.map mkLevelParam
let target mkEq (mkAppN (Lean.mkConst declName us) xs) body
let goal mkFreshExprSyntheticOpaqueMVar target
withReducible do
mkEqnTypes #[] goal.mvarId!
let mut thmNames := #[]
for i in [: eqnTypes.size] do
let type := eqnTypes[i]!
trace[Elab.definition.eqns] "eqnType[{i}]: {eqnTypes[i]!}"
let name := (Name.str baseName eqnThmSuffixBase).appendIndexAfter (i+1)
thmNames := thmNames.push name
let value mkProof declName type
let (type, value) removeUnusedEqnHypotheses type value
addDecl <| Declaration.thmDecl {
name, type, value
levelParams := info.levelParams
}
return thmNames
def getEqnsFor? (declName : Name) : MetaM (Option (Array Name)) := do
if ( isRecursiveDefinition declName) then
return none
if let some (.defnInfo info) := ( getEnv).find? declName then
if eqns.nonrecursive.get ( getOptions) then
mkEqns declName info
else
let o mkSimpleEqThm declName
return o.map (#[·])
else
return none
builtin_initialize
registerGetEqnsFn getEqnsFor?
end Lean.Elab.Nonrec

View File

@@ -193,6 +193,7 @@ def structuralRecursion (preDefs : Array PreDefinition) (termArg?s : Array (Opti
registerEqnsInfo preDef (preDefs.map (·.declName)) recArgPos numFixed
addSmartUnfoldingDef preDef recArgPos
markAsRecursive preDef.declName
generateEagerEqns preDef.declName
applyAttributesOf preDefsNonRec AttributeApplicationTime.afterCompilation

View File

@@ -145,6 +145,7 @@ def wfRecursion (preDefs : Array PreDefinition) (termArg?s : Array (Option Termi
registerEqnsInfo preDefs preDefNonRec.declName fixedPrefixSize argsPacker
for preDef in preDefs do
markAsRecursive preDef.declName
generateEagerEqns preDef.declName
applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation
-- Unless the user asks for something else, mark the definition as irreducible
unless preDef.modifiers.attrs.any fun a =>

View File

@@ -134,7 +134,7 @@ private def printAxiomsOf (constName : Name) : CommandElabM Unit := do
| _ => throwUnsupportedSyntax
private def printEqnsOf (constName : Name) : CommandElabM Unit := do
let some eqns liftTermElabM <| Meta.getEqnsFor? constName (nonRec := true) |
let some eqns liftTermElabM <| Meta.getEqnsFor? constName |
logInfo m!"'{constName}' does not have equations"
let mut m := m!"equations:"
for eq in eqns do

View File

@@ -42,3 +42,4 @@ import Lean.Elab.Tactic.Rfl
import Lean.Elab.Tactic.Rewrites
import Lean.Elab.Tactic.DiscrTreeKey
import Lean.Elab.Tactic.BVDecide
import Lean.Elab.Tactic.BoolToPropSimps

View File

@@ -6,9 +6,73 @@ Authors: Henrik Böving
prelude
import Lean.Elab.Tactic.BVDecide.LRAT
import Lean.Elab.Tactic.BVDecide.External
import Lean.Elab.Tactic.BVDecide.Frontend
/-!
This directory implements the `bv_decide` tactic as a verified bitblaster with subterm sharing.
It makes use of proof by reflection and `ofReduceBool`, thus adding the Lean compiler to the trusted
code base.
This directory offers three different SAT tactics for proving goals involving `BitVec` and `Bool`:
1. `bv_decide` takes the goal, hands it over to a SAT solver and verifies the generated LRAT
UNSAT proof to prove the goal.
2. `bv_check file.lrat` can prove the same things as `bv_decide`. However instead of
dynamically handing the goal to a SAT solver to obtain an LRAT proof, the LRAT proof is read from
`file.lrat`. This allows users that do not have a SAT solver installed to verify proofs.
3. `bv_decide?` offers a code action to turn a `bv_decide` invocation automatically into a
`bv_check` one.
There are also some options to influence the behavior of `bv_decide` and friends:
- `sat.solver`: the name of the SAT solver used by `bv_decide`. It goes through 3 steps to determine
which solver to use:
1. If sat.solver is set to something != "" it will use that.
2. If sat.solver is set to "" it will check if there is a cadical binary next to the executing
program. Usually that program is going to be `lean` itself and we do ship a `cadical` next to it.
3. If that does not succeed try to call `cadical` from PATH.
- `sat.timeout`: The timeout for waiting for the SAT solver in seconds, default 10.
- `sat.trimProofs`: Whether to run the trimming algorithm on LRAT proofs, default true.
- `sat.binaryProofs`: Whether to use the binary LRAT proof format, default true.
- `trace.Meta.Tactic.bv` and `trace.Meta.Tactic.sat` for inspecting the inner workings of `bv_decide`.
- `debug.skipKernelTC`: may be set to true to disable actually checking the LRAT proof.
`bv_decide` will still run bitblasting + SAT solving so this option essentially trusts the SAT
solver.
## Architecture
`bv_decide` roughly runs through the following steps:
1. Apply `false_or_by_contra` to start a proof by contradiction.
2. Apply the `bv_normalize` and `seval` simp set to all hypotheses. This has two effects:
1. It applies a subset of the rewrite rules from [Bitwuzla](https://github.com/bitwuzla/bitwuzla)
for simplification of the expressions.
2. It turns all hypotheses that might be of interest for the remainder of the tactic into the form
`x = true` where `x` is a mixture of `Bool` and fixed width `BitVec` expressions.
3. Use proof by reflection to reduce the proof to showing that an SMTLIB-syntax-like value that
represents the conjunction of all relevant assumptions is UNSAT.
4. Use a verified bitblasting algorithm to turn that expression into an AIG.
The bitblasting algorithms are collected from various other bitblasters, including Bitwuzla and
Z3 and verified using Lean's `BitVec` theory.
5. Turn the AIG into a CNF.
6. Run CaDiCal on the CNF to obtain an LRAT proof that the CNF is UNSAT. If CaDiCal returns SAT
instead the tactic aborts here and presents a counterexample.
7. Use an LRAT checker with a soundness proof in Lean to show that the LRAT proof is correct.
8. Chain all the proofs so far to demonstrate that the original goal holds.
## Axioms
`bv_decide` makes use of proof by reflection and `ofReduceBool`, thus adding the Lean compiler to
the trusted code base.
## Adding a new primitive
`bv_decide` knows two kinds of primitives:
1. The ones that can be reduced to already existing ones.
2. The ones that cannot.
For the first kind the steps to adding them are very simple, go to `Std.Tactic.BVDecide.Normalize`
and add the reduction lemma into the `bv_normalize` simp set. Don't forget to add a test!
For the second kind more steps are involved:
1. Add a new constructor to `BVExpr`/`BVPred`
2. Add a bitblasting algorithm for the new constructor to `Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl`.
3. Verify that algorithm in `Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas`.
4. Integrate it with either the expression or predicate bitblaster and use the proof above to verify it.
5. Add simplification lemmas for the primitive to `bv_normalize` in `Std.Tactic.BVDecide.Normalize`.
If there are mutliple ways to write the primitive (e.g. with TC based notation and without) you
should normalize for one notation here.
6. Add the reflection code to `Lean.Elab.Tactic.BVDecide.Frontend.BVDecide`
7. Add a test!
-/

View File

@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.LRAT.Parser
import Std.Tactic.BVDecide.LRAT.Parser
import Lean.CoreM
import Std.Internal.Parsec
@@ -17,6 +17,8 @@ namespace Lean.Elab.Tactic.BVDecide
namespace External
open Std.Tactic.BVDecide
/--
The result of calling a SAT solver.
-/
@@ -34,6 +36,7 @@ namespace ModelParser
open Std.Internal.Parsec
open Std.Internal.Parsec.ByteArray
open LRAT.Parser.Text (skipNewline)
def parsePartialAssignment : Parser (Bool × (Array (Bool × Nat))) := do
skipByteChar 'v'
@@ -43,7 +46,7 @@ def parsePartialAssignment : Parser (Bool × (Array (Bool × Nat))) := do
(skipString " 0")
(csuccess := fun _ => pure (true, idents))
(cerror := fun _ => do
skipByteChar '\n'
skipNewline
return (false, idents)
)
where
@@ -65,7 +68,8 @@ where
@[inline]
def parseHeader : Parser Unit := do
skipString "s SATISFIABLE\n"
skipString "s SATISFIABLE"
skipNewline
/--
Parse the witness format of a SAT solver. The rough grammar for this is:
@@ -81,41 +85,56 @@ end ModelParser
open Lean (CoreM)
inductive TimedOut (α : Type u) where
| success (x : α)
| timeout
/--
Run a process with `args` until it terminates or the cancellation token in `CoreM` tells us to abort.
Run a process with `args` until it terminates or the cancellation token in `CoreM` tells us to abort
or `timeout` seconds have passed.
-/
partial def runInterruptible (args : IO.Process.SpawnArgs) : CoreM IO.Process.Output := do
partial def runInterruptible (timeout : Nat) (args : IO.Process.SpawnArgs) :
CoreM (TimedOut IO.Process.Output) := do
let child IO.Process.spawn { args with stdout := .piped, stderr := .piped, stdin := .null }
let stdout IO.asTask child.stdout.readToEnd Task.Priority.dedicated
let stderr IO.asTask child.stderr.readToEnd Task.Priority.dedicated
if let some tk := ( read).cancelTk? then
go child stdout stderr tk
else
let stdout IO.ofExcept stdout.get
let stderr IO.ofExcept stderr.get
let exitCode child.wait
return { exitCode := exitCode, stdout := stdout, stderr := stderr }
go (timeout * 1000) child stdout stderr
where
go {cfg} (child : IO.Process.Child cfg) (stdout stderr : Task (Except IO.Error String))
(tk : IO.CancelToken) : CoreM IO.Process.Output := do
withInterruptCheck tk child.kill do
go {cfg} (budgetMs : Nat) (child : IO.Process.Child cfg) (stdout stderr : Task (Except IO.Error String)) :
CoreM (TimedOut IO.Process.Output) := do
let cleanup := killAndWait child
withTimeoutCheck budgetMs cleanup do
withInterruptCheck cleanup do
match child.tryWait with
| some exitCode =>
let stdout IO.ofExcept stdout.get
let stderr IO.ofExcept stderr.get
return { exitCode := exitCode, stdout := stdout, stderr := stderr }
return .success { exitCode := exitCode, stdout := stdout, stderr := stderr }
| none =>
IO.sleep 50
go child stdout stderr tk
let sleepMs : Nat := 50
IO.sleep sleepMs.toUInt32
go (budgetMs - sleepMs) child stdout stderr
withInterruptCheck {α : Type} (tk : IO.CancelToken) (interrupted : CoreM Unit) (x : CoreM α) :
CoreM α := do
if tk.isSet then
interrupted
throw <| .internal Core.interruptExceptionId
killAndWait {cfg} (child : IO.Process.Child cfg) : IO Unit := do
child.kill
discard child.wait
withTimeoutCheck {α : Type} (budgetMs : Nat) (cleanup : CoreM Unit) (x : CoreM (TimedOut α)) :
CoreM (TimedOut α) := do
if budgetMs == 0 then
cleanup
return .timeout
else
x
withInterruptCheck {α : Type} (cleanup : CoreM Unit) (x : CoreM α) :
CoreM α := do
if let some tk := ( read).cancelTk? then
if tk.isSet then
cleanup
throw <| .internal Core.interruptExceptionId
x
/--
Call the SAT solver in `solverPath` with `problemPath` as CNF input and ask it to output an LRAT
UNSAT proof (binary or non-binary depending on `binaryProofs`) into `proofOutput`. To avoid runaway
@@ -123,40 +142,50 @@ solvers the solver is run with `timeout` in seconds as a maximum time limit to s
Note: This function currently assume that the solver has the same CLI as CaDiCal.
-/
def satQuery (solverPath : String) (problemPath : System.FilePath) (proofOutput : System.FilePath)
(timeout : Nat := 10) (binaryProofs : Bool := true) :
def satQuery (solverPath : System.FilePath) (problemPath : System.FilePath) (proofOutput : System.FilePath)
(timeout : Nat) (binaryProofs : Bool) :
CoreM SolverResult := do
let cmd := solverPath
let args := #[
let cmd := solverPath.toString
let mut args := #[
problemPath.toString,
proofOutput.toString,
"-t",
s!"{timeout}",
"--lrat",
s!"--binary={binaryProofs}",
"--quiet",
"--unsat" -- This sets the magic parameters of cadical to optimize for UNSAT search.
/-
This sets the magic parameters of cadical to optimize for UNSAT search.
Given the fact that we are mostly interested in proving things and expect user goals to be
provable this is a fine value to set
-/
"--unsat",
/-
Bitwuzla sets this option and it does improve performance practically:
https://github.com/bitwuzla/bitwuzla/blob/0e81e616af4d4421729884f01928b194c3536c76/src/sat/cadical.cpp#L34
-/
"--shrink=0"
]
let out runInterruptible { cmd, args, stdin := .piped, stdout := .piped, stderr := .null }
if out.exitCode == 255 then
throwError s!"Failed to execute external prover:\n{out.stderr}"
else
let stdout := out.stdout
if stdout.startsWith "s UNSATISFIABLE" then
return .unsat
else if stdout.startsWith "s SATISFIABLE" then
match ModelParser.parse.run stdout.toUTF8 with
| .ok assignment =>
return .sat assignment
| .error err =>
throwError s!"Error {err} while parsing:\n{stdout}"
else if stdout.startsWith "c UNKNOWN" then
let mut err := "The SAT solver timed out while solving the problem."
err := err ++ "\nConsider increasing the timeout with `set_option sat.timeout <sec>`"
throwError err
-- We implement timeouting ourselves because cadicals -t option is not available on Windows.
let out? runInterruptible timeout { cmd, args, stdin := .piped, stdout := .piped, stderr := .null }
match out? with
| .timeout =>
let mut err := "The SAT solver timed out while solving the problem."
err := err ++ "\nConsider increasing the timeout with `set_option sat.timeout <sec>`"
throwError err
| .success { exitCode := exitCode, stdout := stdout, stderr := stderr} =>
if exitCode == 255 then
throwError s!"Failed to execute external prover:\n{stderr}"
else
throwError s!"The external prover produced unexpected output:\n{stdout}"
if stdout.startsWith "s UNSATISFIABLE" then
return .unsat
else if stdout.startsWith "s SATISFIABLE" then
match ModelParser.parse.run stdout.toUTF8 with
| .ok assignment =>
return .sat assignment
| .error err =>
throwError s!"Error {err} while parsing:\n{stdout}"
else
throwError s!"The external prover produced unexpected output, stdout:\n{stdout}stderr:\n{stderr}"
end External

View File

@@ -0,0 +1,19 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.Attr
import Lean.Elab.Tactic.BVDecide.Frontend.BVCheck
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide
import Lean.Elab.Tactic.BVDecide.Frontend.BVTrace
import Lean.Elab.Tactic.BVDecide.Frontend.LRAT
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize
/-!
This module provides the tactic frontends, consisting of:
- `bv_decide`, the bitblasting based `BitVec` decision procedure itself.
- `bv_check`, like `bv_decide` but the LRAT proof is provided as a file so no need to call a SAT solver.
- `bv_decide?`, converts `bv_decide?` into `bv_check` calls.
-/

View File

@@ -0,0 +1,87 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kim Morrison, Henrik Böving
-/
prelude
import Lean.Util.Trace
import Lean.Elab.Tactic.Simp
/-!
Provides environment extensions around the `bv_decide` tactic frontends.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Lean
open Lean.Meta.Simp
builtin_initialize registerTraceClass `Meta.Tactic.sat
builtin_initialize registerTraceClass `Meta.Tactic.bv
register_builtin_option sat.solver : String := {
defValue := ""
descr :=
"Name of the SAT solver used by Lean.Elab.Tactic.BVDecide tactics.\n
1. If this is set to something besides the emtpy string they will use that binary.\n
2. If this is set to the empty string they will check if there is a cadical binary next to the\
executing program. Usually that program is going to be `lean` itself and we do ship a\
`cadical` next to it.\n
3. If that does not succeed try to call `cadical` from PATH. The empty string default indicates\
to use the one that ships with Lean."
}
register_builtin_option sat.timeout : Nat := {
defValue := 10
descr := "the number of seconds that the sat solver is run before aborting"
}
register_builtin_option sat.trimProofs : Bool := {
defValue := true
descr := "Whether to run the trimming algorithm on LRAT proofs"
}
register_builtin_option sat.binaryProofs : Bool := {
defValue := true
descr := "Whether to use the binary LRAT proof format. Currently set to false and ignored on Windows due to a bug in CaDiCal."
}
register_builtin_option debug.bv.graphviz : Bool := {
defValue := false
descr := "Output the AIG of bv_decide as graphviz into a file called aig.gv in the working directory of the Lean process."
}
builtin_initialize bvNormalizeExt : Meta.SimpExtension
Meta.registerSimpAttr `bv_normalize "simp theorems used by bv_normalize"
/-- Builtin `bv_normalize` simprocs. -/
builtin_initialize builtinBVNormalizeSimprocsRef : IO.Ref Meta.Simp.Simprocs IO.mkRef {}
builtin_initialize bvNormalizeSimprocExt : Meta.Simp.SimprocExtension
Meta.Simp.registerSimprocAttr `bv_normalize_proc "simprocs used by bv_normalize" (some builtinBVNormalizeSimprocsRef)
private def addBuiltin (declName : Name) (stx : Syntax) (addDeclName : Name) : AttrM Unit := do
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
let procExpr match ( getConstInfo declName).type with
| .const ``Simproc _ => pure <| mkApp3 (mkConst ``Sum.inl [0, 0]) (mkConst ``Simproc) (mkConst ``DSimproc) (mkConst declName)
| _ => throwError "unexpected type at bv_normalize simproc"
let val := mkAppN (mkConst addDeclName) #[toExpr declName, toExpr post, procExpr]
let initDeclName mkFreshUserName (declName ++ `declare)
declareBuiltin initDeclName val
def addBVNormalizeProcBuiltinAttr (declName : Name) (post : Bool) (proc : Sum Simproc DSimproc) : IO Unit :=
addSimprocBuiltinAttrCore builtinBVNormalizeSimprocsRef declName post proc
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `bvNormalizeProcBuiltinAttr
descr := "Builtin bv_normalize simproc"
applicationTime := AttributeApplicationTime.afterCompilation
erase := fun _ => throwError "Not implemented yet, [-builtin_bv_normalize_proc]"
add := fun declName stx _ => addBuiltin declName stx ``addBVNormalizeProcBuiltinAttr
}
end Frontend
namespace Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,68 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide
import Lean.Meta.Tactic.TryThis
import Std.Tactic.BVDecide.Syntax
/-!
This modules provides the implementation of `bv_check`.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend.BVCheck
open Std.Tactic.BVDecide
open Std.Tactic.BVDecide.Reflect
/--
Get the directory that contains the Lean file which is currently being elaborated.
-/
def getSrcDir : TermElabM System.FilePath := do
let ctx readThe Lean.Core.Context
let srcPath := System.FilePath.mk ctx.fileName
let some srcDir := srcPath.parent
| throwError "cannot compute parent directory of '{srcPath}'"
return srcDir
def mkContext (lratPath : System.FilePath) : TermElabM TacticContext := do
let lratPath := ( getSrcDir) / lratPath
TacticContext.new lratPath
/--
Prepare an `Expr` that proves `bvExpr.unsat` using `ofReduceBool`.
-/
def lratChecker (cfg : TacticContext) (bvExpr : BVLogicalExpr) : MetaM Expr := do
let cert LratCert.ofFile cfg.lratPath cfg.trimProofs
cert.toReflectionProof cfg bvExpr ``verifyBVExpr ``unsat_of_verifyBVExpr_eq_true
@[inherit_doc Lean.Parser.Tactic.bvCheck]
def bvCheck (g : MVarId) (cfg : TacticContext) : MetaM Unit := do
let unsatProver : UnsatProver := fun bvExpr _ => do
withTraceNode `sat (fun _ => return "Preparing LRAT reflection term") do
let proof lratChecker cfg bvExpr
return proof, ""
let _ closeWithBVReflection g unsatProver
return ()
open Lean.Meta.Tactic in
@[builtin_tactic Lean.Parser.Tactic.bvCheck]
def evalBvCheck : Tactic := fun
| `(tactic| bv_check%$tk $path:str) => do
let cfg BVDecide.Frontend.BVCheck.mkContext path.getString
liftMetaFinishingTactic fun g => do
let res Normalize.bvNormalize g
match res.goal with
| some g => bvCheck g cfg
| none =>
let bvNormalizeStx `(tactic| bv_normalize)
TryThis.addSuggestion tk bvNormalizeStx (origSpan? := getRef)
throwError m!"This goal can be closed by only applying bv_normalize, no need to keep the LRAT proof around."
| _ => throwUnsupportedSyntax
end Frontend.BVCheck
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,165 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Std.Sat.AIG.CNF
import Std.Sat.AIG.RelabelNat
import Std.Tactic.BVDecide.Bitblast
import Std.Tactic.BVDecide.Syntax
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide.SatAtBVLogical
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize
import Lean.Elab.Tactic.BVDecide.Frontend.LRAT
/-!
This module provides the implementation of the `bv_decide` frontend itself.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Std.Sat
open Std.Tactic.BVDecide
open Std.Tactic.BVDecide.Reflect
open Lean.Meta
/--
Given:
- `var2Cnf`: The mapping from AIG to CNF variables.
- `assignments`: A model for the CNF as provided by a SAT solver.
- `aigSize`: The amount of nodes in the AIG that was used to produce the CNF.
- `atomsAssignment`: The mapping of the reflection monad from atom indices to `Expr`.
Reconstruct bit by bit which value expression must have had which `BitVec` value and return all
expression - pair values.
-/
def reconstructCounterExample (var2Cnf : Std.HashMap BVBit Nat) (assignment : Array (Bool × Nat))
(aigSize : Nat) (atomsAssignment : Std.HashMap Nat Expr) :
Array (Expr × BVExpr.PackedBitVec) := Id.run do
let mut sparseMap : Std.HashMap Nat (RBMap Nat Bool Ord.compare) := {}
for (bitVar, cnfVar) in var2Cnf.toArray do
/-
The setup of the variables in CNF is as follows:
1. One auxiliary variable for each node in the AIG
2. The actual BitVec bitwise variables
Hence we access the assignment array offset by the AIG size to obtain the value for a BitVec bit.
We assume that a variable can be found at its index as CaDiCal prints them in order.
-/
let (varSet, _) := assignment[cnfVar + aigSize]!
let mut bitMap := sparseMap.getD bitVar.var {}
bitMap := bitMap.insert bitVar.idx varSet
sparseMap := sparseMap.insert bitVar.var bitMap
let mut finalMap := #[]
for (bitVecVar, bitMap) in sparseMap.toArray do
let mut value : Nat := 0
let mut currentBit := 0
for (bitIdx, bitValue) in bitMap.toList do
assert! bitIdx == currentBit
if bitValue then
value := value ||| (1 <<< currentBit)
currentBit := currentBit + 1
let atomExpr := atomsAssignment.get! bitVecVar
finalMap := finalMap.push (atomExpr, BitVec.ofNat currentBit value)
return finalMap
structure UnsatProver.Result where
proof : Expr
lratCert : LratCert
abbrev UnsatProver := BVLogicalExpr Std.HashMap Nat Expr MetaM UnsatProver.Result
def lratBitblaster (cfg : TacticContext) (bv : BVLogicalExpr)
(atomsAssignment : Std.HashMap Nat Expr) :
MetaM UnsatProver.Result := do
let entry
withTraceNode `bv (fun _ => return "Bitblasting BVLogicalExpr to AIG") do
-- lazyPure to prevent compiler lifting
IO.lazyPure (fun _ => bv.bitblast)
let aigSize := entry.aig.decls.size
trace[Meta.Tactic.bv] s!"AIG has {aigSize} nodes."
if cfg.graphviz then
IO.FS.writeFile ("." / "aig.gv") <| AIG.toGraphviz entry
let (cnf, map)
withTraceNode `sat (fun _ => return "Converting AIG to CNF") do
-- lazyPure to prevent compiler lifting
IO.lazyPure (fun _ =>
let (entry, map) := entry.relabelNat'
let cnf := AIG.toCNF entry
(cnf, map)
)
let res
withTraceNode `sat (fun _ => return "Obtaining external proof certificate") do
runExternal cnf cfg.solver cfg.lratPath cfg.trimProofs cfg.timeout cfg.binaryProofs
match res with
| .ok cert =>
let proof cert.toReflectionProof cfg bv ``verifyBVExpr ``unsat_of_verifyBVExpr_eq_true
return proof, cert
| .error assignment =>
let reconstructed := reconstructCounterExample map assignment aigSize atomsAssignment
let mut error := m!"The prover found a potential counterexample, consider the following assignment:\n"
for (var, value) in reconstructed do
error := error ++ m!"{var} = {value.bv}\n"
throwError error
def reflectBV (g : MVarId) : M (BVLogicalExpr × (Expr M Expr)) := g.withContext do
let hyps getLocalHyps
let sats hyps.filterMapM SatAtBVLogical.of
if sats.size = 0 then
let mut error := "None of the hypotheses are in the supported BitVec fragment.\n"
error := error ++ "There are two potential fixes for this:\n"
error := error ++ "1. If you are using custom BitVec constructs simplify them to built-in ones.\n"
error := error ++ "2. If your problem is using only built-in ones it might currently be out of reach.\n"
error := error ++ " Consider expressing it in terms of different operations that are better supported."
throwError error
let sat := sats.foldl (init := SatAtBVLogical.trivial) SatAtBVLogical.and
return (sat.bvExpr, sat.proveFalse)
def closeWithBVReflection (g : MVarId) (unsatProver : UnsatProver) :
MetaM LratCert := M.run do
g.withContext do
let (bvLogicalExpr, f)
withTraceNode `bv (fun _ => return "Reflecting goal into BVLogicalExpr") do
reflectBV g
trace[Meta.Tactic.bv] "Reflected bv logical expression: {bvLogicalExpr}"
let atomsPairs := ( getThe State).atoms.toList.map (fun (expr, _, ident) => (ident, expr))
let atomsAssignment := Std.HashMap.ofList atomsPairs
let bvExprUnsat, cert unsatProver bvLogicalExpr atomsAssignment
let proveFalse f bvExprUnsat
g.assign proveFalse
return cert
def bvUnsat (g : MVarId) (cfg : TacticContext) : MetaM LratCert := M.run do
let unsatProver : UnsatProver := fun bvExpr atomsAssignment => do
withTraceNode `bv (fun _ => return "Preparing LRAT reflection term") do
lratBitblaster cfg bvExpr atomsAssignment
closeWithBVReflection g unsatProver
structure Result where
simpTrace : Simp.Stats
lratCert : Option LratCert
def bvDecide (g : MVarId) (cfg : TacticContext) : MetaM Result := do
let g?, simpTrace Normalize.bvNormalize g
let some g := g? | return simpTrace, none
let lratCert bvUnsat g cfg
return simpTrace, some lratCert
@[builtin_tactic Lean.Parser.Tactic.bvDecide]
def evalBvTrace : Tactic := fun
| `(tactic| bv_decide) => do
IO.FS.withTempFile fun _ lratFile => do
let cfg BVDecide.Frontend.TacticContext.new lratFile
liftMetaFinishingTactic fun g => do
discard <| bvDecide g cfg
| _ => throwUnsupportedSyntax
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,175 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Std.Data.HashMap
import Std.Tactic.BVDecide.Bitblast.BVExpr.Basic
import Lean.Meta.AppBuilder
import Lean.ToExpr
/-!
This module contains the implementation of the reflection monad, used by all other components of this
directory.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Std.Tactic.BVDecide
instance : ToExpr BVBinOp where
toExpr x :=
match x with
| .and => mkConst ``BVBinOp.and
| .or => mkConst ``BVBinOp.or
| .xor => mkConst ``BVBinOp.xor
| .add => mkConst ``BVBinOp.add
| .mul => mkConst ``BVBinOp.mul
toTypeExpr := mkConst ``BVBinOp
instance : ToExpr BVUnOp where
toExpr x :=
match x with
| .not => mkConst ``BVUnOp.not
| .shiftLeftConst n => mkApp (mkConst ``BVUnOp.shiftLeftConst) (toExpr n)
| .shiftRightConst n => mkApp (mkConst ``BVUnOp.shiftRightConst) (toExpr n)
| .rotateLeft n => mkApp (mkConst ``BVUnOp.rotateLeft) (toExpr n)
| .rotateRight n => mkApp (mkConst ``BVUnOp.rotateRight) (toExpr n)
| .arithShiftRightConst n => mkApp (mkConst ``BVUnOp.arithShiftRightConst) (toExpr n)
toTypeExpr := mkConst ``BVUnOp
instance : ToExpr (BVExpr w) where
toExpr x := go x
toTypeExpr := mkApp (mkConst ``BVExpr) (toExpr w)
where
go {w : Nat} : BVExpr w Expr
| .var idx => mkApp2 (mkConst ``BVExpr.var) (toExpr w) (toExpr idx)
| .const val => mkApp2 (mkConst ``BVExpr.const) (toExpr w) (toExpr val)
| .zeroExtend (w := oldWidth) val inner =>
mkApp3 (mkConst ``BVExpr.zeroExtend) (toExpr oldWidth) (toExpr val) (go inner)
| .signExtend (w := oldWidth) val inner =>
mkApp3 (mkConst ``BVExpr.signExtend) (toExpr oldWidth) (toExpr val) (go inner)
| .bin lhs op rhs => mkApp4 (mkConst ``BVExpr.bin) (toExpr w) (go lhs) (toExpr op) (go rhs)
| .un op operand => mkApp3 (mkConst ``BVExpr.un) (toExpr w) (toExpr op) (go operand)
| .append (l := l) (r := r) lhs rhs =>
mkApp4 (mkConst ``BVExpr.append) (toExpr l) (toExpr r) (go lhs) (go rhs)
| .replicate (w := oldWidth) w inner =>
mkApp3 (mkConst ``BVExpr.replicate) (toExpr oldWidth) (toExpr w) (go inner)
| .extract (w := oldWidth) hi lo expr =>
mkApp4 (mkConst ``BVExpr.extract) (toExpr oldWidth) (toExpr hi) (toExpr lo) (go expr)
| .shiftLeft (m := m) (n := n) lhs rhs =>
mkApp4 (mkConst ``BVExpr.shiftLeft) (toExpr m) (toExpr n) (go lhs) (go rhs)
| .shiftRight (m := m) (n := n) lhs rhs =>
mkApp4 (mkConst ``BVExpr.shiftRight) (toExpr m) (toExpr n) (go lhs) (go rhs)
instance : ToExpr BVBinPred where
toExpr x :=
match x with
| .eq => mkConst ``BVBinPred.eq
| .ult => mkConst ``BVBinPred.ult
toTypeExpr := mkConst ``BVBinPred
instance : ToExpr Gate where
toExpr x :=
match x with
| .and => mkConst ``Gate.and
| .or => mkConst ``Gate.or
| .xor => mkConst ``Gate.xor
| .imp => mkConst ``Gate.imp
| .beq => mkConst ``Gate.beq
toTypeExpr := mkConst ``Gate
instance : ToExpr BVPred where
toExpr x := go x
toTypeExpr := mkConst ``BVPred
where
go : BVPred Expr
| .bin (w := w) lhs op rhs =>
mkApp4 (mkConst ``BVPred.bin) (toExpr w) (toExpr lhs) (toExpr op) (toExpr rhs)
| .getLsb (w := w) expr idx =>
mkApp3 (mkConst ``BVPred.getLsb) (toExpr w) (toExpr expr) (toExpr idx)
instance [ToExpr α] : ToExpr (BoolExpr α) where
toExpr x := go x
toTypeExpr := mkApp (mkConst ``BoolExpr) (toTypeExpr α)
where
go : (BoolExpr α) Expr
| .literal lit => mkApp2 (mkConst ``BoolExpr.literal) (toTypeExpr α) (toExpr lit)
| .const b => mkApp2 (mkConst ``BoolExpr.const) (toTypeExpr α) (toExpr b)
| .not x => mkApp2 (mkConst ``BoolExpr.not) (toTypeExpr α) (go x)
| .gate g x y => mkApp4 (mkConst ``BoolExpr.gate) (toTypeExpr α) (toExpr g) (go x) (go y)
open Lean.Meta
/--
The state of the reflection monad
-/
structure State where
/--
The atoms encountered so far. Saved as a map from `BitVec` expressions to a (width, atomNumber)
pair.
-/
atoms : Std.HashMap Expr (Nat × Nat) := {}
/--
A cache for `atomsAssignment`.
-/
atomsAssignmentCache : Expr := mkConst ``List.nil [.zero]
/--
The reflection monad, used to track `BitVec` variables that we see as we traverse the context.
-/
abbrev M := StateRefT State MetaM
namespace M
/--
Run a reflection computation as a `MetaM` one.
-/
def run (m : M α) : MetaM α :=
m.run' { }
/--
Retrieve the atoms as pairs of their width and expression.
-/
def atoms : M (List (Nat × Expr)) := do
let sortedAtoms := ( getThe State).atoms.toArray.qsort (·.2.2 < ·.2.2)
return sortedAtoms.map (fun (expr, width, _) => (width, expr)) |>.toList
/--
Retrieve a `BitVec.Assignment` representing the atoms we found so far.
-/
def atomsAssignment : M Expr := do
return ( getThe State).atomsAssignmentCache
/--
Look up an expression in the atoms, recording it if it has not previously appeared.
-/
def lookup (e : Expr) (width : Nat) : M Nat := do
match ( getThe State).atoms[e]? with
| some (width', ident) =>
if width != width' then
panic! "The same atom occurs with different widths, this is a bug"
return ident
| none =>
trace[Meta.Tactic.bv] "New atom of width {width}: {e}"
let ident modifyGetThe State fun s =>
(s.atoms.size, { s with atoms := s.atoms.insert e (width, s.atoms.size) })
updateAtomsAssignment
return ident
where
updateAtomsAssignment : M Unit := do
let as atoms
let packed :=
as.map (fun (width, expr) => mkApp2 (mkConst ``BVExpr.PackedBitVec.mk) (toExpr width) expr)
let packedType := mkConst ``BVExpr.PackedBitVec
let newAtomsAssignment mkListLit packedType packed
modify fun s => { s with atomsAssignmentCache := newAtomsAssignment }
end M
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,357 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide.Reflect
import Std.Tactic.BVDecide.Reflect
/-!
Provides the logic for reifying `BitVec` expressions.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Lean.Meta
open Std.Tactic.BVDecide
open Std.Tactic.BVDecide.Reflect.BitVec
/--
A reified version of an `Expr` representing a `BVExpr`.
-/
structure ReifiedBVExpr where
width : Nat
/--
The reified expression.
-/
bvExpr : BVExpr width
/--
A proof that `bvExpr.eval atomsAssignment = originalBVExpr`.
-/
evalsAtAtoms : M Expr
/--
A cache for `toExpr bvExpr`.
-/
expr : Expr
namespace ReifiedBVExpr
def mkEvalExpr (w : Nat) (expr : Expr) : M Expr := do
return mkApp3 (mkConst ``BVExpr.eval) (toExpr w) ( M.atomsAssignment) expr
def mkBVRefl (w : Nat) (expr : Expr) : Expr :=
mkApp2
(mkConst ``Eq.refl [1])
(mkApp (mkConst ``BitVec) (toExpr w))
expr
def mkAtom (e : Expr) (width : Nat) : M ReifiedBVExpr := do
let ident M.lookup e width
let expr := mkApp2 (mkConst ``BVExpr.var) (toExpr width) (toExpr ident)
let proof := do
let evalExpr mkEvalExpr width expr
return mkBVRefl width evalExpr
return width, .var ident, proof, expr
def getNatOrBvValue? (ty : Expr) (expr : Expr) : M (Option Nat) := do
match_expr ty with
| Nat =>
getNatValue? expr
| BitVec _ =>
let some _, distance getBitVecValue? expr | return none
return some distance.toNat
| _ => return none
/--
Reify an `Expr` that's a `BitVec`.
-/
partial def of (x : Expr) : M (Option ReifiedBVExpr) := do
match_expr x with
| BitVec.ofNat _ _ => goBvLit x
| HAnd.hAnd _ _ _ _ lhsExpr rhsExpr =>
binaryReflection lhsExpr rhsExpr .and ``Std.Tactic.BVDecide.Reflect.BitVec.and_congr
| HOr.hOr _ _ _ _ lhsExpr rhsExpr =>
binaryReflection lhsExpr rhsExpr .or ``Std.Tactic.BVDecide.Reflect.BitVec.or_congr
| HXor.hXor _ _ _ _ lhsExpr rhsExpr =>
binaryReflection lhsExpr rhsExpr .xor ``Std.Tactic.BVDecide.Reflect.BitVec.xor_congr
| HAdd.hAdd _ _ _ _ lhsExpr rhsExpr =>
binaryReflection lhsExpr rhsExpr .add ``Std.Tactic.BVDecide.Reflect.BitVec.add_congr
| HMul.hMul _ _ _ _ lhsExpr rhsExpr =>
binaryReflection lhsExpr rhsExpr .mul ``Std.Tactic.BVDecide.Reflect.BitVec.mul_congr
| Complement.complement _ _ innerExpr =>
unaryReflection innerExpr .not ``Std.Tactic.BVDecide.Reflect.BitVec.not_congr
| HShiftLeft.hShiftLeft _ β _ _ innerExpr distanceExpr =>
let distance? getNatOrBvValue? β distanceExpr
if distance?.isSome then
shiftConstReflection
β
distanceExpr
innerExpr
.shiftLeftConst
``BVUnOp.shiftLeftConst
``Std.Tactic.BVDecide.Reflect.BitVec.shiftLeftNat_congr
else
shiftReflection
β
distanceExpr
innerExpr
.shiftLeft
``BVExpr.shiftLeft
``Std.Tactic.BVDecide.Reflect.BitVec.shiftLeft_congr
| HShiftRight.hShiftRight _ β _ _ innerExpr distanceExpr =>
let distance? getNatOrBvValue? β distanceExpr
if distance?.isSome then
shiftConstReflection
β
distanceExpr
innerExpr
.shiftRightConst
``BVUnOp.shiftRightConst
``Std.Tactic.BVDecide.Reflect.BitVec.shiftRightNat_congr
else
shiftReflection
β
distanceExpr
innerExpr
.shiftRight
``BVExpr.shiftRight
``Std.Tactic.BVDecide.Reflect.BitVec.shiftRight_congr
| BitVec.sshiftRight _ innerExpr distanceExpr =>
let some distance getNatValue? distanceExpr | return ofAtom x
shiftConstLikeReflection
distance
innerExpr
.arithShiftRightConst
``BVUnOp.arithShiftRightConst
``Std.Tactic.BVDecide.Reflect.BitVec.arithShiftRight_congr
| BitVec.zeroExtend _ newWidthExpr innerExpr =>
let some newWidth getNatValue? newWidthExpr | return ofAtom x
let some inner ofOrAtom innerExpr | return none
let bvExpr := .zeroExtend newWidth inner.bvExpr
let expr :=
mkApp3
(mkConst ``BVExpr.zeroExtend)
(toExpr inner.width)
newWidthExpr
inner.expr
let proof := do
let innerEval mkEvalExpr inner.width inner.expr
let innerProof inner.evalsAtAtoms
return mkApp5 (mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.zeroExtend_congr)
newWidthExpr
(toExpr inner.width)
innerExpr
innerEval
innerProof
return some newWidth, bvExpr, proof, expr
| BitVec.signExtend _ newWidthExpr innerExpr =>
let some newWidth getNatValue? newWidthExpr | return ofAtom x
let some inner ofOrAtom innerExpr | return none
let bvExpr := .signExtend newWidth inner.bvExpr
let expr :=
mkApp3
(mkConst ``BVExpr.signExtend)
(toExpr inner.width)
newWidthExpr
inner.expr
let proof := do
let innerEval mkEvalExpr inner.width inner.expr
let innerProof inner.evalsAtAtoms
return mkApp5 (mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.signExtend_congr)
newWidthExpr
(toExpr inner.width)
innerExpr
innerEval
innerProof
return some newWidth, bvExpr, proof, expr
| HAppend.hAppend _ _ _ _ lhsExpr rhsExpr =>
let some lhs ofOrAtom lhsExpr | return none
let some rhs ofOrAtom rhsExpr | return none
let bvExpr := .append lhs.bvExpr rhs.bvExpr
let expr := mkApp4 (mkConst ``BVExpr.append)
(toExpr lhs.width)
(toExpr rhs.width)
lhs.expr rhs.expr
let proof := do
let lhsEval mkEvalExpr lhs.width lhs.expr
let lhsProof lhs.evalsAtAtoms
let rhsProof rhs.evalsAtAtoms
let rhsEval mkEvalExpr rhs.width rhs.expr
return mkApp8 (mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.append_congr)
(toExpr lhs.width) (toExpr rhs.width)
lhsExpr lhsEval
rhsExpr rhsEval
lhsProof rhsProof
return some lhs.width + rhs.width, bvExpr, proof, expr
| BitVec.replicate _ nExpr innerExpr =>
let some inner ofOrAtom innerExpr | return none
let some n getNatValue? nExpr | return ofAtom x
let bvExpr := .replicate n inner.bvExpr
let expr := mkApp3 (mkConst ``BVExpr.replicate)
(toExpr inner.width)
(toExpr n)
inner.expr
let proof := do
let innerEval mkEvalExpr inner.width inner.expr
let innerProof inner.evalsAtAtoms
return mkApp5 (mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.replicate_congr)
(toExpr n)
(toExpr inner.width)
innerExpr
innerEval
innerProof
return some inner.width * n, bvExpr, proof, expr
| BitVec.extractLsb _ hiExpr loExpr innerExpr =>
let some hi getNatValue? hiExpr | return ofAtom x
let some lo getNatValue? loExpr | return ofAtom x
let some inner ofOrAtom innerExpr | return none
let bvExpr := .extract hi lo inner.bvExpr
let expr := mkApp4 (mkConst ``BVExpr.extract)
(toExpr inner.width)
hiExpr
loExpr
inner.expr
let proof := do
let innerEval mkEvalExpr inner.width inner.expr
let innerProof inner.evalsAtAtoms
return mkApp6 (mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.extract_congr)
hiExpr
loExpr
(toExpr inner.width)
innerExpr
innerEval
innerProof
return some hi - lo + 1, bvExpr, proof, expr
| BitVec.rotateLeft _ innerExpr distanceExpr =>
rotateReflection
distanceExpr
innerExpr
.rotateLeft
``BVUnOp.rotateLeft
``Std.Tactic.BVDecide.Reflect.BitVec.rotateLeft_congr
| BitVec.rotateRight _ innerExpr distanceExpr =>
rotateReflection
distanceExpr
innerExpr
.rotateRight
``BVUnOp.rotateRight
``Std.Tactic.BVDecide.Reflect.BitVec.rotateRight_congr
| _ => ofAtom x
where
ofAtom (x : Expr) : M (Option ReifiedBVExpr) := do
let t instantiateMVars ( whnfR ( inferType x))
let_expr BitVec widthExpr := t | return none
let some width getNatValue? widthExpr | return none
let atom mkAtom x width
return some atom
ofOrAtom (x : Expr) : M (Option ReifiedBVExpr) := do
let res of x
match res with
| some exp => return some exp
| none => ofAtom x
shiftConstLikeReflection (distance : Nat) (innerExpr : Expr) (shiftOp : Nat BVUnOp)
(shiftOpName : Name) (congrThm : Name) :
M (Option ReifiedBVExpr) := do
let some inner ofOrAtom innerExpr | return none
let bvExpr : BVExpr inner.width := .un (shiftOp distance) inner.bvExpr
let expr :=
mkApp3
(mkConst ``BVExpr.un)
(toExpr inner.width)
(mkApp (mkConst shiftOpName) (toExpr distance))
inner.expr
let congrProof :=
mkApp
(mkConst congrThm)
(toExpr distance)
let proof := unaryCongrProof inner innerExpr congrProof
return some inner.width, bvExpr, proof, expr
rotateReflection (distanceExpr : Expr) (innerExpr : Expr) (rotateOp : Nat BVUnOp)
(rotateOpName : Name) (congrThm : Name) :
M (Option ReifiedBVExpr) := do
-- Either the shift values are constant or we abstract the entire term as atoms
let some distance getNatValue? distanceExpr | return ofAtom x
shiftConstLikeReflection distance innerExpr rotateOp rotateOpName congrThm
shiftConstReflection (β : Expr) (distanceExpr : Expr) (innerExpr : Expr) (shiftOp : Nat BVUnOp)
(shiftOpName : Name) (congrThm : Name) :
M (Option ReifiedBVExpr) := do
-- Either the shift values are constant or we abstract the entire term as atoms
let some distance getNatOrBvValue? β distanceExpr | return ofAtom x
shiftConstLikeReflection distance innerExpr shiftOp shiftOpName congrThm
shiftReflection (β : Expr) (distanceExpr : Expr) (innerExpr : Expr)
(shiftOp : {m n : Nat} BVExpr m BVExpr n BVExpr m) (shiftOpName : Name)
(congrThm : Name) :
M (Option ReifiedBVExpr) := do
let_expr BitVec _ β | return ofAtom x
let some inner of innerExpr | return none
let some distance of distanceExpr | return none
let bvExpr : BVExpr inner.width := shiftOp inner.bvExpr distance.bvExpr
let expr :=
mkApp4
(mkConst shiftOpName)
(toExpr inner.width)
(toExpr distance.width)
inner.expr
distance.expr
let congrProof :=
mkApp2
(mkConst congrThm)
(toExpr inner.width)
(toExpr distance.width)
let proof := binaryCongrProof inner distance innerExpr distanceExpr congrProof
return some inner.width, bvExpr, proof, expr
binaryReflection (lhsExpr rhsExpr : Expr) (op : BVBinOp) (congrThm : Name) :
M (Option ReifiedBVExpr) := do
let some lhs ofOrAtom lhsExpr | return none
let some rhs ofOrAtom rhsExpr | return none
if h : rhs.width = lhs.width then
let bvExpr : BVExpr lhs.width := .bin lhs.bvExpr op (h rhs.bvExpr)
let expr := mkApp4 (mkConst ``BVExpr.bin) (toExpr lhs.width) lhs.expr (toExpr op) rhs.expr
let congrThm := mkApp (mkConst congrThm) (toExpr lhs.width)
let proof := binaryCongrProof lhs rhs lhsExpr rhsExpr congrThm
return some lhs.width, bvExpr, proof, expr
else
return none
binaryCongrProof (lhs rhs : ReifiedBVExpr) (lhsExpr rhsExpr : Expr) (congrThm : Expr) :
M Expr := do
let lhsEval mkEvalExpr lhs.width lhs.expr
let lhsProof lhs.evalsAtAtoms
let rhsProof rhs.evalsAtAtoms
let rhsEval mkEvalExpr rhs.width rhs.expr
return mkApp6 congrThm lhsExpr rhsExpr lhsEval rhsEval lhsProof rhsProof
unaryReflection (innerExpr : Expr) (op : BVUnOp) (congrThm : Name) :
M (Option ReifiedBVExpr) := do
let some inner ofOrAtom innerExpr | return none
let bvExpr := .un op inner.bvExpr
let expr := mkApp3 (mkConst ``BVExpr.un) (toExpr inner.width) (toExpr op) inner.expr
let proof := unaryCongrProof inner innerExpr (mkConst congrThm)
return some inner.width, bvExpr, proof, expr
unaryCongrProof (inner : ReifiedBVExpr) (innerExpr : Expr) (congrProof : Expr) : M Expr := do
let innerEval mkEvalExpr inner.width inner.expr
let innerProof inner.evalsAtAtoms
return mkApp4 congrProof (toExpr inner.width) innerExpr innerEval innerProof
goBvLit (x : Expr) : M (Option ReifiedBVExpr) := do
let some width, bvVal getBitVecValue? x | return none
let bvExpr : BVExpr width := .const bvVal
let expr := mkApp2 (mkConst ``BVExpr.const) (toExpr width) (toExpr bvVal)
let proof := do
let evalExpr mkEvalExpr width expr
return mkBVRefl width evalExpr
return some width, bvExpr, proof, expr
end ReifiedBVExpr
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,112 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide.ReifiedBVPred
/-!
Provides the logic for reifying `BitVec` problems with boolean substructure.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Std.Tactic.BVDecide
open Std.Tactic.BVDecide.Reflect.Bool
/--
A reified version of an `Expr` representing a `BVLogicalExpr`.
-/
structure ReifiedBVLogical where
/--
The reified expression.
-/
bvExpr : BVLogicalExpr
/--
A proof that `bvExpr.eval atomsAssignment = originalBVLogicalExpr`.
-/
evalsAtAtoms : M Expr
/--
A cache for `toExpr bvExpr`
-/
expr : Expr
namespace ReifiedBVLogical
def mkRefl (expr : Expr) : Expr :=
mkApp2 (mkConst ``Eq.refl [1]) (mkConst ``Bool) expr
def mkTrans (x y z : Expr) (hxy hyz : Expr) : Expr :=
mkApp6 (mkConst ``Eq.trans [1]) (mkConst ``Bool) x y z hxy hyz
def mkEvalExpr (expr : Expr) : M Expr := do
return mkApp2 (mkConst ``BVLogicalExpr.eval) ( M.atomsAssignment) expr
partial def of (t : Expr) : M (Option ReifiedBVLogical) := do
match_expr t with
| Bool.true =>
let boolExpr := .const true
let expr := mkApp2 (mkConst ``BoolExpr.const) (mkConst ``BVPred) (toExpr Bool.true)
let proof := return mkRefl (mkConst ``Bool.true)
return some boolExpr, proof, expr
| Bool.false =>
let boolExpr := .const false
let expr := mkApp2 (mkConst ``BoolExpr.const) (mkConst ``BVPred) (toExpr Bool.false)
let proof := return mkRefl (mkConst ``Bool.false)
return some boolExpr, proof, expr
| not subExpr =>
let some sub of subExpr | return none
let boolExpr := .not sub.bvExpr
let expr := mkApp2 (mkConst ``BoolExpr.not) (mkConst ``BVPred) sub.expr
let proof := do
let subEvalExpr mkEvalExpr sub.expr
let subProof sub.evalsAtAtoms
return mkApp3 (mkConst ``Std.Tactic.BVDecide.Reflect.Bool.not_congr) subExpr subEvalExpr subProof
return some boolExpr, proof, expr
| or lhsExpr rhsExpr => gateReflection lhsExpr rhsExpr .or ``Std.Tactic.BVDecide.Reflect.Bool.or_congr
| and lhsExpr rhsExpr => gateReflection lhsExpr rhsExpr .and ``Std.Tactic.BVDecide.Reflect.Bool.and_congr
| xor lhsExpr rhsExpr => gateReflection lhsExpr rhsExpr .xor ``Std.Tactic.BVDecide.Reflect.Bool.xor_congr
| BEq.beq α _ lhsExpr rhsExpr =>
match_expr α with
| Bool => gateReflection lhsExpr rhsExpr .beq ``Std.Tactic.BVDecide.Reflect.Bool.beq_congr
| BitVec _ => goPred t
| _ => return none
| _ => goPred t
where
gateReflection (lhsExpr rhsExpr : Expr) (gate : Gate) (congrThm : Name) :
M (Option ReifiedBVLogical) := do
let some lhs of lhsExpr | return none
let some rhs of rhsExpr | return none
let boolExpr := .gate gate lhs.bvExpr rhs.bvExpr
let expr :=
mkApp4
(mkConst ``BoolExpr.gate)
(mkConst ``BVPred)
(toExpr gate)
lhs.expr
rhs.expr
let proof := do
let lhsEvalExpr mkEvalExpr lhs.expr
let rhsEvalExpr mkEvalExpr rhs.expr
let lhsProof lhs.evalsAtAtoms
let rhsProof rhs.evalsAtAtoms
return mkApp6
(mkConst congrThm)
lhsExpr rhsExpr
lhsEvalExpr rhsEvalExpr
lhsProof rhsProof
return some boolExpr, proof, expr
goPred (t : Expr) : M (Option ReifiedBVLogical) := do
let some bvPred ReifiedBVPred.of t | return none
let boolExpr := .literal bvPred.bvPred
let expr := mkApp2 (mkConst ``BoolExpr.literal) (mkConst ``BVPred) bvPred.expr
let proof := bvPred.evalsAtAtoms
return some boolExpr, proof, expr
end ReifiedBVLogical
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,120 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide.ReifiedBVExpr
/-!
Provides the logic for reifying expressions consisting of predicates over `BitVec`s.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Lean.Meta
open Std.Tactic.BVDecide
open Std.Tactic.BVDecide.Reflect.BitVec
/--
A reified version of an `Expr` representing a `BVPred`.
-/
structure ReifiedBVPred where
/--
The reified expression.
-/
bvPred : BVPred
/--
A proof that `bvPred.eval atomsAssignment = originalBVPredExpr`.
-/
evalsAtAtoms : M Expr
/--
A cache for `toExpr bvPred`
-/
expr : Expr
namespace ReifiedBVPred
/--
Reify an `Expr` that is a proof of a predicate about `BitVec`.
-/
def of (t : Expr) : M (Option ReifiedBVPred) := do
match_expr t with
| BEq.beq α _ lhsExpr rhsExpr =>
let_expr BitVec _ := α | return none
binaryReflection lhsExpr rhsExpr .eq ``Std.Tactic.BVDecide.Reflect.BitVec.beq_congr
| BitVec.ult _ lhsExpr rhsExpr =>
binaryReflection lhsExpr rhsExpr .ult ``Std.Tactic.BVDecide.Reflect.BitVec.ult_congr
| BitVec.getLsb _ subExpr idxExpr =>
let some sub ReifiedBVExpr.of subExpr | return none
let some idx getNatValue? idxExpr | return none
let bvExpr : BVPred := .getLsb sub.bvExpr idx
let expr := mkApp3 (mkConst ``BVPred.getLsb) (toExpr sub.width) sub.expr idxExpr
let proof := do
let subEval ReifiedBVExpr.mkEvalExpr sub.width sub.expr
let subProof sub.evalsAtAtoms
return mkApp5
(mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.getLsb_congr)
idxExpr
(toExpr sub.width)
subExpr
subEval
subProof
return some bvExpr, proof, expr
| _ =>
/-
Idea: we have t : Bool here, let's construct:
BitVec.ofBool t : BitVec 1
as an atom. Then construct the BVPred corresponding to
BitVec.getLsb (BitVec.ofBool t) 0 : Bool
We can prove that this is equivalent to `t`. This allows us to have boolean variables in BVPred.
-/
let ty inferType t
let_expr Bool := ty | return none
let atom ReifiedBVExpr.mkAtom (mkApp (mkConst ``BitVec.ofBool) t) 1
let bvExpr : BVPred := .getLsb atom.bvExpr 0
let expr := mkApp3 (mkConst ``BVPred.getLsb) (toExpr 1) atom.expr (toExpr 0)
let proof := do
let atomEval ReifiedBVExpr.mkEvalExpr atom.width atom.expr
let atomProof atom.evalsAtAtoms
return mkApp3
(mkConst ``Std.Tactic.BVDecide.Reflect.BitVec.ofBool_congr)
t
atomEval
atomProof
return some bvExpr, proof, expr
where
binaryReflection (lhsExpr rhsExpr : Expr) (pred : BVBinPred) (congrThm : Name) :
M (Option ReifiedBVPred) := do
let some lhs ReifiedBVExpr.of lhsExpr | return none
let some rhs ReifiedBVExpr.of rhsExpr | return none
if h:lhs.width = rhs.width then
let bvExpr : BVPred := .bin (w := lhs.width) lhs.bvExpr pred (h rhs.bvExpr)
let expr :=
mkApp4
(mkConst ``BVPred.bin)
(toExpr lhs.width)
lhs.expr
(toExpr pred)
rhs.expr
let proof := do
let lhsEval ReifiedBVExpr.mkEvalExpr lhs.width lhs.expr
let lhsProof lhs.evalsAtAtoms
let rhsEval ReifiedBVExpr.mkEvalExpr rhs.width rhs.expr
let rhsProof rhs.evalsAtAtoms
return mkApp7
(mkConst congrThm)
(toExpr lhs.width)
lhsExpr rhsExpr lhsEval rhsEval
lhsProof
rhsProof
return some bvExpr, proof, expr
else
return none
end ReifiedBVPred
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,101 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide.ReifiedBVLogical
/-!
This module is the main entry point for reifying `BitVec` problems with boolean substructure.
Given some proof `h : exp = true` where `exp` is a `BitVec` problem with boolean substructure, it
returns a `SatAtBVLogical`, containing the reified version as well as a proof that the reified
version must be equal to true.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Lean.Meta
open Std.Tactic.BVDecide
/--
A reified version of an `Expr` representing a `BVLogicalExpr` that we know to be true.
-/
structure SatAtBVLogical where
/--
The reified expression.
-/
bvExpr : BVLogicalExpr
/--
A proof that `bvExpr.eval atomsAssignment = true`.
-/
satAtAtoms : M Expr
/--
A cache for `toExpr bvExpr`
-/
expr : Expr
namespace SatAtBVLogical
/--
Reify an `Expr` that is a proof of some boolean structure on top of predicates about `BitVec`s.
-/
partial def of (h : Expr) : M (Option SatAtBVLogical) := do
let t instantiateMVars ( whnfR ( inferType h))
match_expr t with
| Eq α lhsExpr rhsExpr =>
let_expr Bool := α | return none
let_expr Bool.true := rhsExpr | return none
-- We now know that `h : lhsExpr = true`
-- We attempt to reify lhsExpr into a BVLogicalExpr, then prove that evaluating
-- this BVLogicalExpr must eval to true due to `h`
let some bvLogical ReifiedBVLogical.of lhsExpr | return none
let proof := do
let evalLogic ReifiedBVLogical.mkEvalExpr bvLogical.expr
-- this is evalLogic = lhsExpr
let evalProof bvLogical.evalsAtAtoms
-- h is lhsExpr = true
-- we prove evalLogic = true by evalLogic = lhsExpr = true
return ReifiedBVLogical.mkTrans evalLogic lhsExpr (mkConst ``Bool.true) evalProof h
return some bvLogical.bvExpr, proof, bvLogical.expr
| _ => return none
/--
The trivially true `BVLogicalExpr`.
-/
def trivial : SatAtBVLogical where
bvExpr := .const true
expr := toExpr (.const true : BVLogicalExpr)
satAtAtoms := return mkApp (mkConst ``BVLogicalExpr.sat_true) ( M.atomsAssignment)
/--
Logical conjunction of two `ReifiedBVLogical`.
-/
def and (x y : SatAtBVLogical) : SatAtBVLogical where
bvExpr := .gate .and x.bvExpr y.bvExpr
expr := mkApp4 (mkConst ``BoolExpr.gate) (mkConst ``BVPred) (mkConst ``Gate.and) x.expr y.expr
satAtAtoms :=
return mkApp5
(mkConst ``BVLogicalExpr.sat_and)
x.expr
y.expr
( M.atomsAssignment)
( x.satAtAtoms)
( y.satAtAtoms)
/-- Given a proof that `x.expr.Unsat`, produce a proof of `False`. -/
def proveFalse (x : SatAtBVLogical) (h : Expr) : M Expr := do
let atomsList M.atomsAssignment
let evalExpr := mkApp2 (mkConst ``BVLogicalExpr.eval) atomsList x.expr
return mkApp3
(mkConst ``Std.Tactic.BVDecide.Reflect.Bool.false_of_eq_true_of_eq_false)
evalExpr
( x.satAtAtoms)
(.app h atomsList)
end SatAtBVLogical
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,70 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.BVDecide
import Lean.Elab.Tactic.BVDecide.Frontend.BVCheck
import Lean.Elab.Tactic.BVDecide.LRAT.Trim
import Lean.Meta.Tactic.TryThis
import Std.Tactic.BVDecide.Syntax
/-!
This module contains the implementation of `bv_decide?`.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend.BVTrace
-- TODO: think of a more maintainable file pattern for this stuff.
/--
Produce a file with the pattern:
LeanFileName-DeclName-Line-Col.lrat
-/
def getLratFileName : TermElabM System.FilePath := do
let some baseName := System.FilePath.mk ( getFileName) |>.fileName | throwError "could not find file name"
let some declName Term.getDeclName? | throwError "could not find declaration name"
let pos := ( getFileMap).toPosition ( getRefPos)
return s!"{baseName}-{declName}-{pos.line}-{pos.column}.lrat"
open Std.Tactic.BVDecide.LRAT in
open Lean.Meta.Tactic in
open Lean.Elab.Tactic.BVDecide.LRAT in
@[builtin_tactic Lean.Parser.Tactic.bvTrace]
def evalBvTrace : Tactic := fun
| `(tactic| bv_decide?%$tk) => do
let lratFile : System.FilePath BVTrace.getLratFileName
let cfg := { ( BVCheck.mkContext lratFile) with trimProofs := false }
let g getMainGoal
let trace g.withContext do
bvDecide g cfg
/-
Ideally trace.lratCert would be the `ByteArray` version of the proof already and we just write
it. This isn't yet possible so instead we do the following:
1. Produce the proof in the tactic.
2. Skip trimming it in the tactic.
3. Run trimming on the LRAT file that was produced by the SAT solver directly, emitting the
correct binary format according to `sat.binaryProofs`.
TODO: Fix this hack:
1. Introduce `ByteArray` literals to the kernel.
2. Just return the fully trimmed proof in the format desired by the configuration from `bvDecide`.
3. Write it to the file directly.
-/
match trace.lratCert with
| none =>
let normalizeStx `(tactic| bv_normalize)
TryThis.addSuggestion tk normalizeStx (origSpan? := getRef)
| some .. =>
if sat.trimProofs.get ( getOptions) then
let lratPath := ( BVCheck.getSrcDir) / lratFile
let proof loadLRATProof lratPath
let trimmed IO.ofExcept <| trim proof
dumpLRATProof lratPath trimmed cfg.binaryProofs
let bvCheckStx `(tactic| bv_check $(quote lratFile.toString))
TryThis.addSuggestion tk bvCheckStx (origSpan? := getRef)
| _ => throwUnsupportedSyntax
end Frontend.BVTrace
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,210 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.Frontend.Attr
import Lean.Elab.Tactic.BVDecide.LRAT.Trim
import Lean.Elab.Tactic.BVDecide.External
import Std.Tactic.BVDecide.LRAT.Checker
import Std.Sat.CNF.Dimacs
/-!
This module contains the logic around writing proofs of UNSAT, using LRAT proofs, as meta code.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend
open Std.Sat
open Std.Tactic.BVDecide
open Lean.Meta
/--
The context for the `bv_decide` tactic.
-/
structure TacticContext where
exprDef : Name
certDef : Name
reflectionDef : Name
solver : System.FilePath
lratPath : System.FilePath
graphviz : Bool
timeout : Nat
trimProofs : Bool
binaryProofs : Bool
def TacticContext.new (lratPath : System.FilePath) : Lean.Elab.TermElabM TacticContext := do
let exprDef Lean.Elab.Term.mkAuxName `_expr_def
let certDef Lean.Elab.Term.mkAuxName `_cert_def
let reflectionDef Lean.Elab.Term.mkAuxName `_reflection_def
let opts getOptions
let solver determineSolver
trace[Meta.Tactic.sat] m!"Using SAT solver at '{solver}'"
let timeout := sat.timeout.get opts
let graphviz := debug.bv.graphviz.get opts
let trimProofs := sat.trimProofs.get opts
let binaryProofs :=
-- Account for: https://github.com/arminbiere/cadical/issues/112
if System.Platform.isWindows then
false
else
sat.binaryProofs.get opts
return {
exprDef,
certDef,
reflectionDef,
solver,
lratPath,
graphviz,
timeout,
trimProofs,
binaryProofs
}
where
determineSolver : Lean.Elab.TermElabM System.FilePath := do
let opts getOptions
let option := sat.solver.get opts
if option == "" then
let cadicalPath := ( IO.appPath).parent.get! / "cadical" |>.withExtension System.FilePath.exeExtension
if cadicalPath.pathExists then
return cadicalPath
else
return "cadical"
else
return option
/-- An LRAT proof read from a file. This will get parsed using ofReduceBool. -/
abbrev LratCert := String
instance : ToExpr LRAT.IntAction where
toExpr action :=
let beta := mkApp (mkConst ``Array [.zero]) (mkConst ``Int)
let alpha := mkConst ``Nat
match action with
| .addEmpty id hints =>
mkApp4 (mkConst ``LRAT.Action.addEmpty [.zero, .zero]) beta alpha (toExpr id) (toExpr hints)
| .addRup id c hints =>
mkApp5 (mkConst ``LRAT.Action.addRup [.zero, .zero])
beta
alpha
(toExpr id)
(toExpr c)
(toExpr hints)
| .addRat id c pivot rupHints ratHints =>
mkApp7 (mkConst ``LRAT.Action.addRat [.zero, .zero])
beta
alpha
(toExpr id)
(toExpr c)
(toExpr pivot)
(toExpr rupHints)
(toExpr ratHints)
| .del ids =>
mkApp3 (mkConst ``LRAT.Action.del [.zero, .zero]) beta alpha (toExpr ids)
toTypeExpr := mkConst ``LRAT.IntAction
def LratCert.ofFile (lratPath : System.FilePath) (trimProofs : Bool) : MetaM LratCert := do
let proofInput IO.FS.readBinFile lratPath
let proof
withTraceNode `sat (fun _ => return s!"Parsing LRAT file") do
-- lazyPure to prevent compiler lifting
let proof? IO.lazyPure (fun _ => LRAT.parseLRATProof proofInput)
match proof? with
| .ok proof => pure proof
| .error err => throwError "SAT solver produced invalid LRAT: {err}"
trace[Meta.Tactic.sat] s!"LRAT proof has {proof.size} steps before trimming"
let proof
if trimProofs then
withTraceNode `sat (fun _ => return "Trimming LRAT proof") do
-- lazyPure to prevent compiler lifting
let trimmed IO.lazyPure (fun _ => LRAT.trim proof)
IO.ofExcept trimmed
else
pure proof
trace[Meta.Tactic.sat] s!"LRAT proof has {proof.size} steps after trimming"
-- This is necessary because the proof might be in the binary format in which case we cannot
-- store it as a string in the environment (yet) due to missing support for binary literals.
let newProof := LRAT.lratProofToString proof
return newProof
/--
Run an external SAT solver on the `CNF` to obtain an LRAT proof.
This will obtain an `LratCert` if the formula is UNSAT and throw errors otherwise.
-/
def runExternal (cnf : CNF Nat) (solver : System.FilePath) (lratPath : System.FilePath)
(trimProofs : Bool) (timeout : Nat) (binaryProofs : Bool)
: MetaM (Except (Array (Bool × Nat)) LratCert) := do
IO.FS.withTempFile fun _ cnfPath => do
withTraceNode `sat (fun _ => return "Serializing SAT problem to DIMACS file") do
-- lazyPure to prevent compiler lifting
IO.FS.writeFile cnfPath ( IO.lazyPure (fun _ => cnf.dimacs))
let res
withTraceNode `sat (fun _ => return "Running SAT solver") do
External.satQuery solver cnfPath lratPath timeout binaryProofs
if let .sat assignment := res then
return .error assignment
let lratProof
withTraceNode `sat (fun _ => return "Obtaining LRAT certificate") do
LratCert.ofFile lratPath trimProofs
return .ok lratProof
/--
Add an auxiliary declaration. Only used to create constants that appear in our reflection proof.
-/
def mkAuxDecl (name : Name) (value type : Expr) : MetaM Unit :=
addAndCompile <| .defnDecl {
name := name,
levelParams := [],
type := type,
value := value,
hints := .abbrev,
safety := .safe
}
/--
Turn an `LratCert` into a proof that some `reflected` expression is UNSAT by providing a `verifier`
function together with a correctness theorem for it.
- `verifier` is expected to have type `α → LratCert → Bool`
- `unsat_of_verifier_eq_true` is expected to have type
`∀ (b : α) (c : LratCert), verifier b c = true → unsat b`
-/
def LratCert.toReflectionProof [ToExpr α] (cert : LratCert) (cfg : TacticContext) (reflected : α)
(verifier : Name) (unsat_of_verifier_eq_true : Name) :
MetaM Expr := do
withTraceNode `sat (fun _ => return "Compiling expr term") do
mkAuxDecl cfg.exprDef (toExpr reflected) (toTypeExpr α)
let certType := toTypeExpr LratCert
withTraceNode `sat (fun _ => return "Compiling proof certificate term") do
mkAuxDecl cfg.certDef (toExpr cert) certType
let reflectedExpr := mkConst cfg.exprDef
let certExpr := mkConst cfg.certDef
withTraceNode `sat (fun _ => return "Compiling reflection proof term") do
let auxValue := mkApp2 (mkConst verifier) reflectedExpr certExpr
mkAuxDecl cfg.reflectionDef auxValue (mkConst ``Bool)
let nativeProof :=
mkApp3
(mkConst ``Lean.ofReduceBool)
(mkConst cfg.reflectionDef)
(toExpr true)
( mkEqRefl (toExpr true))
return mkApp3 (mkConst unsat_of_verifier_eq_true) reflectedExpr certExpr nativeProof
end Frontend
end Lean.Elab.Tactic.BVDecide

View File

@@ -0,0 +1,90 @@
/-
Copyright (c) 2024 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Meta.AppBuilder
import Lean.Elab.Tactic.Simp
import Lean.Elab.Tactic.FalseOrByContra
import Lean.Elab.Tactic.BVDecide.Frontend.Attr
import Std.Tactic.BVDecide.Normalize
import Std.Tactic.BVDecide.Syntax
/-!
This module contains the implementation of `bv_normalize` which is effectively a custom `bv_normalize`
simp set that is called like this: `simp only [seval, bv_normalize]`. The rules in `bv_normalize`
fulfill two goals:
1. Turn all hypothesis involving `Bool` and `BitVec` into the form `x = true` where `x` only consists
of a operations on `Bool` and `BitVec`. In particular no `Prop` should be contained. This makes
the reflection procedure further down the pipeline much easier to implement.
2. Apply simplification rules from the Bitwuzla SMT solver.
-/
namespace Lean.Elab.Tactic.BVDecide
namespace Frontend.Normalize
open Lean.Meta
open Std.Tactic.BVDecide.Normalize
/--
The bitblaster for multiplication introduces symbolic branches over the right hand side.
If we have an expression of the form `c * x` where `c` is constant we should change it to `x * c`
such that these symbolic branches get constant folded by the AIG framework.
-/
builtin_simproc [bv_normalize] mulConst ((_ : BitVec _) * (_ : BitVec _)) := fun e => do
let_expr HMul.hMul _ _ _ _ lhs rhs := e | return .continue
let some width, _ Lean.Meta.getBitVecValue? lhs | return .continue
let new mkAppM ``HMul.hMul #[rhs, lhs]
let proof := mkApp3 (mkConst ``BitVec.mul_comm) (toExpr width) lhs rhs
return .done { expr := new, proof? := some proof }
builtin_simproc [bv_normalize] eqToBEq (((_ : Bool) = (_ : Bool))) := fun e => do
let_expr Eq _ lhs rhs := e | return .continue
match_expr rhs with
| Bool.true => return .continue
| _ =>
let beqApp mkAppM ``BEq.beq #[lhs, rhs]
let new := mkApp3 (mkConst ``Eq [1]) (mkConst ``Bool) beqApp (mkConst ``Bool.true)
let proof := mkApp2 (mkConst ``Bool.eq_to_beq) lhs rhs
return .done { expr := new, proof? := some proof }
structure Result where
goal : Option MVarId := none
stats : Simp.Stats := {}
def bvNormalize (g : MVarId) : MetaM Result := do
withTraceNode `bv (fun _ => return "Normalizing goal") do
-- Contradiction proof
let some g g.falseOrByContra | return {}
-- Normalization by simp
let bvThms bvNormalizeExt.getTheorems
let bvSimprocs bvNormalizeSimprocExt.getSimprocs
let sevalThms getSEvalTheorems
let sevalSimprocs Simp.getSEvalSimprocs
let simpCtx : Simp.Context := {
simpTheorems := #[bvThms, sevalThms]
congrTheorems := ( getSimpCongrTheorems)
}
let hyps g.getNondepPropHyps
let result?, stats simpGoal g
(ctx := simpCtx)
(simprocs := #[bvSimprocs, sevalSimprocs])
(fvarIdsToSimp := hyps)
let some (_, g) := result? | return none, stats
return some g, stats
@[builtin_tactic Lean.Parser.Tactic.bvNormalize]
def evalBVNormalize : Tactic := fun
| `(tactic| bv_normalize) => do
liftMetaFinishingTactic fun g => do
discard <| bvNormalize g
| _ => throwUnsupportedSyntax
end Frontend.Normalize
end Lean.Elab.Tactic.BVDecide

View File

@@ -5,10 +5,9 @@ Authors: Henrik Böving
-/
prelude
import Lean.Elab.Tactic.BVDecide.LRAT.Trim
import Lean.Elab.Tactic.BVDecide.LRAT.Parser
/-!
This directory contains the implementation of the LRAT parsing and trimming algorithms.
They mostly live here because they used datastructures and parsing infrastructure from `Lean`.
This directory contains the implementation of the LRAT trimming algorithms.
It lives here because it uses datastructures and parsing infrastructure from `Lean`.
Otherwise they could be put into `Std.Tactic.BVDecide.LRAT`.
-/

View File

@@ -0,0 +1,12 @@
/-
Copyright (c) 2024 University of Cambridge. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Tobias Grosser
-/
prelude
import Lean.Meta.Tactic.Simp.Attr
builtin_initialize boolToPropSimps : Lean.Meta.SimpExtension
Lean.Meta.registerSimpAttr `boolToPropSimps
"simp lemmas converting boolean expressions in terms of `decide` into propositional statements"

View File

@@ -458,6 +458,8 @@ def renameInaccessibles (mvarId : MVarId) (hs : TSyntaxArray ``binderIdent) : Ta
match lctx.getAt? j with
| none => pure ()
| some localDecl =>
if localDecl.isImplementationDetail then
continue
let inaccessible := !(extractMacroScopes localDecl.userName |>.equalScope callerScopes)
let shadowed := found.contains localDecl.userName
if inaccessible || shadowed then

View File

@@ -75,20 +75,28 @@ def congr (mvarId : MVarId) (addImplicitArgs := false) (nameSubgoals := true) :
@[builtin_tactic Lean.Parser.Tactic.Conv.congr] def evalCongr : Tactic := fun _ => do
replaceMainGoal <| List.filterMap id ( congr ( getMainGoal))
-- mvarIds is the list of goals produced by congr. We only want to change the one at position `i`
-- so this closes all other equality goals with `rfl.`. There are non-equality goals produced
-- by `congr` (e.g. dependent instances), thes are kept as goals.
private def selectIdx (tacticName : String) (mvarIds : List (Option MVarId)) (i : Int) :
TacticM Unit := do
if i >= 0 then
let i := i.toNat
if h : i < mvarIds.length then
let mut otherGoals := #[]
for mvarId? in mvarIds, j in [:mvarIds.length] do
match mvarId? with
| none => pure ()
| some mvarId =>
if i != j then
mvarId.refl
if ( mvarId.getType').isEq then
mvarId.refl
else
-- If its not an equality, it's likely a class constraint, to be left open
otherGoals := otherGoals.push mvarId
match mvarIds[i] with
| none => throwError "cannot select argument"
| some mvarId => replaceMainGoal [mvarId]
| some mvarId => replaceMainGoal (mvarId :: otherGoals.toList)
return ()
throwError "invalid '{tacticName}' conv tactic, application has only {mvarIds.length} (nondependent) argument(s)"

View File

@@ -10,7 +10,7 @@ import Lean.Elab.Tactic.RCases
import Lean.Elab.Tactic.Repeat
import Lean.Elab.Tactic.BuiltinTactic
import Lean.Elab.Command
import Lean.Linter.Util
import Lean.Linter.Basic
/-!
# Implementation of the `@[ext]` attribute

View File

@@ -31,16 +31,16 @@ open Lean Meta Elab Tactic
-- but fall back to a classical instance. When it is `some true`, we always use the classical instance.
-- When it is `some false`, if there is no `Decidable` instance we don't introduce the double negation,
-- and fall back to `False.elim`.
partial def falseOrByContra (g : MVarId) (useClassical : Option Bool := none) : MetaM MVarId := do
partial def falseOrByContra (g : MVarId) (useClassical : Option Bool := none) : MetaM (Option MVarId) := do
let ty whnfR ( g.getType)
match ty with
| .const ``False _ => pure g
| .forallE _ _ _ _
| .const ``False _ => return g
| .forallE ..
| .app (.const ``Not _) _ =>
-- We set the transparency back to default; otherwise this breaks when run by a `simp` discharger.
falseOrByContra ( withTransparency default g.intro1P).2 useClassical
| _ =>
let gs if isProp ty then
let gs if ( isProp ty) then
match useClassical with
| some true => some <$> g.applyConst ``Classical.byContradiction
| some false =>
@@ -51,12 +51,15 @@ partial def falseOrByContra (g : MVarId) (useClassical : Option Bool := none) :
catch _ => some <$> g.applyConst ``Classical.byContradiction
else
pure none
if let some gs := gs then
let [g] := gs | panic! "expected one subgoal"
pure ( g.intro1).2
else
let [g] g.applyConst ``False.elim | panic! "expected one sugoal"
pure g
match gs with
| some [] => return none
| some [g] => return some ( g.intro1).2
| some _ => panic! "expected at most one sugoal"
| none =>
match ( g.applyConst ``False.elim) with
| [] => return none
| [g] => return some g
| _ => panic! "expected at most one sugoal"
@[builtin_tactic Lean.Parser.Tactic.falseOrByContra]
def elabFalseOrByContra : Tactic

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