isabelle-query 0.6.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isabelle_query-0.6.7/.gitignore +51 -0
- isabelle_query-0.6.7/CONTRIBUTING.md +102 -0
- isabelle_query-0.6.7/LICENSE +21 -0
- isabelle_query-0.6.7/METRICS.md +156 -0
- isabelle_query-0.6.7/PKG-INFO +159 -0
- isabelle_query-0.6.7/README.md +131 -0
- isabelle_query-0.6.7/SCANNING.md +165 -0
- isabelle_query-0.6.7/configs/m3.toml +15 -0
- isabelle_query-0.6.7/pyproject.toml +105 -0
- isabelle_query-0.6.7/src/isabelle_query/__init__.py +12 -0
- isabelle_query-0.6.7/src/isabelle_query/_census_namespace.py +202 -0
- isabelle_query-0.6.7/src/isabelle_query/_corpus_constants.py +27 -0
- isabelle_query-0.6.7/src/isabelle_query/_dump_namespace.ML +54 -0
- isabelle_query-0.6.7/src/isabelle_query/_isabelle_namespace.py +114 -0
- isabelle_query-0.6.7/src/isabelle_query/_namespace_resolve.py +369 -0
- isabelle_query-0.6.7/src/isabelle_query/_notation.py +44 -0
- isabelle_query-0.6.7/src/isabelle_query/cli.py +1550 -0
- isabelle_query-0.6.7/src/isabelle_query/commands.py +1679 -0
- isabelle_query-0.6.7/src/isabelle_query/common.py +103 -0
- isabelle_query-0.6.7/src/isabelle_query/graph.py +822 -0
- isabelle_query-0.6.7/src/isabelle_query/model.py +413 -0
- isabelle_query-0.6.7/src/isabelle_query/parsing.py +2285 -0
- isabelle_query-0.6.7/src/isabelle_query/render.py +373 -0
- isabelle_query-0.6.7/src/isabelle_query/shape.py +2143 -0
- isabelle_query-0.6.7/src/isabelle_query/shape_cmds.py +490 -0
- isabelle_query-0.6.7/tests/README.md +147 -0
- isabelle_query-0.6.7/tests/fixtures/Nested.thy +28 -0
- isabelle_query-0.6.7/tests/fixtures/Owners.thy +18 -0
- isabelle_query-0.6.7/tests/fixtures/Shape.thy +108 -0
- isabelle_query-0.6.7/tests/fixtures/m3_configs.toml +9 -0
- isabelle_query-0.6.7/tests/support.py +164 -0
- isabelle_query-0.6.7/tests/test_annotations.py +302 -0
- isabelle_query-0.6.7/tests/test_bad_root.py +164 -0
- isabelle_query-0.6.7/tests/test_balanced.py +72 -0
- isabelle_query-0.6.7/tests/test_base_logic.py +65 -0
- isabelle_query-0.6.7/tests/test_bfs_depths.py +154 -0
- isabelle_query-0.6.7/tests/test_call_graph.py +215 -0
- isabelle_query-0.6.7/tests/test_census_sessions.py +351 -0
- isabelle_query-0.6.7/tests/test_cli_context_flag.py +62 -0
- isabelle_query-0.6.7/tests/test_cli_families.py +106 -0
- isabelle_query-0.6.7/tests/test_cli_names_flag.py +42 -0
- isabelle_query-0.6.7/tests/test_cli_no_global_only_options.py +77 -0
- isabelle_query-0.6.7/tests/test_cli_root_flag.py +80 -0
- isabelle_query-0.6.7/tests/test_cli_version.py +96 -0
- isabelle_query-0.6.7/tests/test_cli_with_comments.py +65 -0
- isabelle_query-0.6.7/tests/test_corpus.py +138 -0
- isabelle_query-0.6.7/tests/test_declared_names.py +533 -0
- isabelle_query-0.6.7/tests/test_deps_qualified.py +137 -0
- isabelle_query-0.6.7/tests/test_discover_roots.py +150 -0
- isabelle_query-0.6.7/tests/test_enclosing.py +306 -0
- isabelle_query-0.6.7/tests/test_enclosing_blocks.py +200 -0
- isabelle_query-0.6.7/tests/test_fact_citations.py +218 -0
- isabelle_query-0.6.7/tests/test_grep_window.py +80 -0
- isabelle_query-0.6.7/tests/test_heaps_dirs.py +211 -0
- isabelle_query-0.6.7/tests/test_indented_decls.py +235 -0
- isabelle_query-0.6.7/tests/test_induction.py +258 -0
- isabelle_query-0.6.7/tests/test_instantiation_spans.py +182 -0
- isabelle_query-0.6.7/tests/test_keywords.py +173 -0
- isabelle_query-0.6.7/tests/test_known_failures.py +61 -0
- isabelle_query-0.6.7/tests/test_largest.py +152 -0
- isabelle_query-0.6.7/tests/test_layout_surface.py +129 -0
- isabelle_query-0.6.7/tests/test_lines_forms.py +78 -0
- isabelle_query-0.6.7/tests/test_live_source.py +216 -0
- isabelle_query-0.6.7/tests/test_locus_format.py +170 -0
- isabelle_query-0.6.7/tests/test_methods.py +124 -0
- isabelle_query-0.6.7/tests/test_names.py +346 -0
- isabelle_query-0.6.7/tests/test_namespace_cache.py +260 -0
- isabelle_query-0.6.7/tests/test_namespace_fallback.py +115 -0
- isabelle_query-0.6.7/tests/test_nonisar_regions.py +822 -0
- isabelle_query-0.6.7/tests/test_open_ranges.py +192 -0
- isabelle_query-0.6.7/tests/test_outer_source.py +186 -0
- isabelle_query-0.6.7/tests/test_perf.py +153 -0
- isabelle_query-0.6.7/tests/test_preamble_attribution.py +121 -0
- isabelle_query-0.6.7/tests/test_proof_line.py +428 -0
- isabelle_query-0.6.7/tests/test_session_theories.py +149 -0
- isabelle_query-0.6.7/tests/test_shadowed_names.py +170 -0
- isabelle_query-0.6.7/tests/test_shape.py +999 -0
- isabelle_query-0.6.7/tests/test_shape_cli.py +282 -0
- isabelle_query-0.6.7/tests/test_shape_inline.py +165 -0
- isabelle_query-0.6.7/tests/test_show_inline_proof.py +102 -0
- isabelle_query-0.6.7/tests/test_src_doc_attribution.py +157 -0
- isabelle_query-0.6.7/tests/test_statement_slice.py +196 -0
- isabelle_query-0.6.7/tests/test_stdin_path.py +245 -0
- isabelle_query-0.6.7/tests/test_summary_aggregate.py +119 -0
- isabelle_query-0.6.7/tests/test_targets.py +395 -0
- isabelle_query-0.6.7/tests/test_thy_header.py +216 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Editor / OS / agent
|
|
14
|
+
.claude/*
|
|
15
|
+
!.claude/memory/
|
|
16
|
+
.DS_Store
|
|
17
|
+
|
|
18
|
+
# Reference papers (not distributed with the tool)
|
|
19
|
+
papers/
|
|
20
|
+
|
|
21
|
+
# End-of-session handoff scratchpad — rewritten each session, survives compaction.
|
|
22
|
+
prompt.md
|
|
23
|
+
|
|
24
|
+
# Generated data from batch runs — never tracked (regenerable, and bulky).
|
|
25
|
+
data/
|
|
26
|
+
|
|
27
|
+
# namespace_cache.py prototype scratch cache (QUERY_CACHE_DIR demo target).
|
|
28
|
+
.nscache/
|
|
29
|
+
|
|
30
|
+
# Scratch file for commit message bodies (see .claude/memory)
|
|
31
|
+
.commit-msg
|
|
32
|
+
|
|
33
|
+
# Drafted issue replies, written to be pasted into GitHub and then deleted.
|
|
34
|
+
# Ignored so a `git add -A` cannot sweep one into a commit.
|
|
35
|
+
.issue-*.md
|
|
36
|
+
|
|
37
|
+
# Session scratch: probe output, timing logs, mutation harnesses, and the
|
|
38
|
+
# throwaway session roots / HEAD checkouts a corpus comparison needs. Same
|
|
39
|
+
# reason as above — `git add -A` swept a scratch `.rootless/Bare.thy` into a
|
|
40
|
+
# commit once. Scratch lives in the working tree (not /tmp, which is gated),
|
|
41
|
+
# so the working tree has to know to ignore it.
|
|
42
|
+
.scratch*
|
|
43
|
+
.headtree/
|
|
44
|
+
.rootless/
|
|
45
|
+
.batchroot/
|
|
46
|
+
.census-*
|
|
47
|
+
.compare*
|
|
48
|
+
.mutate*
|
|
49
|
+
.timing*
|
|
50
|
+
.repro*
|
|
51
|
+
.variants*
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Contributing to isabelle-query
|
|
2
|
+
|
|
3
|
+
Normative rules for changing the tool. Orientation (what `query` is, how the
|
|
4
|
+
package is laid out) is in `CLAUDE.md`. User-facing docs are `README.md` (the
|
|
5
|
+
CLI surface), `SCANNING.md` (what counts as a declaration, a citation and a
|
|
6
|
+
session) and `METRICS.md` (the `query shape` reference).
|
|
7
|
+
|
|
8
|
+
## Where design decisions are recorded
|
|
9
|
+
|
|
10
|
+
**In commit messages.** They carry the reasoning, the rejected alternatives and
|
|
11
|
+
the before/after, and they are the only record that cannot drift from the code.
|
|
12
|
+
Cross-reference a body of work with a stable `[tag]` handle, and recover it with
|
|
13
|
+
|
|
14
|
+
git log --grep='\[grep-owner-span\]'
|
|
15
|
+
|
|
16
|
+
`todo.md` holds only *open* work — things not yet done. It is not a changelog.
|
|
17
|
+
If you want to know why something is the way it is, read the commits.
|
|
18
|
+
|
|
19
|
+
**Cite only what a reader of this repo can open.** Comments, docstrings and
|
|
20
|
+
config headers must not reference paths outside it — a pointer the reader cannot
|
|
21
|
+
resolve is worse than no pointer, and the design material it names may be
|
|
22
|
+
unpublished. For the proof-shape metrics the public authority is
|
|
23
|
+
`src/isabelle_query/shape.py` itself (definitions at each metric) plus the
|
|
24
|
+
`M1`–`M6` table in `METRICS.md`. Check with:
|
|
25
|
+
|
|
26
|
+
grep -rn 'docs/' --include='*.py' --include='*.toml' src tests scripts configs
|
|
27
|
+
|
|
28
|
+
## CLI contract (follow when adding or changing commands)
|
|
29
|
+
|
|
30
|
+
Two families, each matching an external convention; a command's primary
|
|
31
|
+
positional decides which one it is.
|
|
32
|
+
|
|
33
|
+
- **lookup** (git/brew: `git show REF...`, `brew deps FORMULA`) — the
|
|
34
|
+
primary positional is a **subject** (entry/theory name), one-or-more,
|
|
35
|
+
reported in turn. Add it with `_add_subject_list_arg`. **No trailing
|
|
36
|
+
PATH positionals**: "who calls X" is corpus-global, so scope with the
|
|
37
|
+
global `-R/--root` and narrow with *semantic* flags (`--external`,
|
|
38
|
+
`-r/--recursive`), never a file subset. Members: `show`, `callers`,
|
|
39
|
+
`callees`, `deps`, `uses`, `theory`, `defs`, `outline`, `methods`.
|
|
40
|
+
- **search** (grep/rg: `grep PAT PATH...`) — the primary positional is a
|
|
41
|
+
pattern (or nothing), and **paths are the trailing positionals**, added
|
|
42
|
+
with `_add_path_files_arg` (resolved by `_load_sections`). Members:
|
|
43
|
+
`grep`, `largest`, `sorry` (and `find` once it gains PATH/`--theory`
|
|
44
|
+
scope under `[theory-refs]`).
|
|
45
|
+
|
|
46
|
+
**Never return an empty success for a question you could not ask.** A silent
|
|
47
|
+
zero is indistinguishable from an honest zero, so a caller cannot tell a broken
|
|
48
|
+
run from a real one — `query -R /typo shape census` once printed nothing and
|
|
49
|
+
exited 0, and a shell path-expansion bug turned that into a run of plausible
|
|
50
|
+
zero-record censuses. A root that cannot be read reports on stderr and exits
|
|
51
|
+
`2`, a code deliberately distinct from `1`.
|
|
52
|
+
|
|
53
|
+
Shared-feature help text comes from one helper each, so wording can't
|
|
54
|
+
drift command-to-command — always add a feature through its helper, never
|
|
55
|
+
inline:
|
|
56
|
+
|
|
57
|
+
| helper | feature |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `_add_subject_list_arg` | subject list |
|
|
60
|
+
| `_add_path_files_arg` | trailing `PATH` |
|
|
61
|
+
| `_add_names_flag` | `--names` (**no `-n`** — reserved for grep's line-number meaning) |
|
|
62
|
+
| `_add_count_flag` | `-c/--count` |
|
|
63
|
+
| `_add_with_comments_flag` | `--with-comments` — the *only* prose-search toggle on `find`/`grep` (**no `-a`**, which is `_add_mode_flags`' show-all) |
|
|
64
|
+
| `_add_mode_flags` | the `-a` / `--names` / `-c` bundle |
|
|
65
|
+
| `_add_verbatim_flag` | `-V/--verbatim` |
|
|
66
|
+
| `_add_comment_flags` | `--comments-off` / `--comments-only` |
|
|
67
|
+
| `_add_context_flag` | `-U/--context` — one short flag everywhere, default per-command |
|
|
68
|
+
| `_add_drop_names_flag` | `--drop-names-upto` |
|
|
69
|
+
|
|
70
|
+
## Verification
|
|
71
|
+
|
|
72
|
+
The suite is not sufficient on its own for parser changes; unit tests cannot
|
|
73
|
+
see a scanner failing at scale. Two habits catch what they miss:
|
|
74
|
+
|
|
75
|
+
- **Diff the entry set** after any parser change — `scripts/dump_entries.py`
|
|
76
|
+
(add `--spans` when extents may move; a change can leave entries identical
|
|
77
|
+
while moving a thousand declaration ends).
|
|
78
|
+
- **Diff the discovered theory set** after any change to session or `imports`
|
|
79
|
+
parsing — and diff it **as a set, not a count**. `dump_entries.py` walks
|
|
80
|
+
`ent.rglob("*.thy")`, so it never calls `parse_thy_imports` and cannot see a
|
|
81
|
+
discovery regression: when `[thy-header]` silently dropped 72 theories it
|
|
82
|
+
reported an identical 55,838 entries. Compare
|
|
83
|
+
`{p for s in iter_sessions(root) for _, p in session_theories(s)}` against
|
|
84
|
+
the same set from `git archive <ref> | tar -x -C .scratch-head`. A count
|
|
85
|
+
alone hides a simultaneous gain and loss, which is exactly what happened.
|
|
86
|
+
- **Check a new test can fail.** Patch the behaviour it pins, run it, restore.
|
|
87
|
+
Several tests have been written that could not fail. Two traps in the loop
|
|
88
|
+
itself, both of which have produced a wrong verdict here:
|
|
89
|
+
- **Run the mutation with `PYTHONDONTWRITEBYTECODE=1`.** A mutation harness
|
|
90
|
+
rewrites the source several times a second, and CPython invalidates a
|
|
91
|
+
`.pyc` on `(mtime, size)` — so a same-second rewrite can leave the previous
|
|
92
|
+
bytecode in place and the subprocess runs code that is not on disk. That
|
|
93
|
+
reports a *live* mutation as SURVIVED. `CAUGHT` is always trustworthy;
|
|
94
|
+
`SURVIVED` is not, until the cache is off.
|
|
95
|
+
- **A survivor may be masked, not dead.** A unit test can be shadowed by a
|
|
96
|
+
downstream guard that ends the scan anyway. Before concluding a branch is
|
|
97
|
+
unreachable, diff the entry set with the mutation applied: two branches
|
|
98
|
+
that survived every unit test moved 706 and 330 entries.
|
|
99
|
+
When a branch really is unreachable from valid input, say so in the comment
|
|
100
|
+
and pin it at the helper/regex level, rather than leaving it looking tested.
|
|
101
|
+
|
|
102
|
+
`pytest -q` stays green after every change.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 András Salamon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Proof-shape metrics — `query shape`
|
|
2
|
+
|
|
3
|
+
A command reference for the `shape` family. Where the other subcommands ask
|
|
4
|
+
*what is declared* and *which facts cite which*, `shape` measures the **shape of
|
|
5
|
+
individual proof steps**: how big a step is, how deeply nested, how many facts
|
|
6
|
+
it holds at once, how much it re-says, and how it is discharged.
|
|
7
|
+
|
|
8
|
+
Everything here is **source-level** — computed by parsing `.thy` text, with no
|
|
9
|
+
Isabelle build and no proof replay. Each value is either *exact* at source level
|
|
10
|
+
or a token-based *estimator*; estimator columns carry an `_est` suffix, so the
|
|
11
|
+
two are never silently conflated.
|
|
12
|
+
|
|
13
|
+
The authoritative definitions — each metric's exact rule, the term-level
|
|
14
|
+
semantics an estimator approximates, and its known approximations — are in
|
|
15
|
+
`src/isabelle_query/shape.py`, stated at the metric. This file decodes the
|
|
16
|
+
identifiers and says how to invoke them; it does not restate the definitions,
|
|
17
|
+
which would drift.
|
|
18
|
+
|
|
19
|
+
## Views
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
query shape summary # per-theory aggregate table
|
|
23
|
+
query shape steps [THEORY[:A..B]] # per-step records
|
|
24
|
+
query shape lemma <name>... # one proof: every step + M6 curve
|
|
25
|
+
query shape widest [-N n] [PATH...] # the widest steps
|
|
26
|
+
query shape census # stream per-proof JSONL over a corpus
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| view | takes | output |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `summary` | — | one row per theory |
|
|
32
|
+
| `steps` | an optional `THEORY` or `THEORY:A..B` span | one row per Isar step |
|
|
33
|
+
| `lemma` | one or more entry names | every step, an aggregate footer, and the M6 curve |
|
|
34
|
+
| `widest` | optional trailing `PATH`s | the *n* widest steps by a chosen metric |
|
|
35
|
+
| `census` | — | one JSON object per proof, streamed and resumable |
|
|
36
|
+
|
|
37
|
+
Flags, beyond the global `-R/--root` and `--version`:
|
|
38
|
+
|
|
39
|
+
| flag | views | meaning |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `--json` | all but `census` | one JSONL record per line instead of the table |
|
|
42
|
+
| `-a`, `--all` | `steps` | include non-goal steps (context / plumbing / closing); the default shows goal steps only, where the metrics attach |
|
|
43
|
+
| `--scope {proof,entry}` | `summary` | size columns over the proof body (default) or the whole entry including its statement, as `largest` counts |
|
|
44
|
+
| `--content {all,code,prose}` | `summary` | size columns over all lines (default), code only (prose stripped), or prose only |
|
|
45
|
+
| `-N n` | `widest` | how many steps to rank |
|
|
46
|
+
| `--metric {w2,w1,fanin,live}` | `widest` | rank by as-written token width (default), free variables, cited facts, or simultaneously-live facts |
|
|
47
|
+
| `--config TOML`, `--corpus NAME` | `steps`, `lemma` | add the M3 `frame_ratio` columns; see [Corpus configs](#corpus-configs-m3) |
|
|
48
|
+
| `--resume FILE` | `census` | skip records already present in a prior run |
|
|
49
|
+
|
|
50
|
+
## Metrics
|
|
51
|
+
|
|
52
|
+
| id | column | measures | kind |
|
|
53
|
+
|----|--------|----------|------|
|
|
54
|
+
| M1 | `w1_est` | distinct free variables in the stated proposition | estimator |
|
|
55
|
+
| — | `const_est` | distinct constants in the stated proposition (names + operator notation) | estimator |
|
|
56
|
+
| — | `const_canon_est` | `const_est` with operator glyphs canonicalised to their Isabelle constant (`\<le>`/`\<subseteq>` → one) | estimator |
|
|
57
|
+
| M2 | `w2_src` | as-written proposition width, in tokens | exact |
|
|
58
|
+
| M3 | `frame_ratio` | delta-tracing overhead (components mentioned / changed) | definitional¹ |
|
|
59
|
+
| M4 | `dag_ratio_est` | cross-step redundancy (repeated bracketed subterms / block) | estimator |
|
|
60
|
+
| M5a | `fanin` | distinct facts cited for a step | exact |
|
|
61
|
+
| M5b | `live` | named facts simultaneously live at a step | exact |
|
|
62
|
+
| M5c | `introduce`/`consume` | fact-introducing vs fact-citing lines | exact |
|
|
63
|
+
| M6 | extension curve | width remaining after naming the *k* most-repeated subterms | estimator |
|
|
64
|
+
|
|
65
|
+
¹ M3 is defined purely syntactically, so it has no estimator/reference split —
|
|
66
|
+
but it needs a per-corpus config.
|
|
67
|
+
|
|
68
|
+
The columns group into axes, which is how the `summary` table is laid out:
|
|
69
|
+
|
|
70
|
+
| axis | asks | columns |
|
|
71
|
+
|------|------|---------|
|
|
72
|
+
| Length | how big is the proof? | `n_steps`, `n_goals`, `proof_lines`, `proof_tokens` (raw + `_code`), `entry_lines` |
|
|
73
|
+
| Depth | how deeply nested? | `depth_max` |
|
|
74
|
+
| Width | how big is one step? | `w1_est`, `const_est`/`const_canon_est`, `w2_src` |
|
|
75
|
+
| Space | how many facts held at once? | `fanin`, `live`, `introduce`/`consume` |
|
|
76
|
+
| Redundancy | how much is re-said? | `dag_ratio_est`, M6 extension curve |
|
|
77
|
+
| Automation | how is it discharged? | `trivial_frac`, `method_kinds`, induction discipline (`n_induct`, `induct_arbitrary_max`, `induct_recursion`) |
|
|
78
|
+
| Framing | how much to say to change a little? | `frame_ratio` |
|
|
79
|
+
|
|
80
|
+
They are separate measurements, not components of a single score: a proof can be
|
|
81
|
+
long and shallow, or short and wide.
|
|
82
|
+
|
|
83
|
+
## JSONL records
|
|
84
|
+
|
|
85
|
+
`--json` (and `census`) emit one JSON object per line, keyed by a stable
|
|
86
|
+
`(theory, lemma, line)` position so records join across runs without
|
|
87
|
+
re-instrumentation. Two record shapes — per-step (`steps` / `lemma` / `widest`)
|
|
88
|
+
and per-proof aggregate (`summary --json` / `census`). **The full field lists are
|
|
89
|
+
in the `shape_cmds` module docstring.**
|
|
90
|
+
|
|
91
|
+
Per-proof records also carry `session` (`null` when the load had no session
|
|
92
|
+
context), which a corpus run needs: AFP theory names are not unique across
|
|
93
|
+
entries, so `(theory, lemma)` alone cannot identify one.
|
|
94
|
+
|
|
95
|
+
## Running a census over a corpus
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
query -R AFP/thys shape census > afp.jsonl
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
One process for the whole corpus, one session at a time — do not loop `query`
|
|
102
|
+
over entries in a shell, which pays interpreter and process startup per entry
|
|
103
|
+
and dominates the run. Memory is bounded by the largest single session rather
|
|
104
|
+
than by the corpus, and a session that fails to parse is reported on stderr and
|
|
105
|
+
skipped rather than aborting the run.
|
|
106
|
+
|
|
107
|
+
Output is flushed per session, so a killed run leaves a valid JSONL prefix, and
|
|
108
|
+
`--resume FILE` skips records already present:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
query -R AFP/thys shape census --resume afp.jsonl >> afp.jsonl
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Exit status follows the usual rule: `2` if no session could be read at all,
|
|
115
|
+
`0` with a stderr summary if some were skipped, `0` in silence for an honest
|
|
116
|
+
zero. A directory with no `ROOT` is still a corpus (the `*.thy` fallback),
|
|
117
|
+
censused as a single unnamed group.
|
|
118
|
+
|
|
119
|
+
## The method table, and where it limits the numbers
|
|
120
|
+
|
|
121
|
+
The one place Isabelle informs a result is the method/attribute table that tells
|
|
122
|
+
a proof method (`by auto`) from a fact citation.
|
|
123
|
+
|
|
124
|
+
`census` uses a fixed, committed **approximate** table — the union of the
|
|
125
|
+
distribution sessions most AFP entries build on (HOL, HOL-Library, HOL-Analysis,
|
|
126
|
+
HOL-Eisbach, HOL-Decision_Procs) — so it needs no Isabelle and regenerates
|
|
127
|
+
identically anywhere. Being fixed, it is an approximation: methods an entry
|
|
128
|
+
defines itself (an Eisbach `cs_concl`) or that come from a niche logic (Nominal's
|
|
129
|
+
`nominal_induct`) are not in it, so the **Automation** axis under-counts on those
|
|
130
|
+
steps. This affects a few percent of proofs, only in method-defining entries;
|
|
131
|
+
fan-in and width are unaffected.
|
|
132
|
+
|
|
133
|
+
The per-project verbs (`callers` / `callees` / `unused` / `methods` / `shape`)
|
|
134
|
+
instead resolve a **session-exact** table from a loaded Isabelle heap when one is
|
|
135
|
+
built — cached, and never a build — falling back to the committed table with a
|
|
136
|
+
warning. Heaps are looked for where Isabelle looks: `$ISABELLE_HEAPS` first, then
|
|
137
|
+
`$ISABELLE_HEAPS_SYSTEM`, so a stock install with nothing built locally still
|
|
138
|
+
resolves the exact table from the distribution's own `HOL`.
|
|
139
|
+
|
|
140
|
+
A second, smaller committed table backs `const_canon_est`: a notation table
|
|
141
|
+
mapping operator glyphs to their Isabelle constant (`\<le>` → `less_eq`),
|
|
142
|
+
resolved once from a heap by `scripts/extract_notation.py` and checked in, so
|
|
143
|
+
runtime stays pure-Python. A glyph the table does not carry falls back to itself,
|
|
144
|
+
so it only ever dedups, never loses, a constant — and the raw-glyph `const_est`
|
|
145
|
+
beside it stays table-independent.
|
|
146
|
+
|
|
147
|
+
## Corpus configs (M3)
|
|
148
|
+
|
|
149
|
+
Source parsing cannot resolve types, so M3's "configuration type" degrades to a
|
|
150
|
+
per-corpus list of selector / constructor / relation names. Supply it as TOML —
|
|
151
|
+
one `[corpus]` table per entry — and pass `--config FILE [--corpus NAME]` to
|
|
152
|
+
`steps` / `lemma` to add the `frame_ratio` columns. `configs/m3.toml` ships a
|
|
153
|
+
`Cook_Levin` table. Without a config the rest of the family runs unchanged.
|
|
154
|
+
|
|
155
|
+
When comparing across corpora, normalize per proof or per goal step, never per
|
|
156
|
+
kloc — line counts confound formatting.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: isabelle-query
|
|
3
|
+
Version: 0.6.7
|
|
4
|
+
Summary: Query the live theory index of an Isabelle/Isar project — entries, call graph, dependencies, and dead code — by parsing .thy files on every invocation.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ott2/isabelle-query
|
|
6
|
+
Project-URL: Source, https://github.com/ott2/isabelle-query
|
|
7
|
+
Author-email: András Salamon <Andras.Salamon@st-andrews.ac.uk>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: afp,formal-verification,isabelle,isar,proof-engineering,theorem-proving
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering
|
|
24
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: isabelle-layout>=0.2.2
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# isabelle-query
|
|
30
|
+
|
|
31
|
+
`query` is a command-line tool for **querying an Isabelle/Isar project** — its
|
|
32
|
+
entries (definitions, lemmas, theorems, datatypes), call graph, theory
|
|
33
|
+
dependencies, outstanding `sorry`s, dead code, and the shape of its proofs.
|
|
34
|
+
|
|
35
|
+
It parses the project's `.thy` sources on every invocation, so results always
|
|
36
|
+
match the current tree: **no Isabelle build, no proof replay**. A large project
|
|
37
|
+
parses in a fraction of a second, and the whole AFP in a couple of minutes. It is
|
|
38
|
+
aimed at projects big enough that grep-and-examine has stopped working — AFP
|
|
39
|
+
entries, the AFP itself, or industrial verification.
|
|
40
|
+
|
|
41
|
+
Pure Python. One runtime dependency,
|
|
42
|
+
[isabelle-layout](https://pypi.org/project/isabelle-layout/) — the ROOT and
|
|
43
|
+
theory-header parser, split out so that reading an Isabelle project's structure
|
|
44
|
+
does not require installing a CLI. `pip` fetches it for you.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
query summary # theory overview table (-S: corpus/session aggregate)
|
|
50
|
+
query theory MyTheory # entries in a theory (-n for terse names)
|
|
51
|
+
query find <regex> # search entry names (--statement: search statements)
|
|
52
|
+
query show <name> # a named entry's declaration + body
|
|
53
|
+
query enclosing FILE:LINE # which entry + proof block owns a line; inverse of outline
|
|
54
|
+
query callers <name> [-r] # who references a name (reverse; -r = transitive)
|
|
55
|
+
query callees <name> [-r] # what a name references (forward)
|
|
56
|
+
query deps <theory> [-r] # what a theory imports (forward; reverse: uses)
|
|
57
|
+
query sorry # outstanding sorry's
|
|
58
|
+
query unused # dead-code / unused-entry analysis
|
|
59
|
+
query shape <view> # proof-shape metrics (summary|steps|lemma|widest|census)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Every subcommand takes `-h`; `query -h` lists all 18.
|
|
63
|
+
|
|
64
|
+
## Examples
|
|
65
|
+
|
|
66
|
+
Point `query` at any session directory with `-R` (or `--root`):
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
query -R AFP/thys largest # the biggest entries, by line count
|
|
70
|
+
query -R AFP/thys callers metric_domain_tfin_def # every proof step that cites a fact
|
|
71
|
+
query -R AFP/thys find --statement tfin # lemmas *stated about* tfin, whatever their name
|
|
72
|
+
query -R AFP/thys enclosing Tfin.thy:412 # the lemma and nearest proof block a build error sits in
|
|
73
|
+
query -R AFP/thys enclosing Tfin:88..140 # every entry a diff hunk touches
|
|
74
|
+
query -R AFP/thys grep simp Tfin.thy:88..140 # search just a hunk
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Locations and spans share one grammar (`theory:line`, `theory:A..B`), so the
|
|
78
|
+
tool's output is valid input: a locus from `callers` / `sorry` pastes into
|
|
79
|
+
`enclosing`, and a span from `outline` / `largest` — or a proof block from
|
|
80
|
+
`enclosing`'s own drill-down (`▸ have key 11..14`) — pastes into `lines`.
|
|
81
|
+
|
|
82
|
+
## What it reads
|
|
83
|
+
|
|
84
|
+
Only **live Isar text**. A name in a comment, a `\<comment>` note, a `text`
|
|
85
|
+
block or an `ML` body is not a citation, so it never invents a caller or hides a
|
|
86
|
+
dead lemma — and a `definition` left behind in a comment is not an entry.
|
|
87
|
+
Regions are found by a character-level scan, not by line, so
|
|
88
|
+
`by (simp add: foo) (* not bar *)` keeps `foo` and drops `bar`.
|
|
89
|
+
|
|
90
|
+
Layout carries no meaning: Isar is whitespace-insensitive, so a declaration is
|
|
91
|
+
recognised wherever a *command* can start, at any indentation and any block
|
|
92
|
+
depth. Discovery loads what `isabelle build` compiles — each session's declared
|
|
93
|
+
theories plus the closure of their in-entry imports.
|
|
94
|
+
|
|
95
|
+
See **[SCANNING.md](SCANNING.md)** for the details: locale scope, method names
|
|
96
|
+
that collide with fact names, corpus aggregation, and the prose view.
|
|
97
|
+
|
|
98
|
+
## Proof-shape metrics
|
|
99
|
+
|
|
100
|
+
`query shape` measures the shape of individual proof steps — how big a step is,
|
|
101
|
+
how deeply nested, how many facts it holds at once, how much is re-said, and how
|
|
102
|
+
it is discharged. All source-level, no build.
|
|
103
|
+
|
|
104
|
+
```sh
|
|
105
|
+
query shape summary # per-theory aggregate table
|
|
106
|
+
query shape lemma <name> # one proof: every step
|
|
107
|
+
query -R AFP/thys shape census # per-proof JSONL over a whole corpus
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
See **[METRICS.md](METRICS.md)** for the command reference, the metric table, and
|
|
111
|
+
the JSONL record schema.
|
|
112
|
+
|
|
113
|
+
## Exit status
|
|
114
|
+
|
|
115
|
+
`0` the command ran; `1` the request could not be resolved (unknown theory or
|
|
116
|
+
path, no subcommand); `2` bad usage — an argparse error, or **a root that could
|
|
117
|
+
not be read**; `141` a downstream reader closed the pipe (`query shape census |
|
|
118
|
+
head`), as a shell reports for SIGPIPE.
|
|
119
|
+
|
|
120
|
+
A root that yields no theories is reported on stderr and never as an empty
|
|
121
|
+
success, so a script can tell a broken run from an honestly empty one:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
$ query -R /typo/path shape census
|
|
125
|
+
query: /typo/path: no such directory (given to -R/--root)
|
|
126
|
+
$ echo $?
|
|
127
|
+
2
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Installation
|
|
131
|
+
|
|
132
|
+
Requires Python 3.9 or greater. Installs a `query` command on your `PATH`, and
|
|
133
|
+
pulls `isabelle-layout` from PyPI.
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
pip install isabelle-query # from PyPI
|
|
137
|
+
pip install . # from a checkout
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
An editable install, for working on the tool itself:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
git clone https://github.com/ott2/isabelle-query
|
|
144
|
+
cd isabelle-query
|
|
145
|
+
python -m venv .venv && source .venv/bin/activate # optional but recommended
|
|
146
|
+
pip install -e .
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Documentation
|
|
150
|
+
|
|
151
|
+
| file | what |
|
|
152
|
+
|---|---|
|
|
153
|
+
| [SCANNING.md](SCANNING.md) | how `query` reads a project — what counts as a declaration, a citation, and a session |
|
|
154
|
+
| [METRICS.md](METRICS.md) | `query shape` command reference and metric definitions |
|
|
155
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | the CLI contract and where design decisions are recorded |
|
|
156
|
+
|
|
157
|
+
## Authors & license
|
|
158
|
+
|
|
159
|
+
By András Salamon, with Claude Opus 4.6, 4.7, 4.8, and 5. [MIT](LICENSE).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# isabelle-query
|
|
2
|
+
|
|
3
|
+
`query` is a command-line tool for **querying an Isabelle/Isar project** — its
|
|
4
|
+
entries (definitions, lemmas, theorems, datatypes), call graph, theory
|
|
5
|
+
dependencies, outstanding `sorry`s, dead code, and the shape of its proofs.
|
|
6
|
+
|
|
7
|
+
It parses the project's `.thy` sources on every invocation, so results always
|
|
8
|
+
match the current tree: **no Isabelle build, no proof replay**. A large project
|
|
9
|
+
parses in a fraction of a second, and the whole AFP in a couple of minutes. It is
|
|
10
|
+
aimed at projects big enough that grep-and-examine has stopped working — AFP
|
|
11
|
+
entries, the AFP itself, or industrial verification.
|
|
12
|
+
|
|
13
|
+
Pure Python. One runtime dependency,
|
|
14
|
+
[isabelle-layout](https://pypi.org/project/isabelle-layout/) — the ROOT and
|
|
15
|
+
theory-header parser, split out so that reading an Isabelle project's structure
|
|
16
|
+
does not require installing a CLI. `pip` fetches it for you.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
query summary # theory overview table (-S: corpus/session aggregate)
|
|
22
|
+
query theory MyTheory # entries in a theory (-n for terse names)
|
|
23
|
+
query find <regex> # search entry names (--statement: search statements)
|
|
24
|
+
query show <name> # a named entry's declaration + body
|
|
25
|
+
query enclosing FILE:LINE # which entry + proof block owns a line; inverse of outline
|
|
26
|
+
query callers <name> [-r] # who references a name (reverse; -r = transitive)
|
|
27
|
+
query callees <name> [-r] # what a name references (forward)
|
|
28
|
+
query deps <theory> [-r] # what a theory imports (forward; reverse: uses)
|
|
29
|
+
query sorry # outstanding sorry's
|
|
30
|
+
query unused # dead-code / unused-entry analysis
|
|
31
|
+
query shape <view> # proof-shape metrics (summary|steps|lemma|widest|census)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Every subcommand takes `-h`; `query -h` lists all 18.
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
Point `query` at any session directory with `-R` (or `--root`):
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
query -R AFP/thys largest # the biggest entries, by line count
|
|
42
|
+
query -R AFP/thys callers metric_domain_tfin_def # every proof step that cites a fact
|
|
43
|
+
query -R AFP/thys find --statement tfin # lemmas *stated about* tfin, whatever their name
|
|
44
|
+
query -R AFP/thys enclosing Tfin.thy:412 # the lemma and nearest proof block a build error sits in
|
|
45
|
+
query -R AFP/thys enclosing Tfin:88..140 # every entry a diff hunk touches
|
|
46
|
+
query -R AFP/thys grep simp Tfin.thy:88..140 # search just a hunk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Locations and spans share one grammar (`theory:line`, `theory:A..B`), so the
|
|
50
|
+
tool's output is valid input: a locus from `callers` / `sorry` pastes into
|
|
51
|
+
`enclosing`, and a span from `outline` / `largest` — or a proof block from
|
|
52
|
+
`enclosing`'s own drill-down (`▸ have key 11..14`) — pastes into `lines`.
|
|
53
|
+
|
|
54
|
+
## What it reads
|
|
55
|
+
|
|
56
|
+
Only **live Isar text**. A name in a comment, a `\<comment>` note, a `text`
|
|
57
|
+
block or an `ML` body is not a citation, so it never invents a caller or hides a
|
|
58
|
+
dead lemma — and a `definition` left behind in a comment is not an entry.
|
|
59
|
+
Regions are found by a character-level scan, not by line, so
|
|
60
|
+
`by (simp add: foo) (* not bar *)` keeps `foo` and drops `bar`.
|
|
61
|
+
|
|
62
|
+
Layout carries no meaning: Isar is whitespace-insensitive, so a declaration is
|
|
63
|
+
recognised wherever a *command* can start, at any indentation and any block
|
|
64
|
+
depth. Discovery loads what `isabelle build` compiles — each session's declared
|
|
65
|
+
theories plus the closure of their in-entry imports.
|
|
66
|
+
|
|
67
|
+
See **[SCANNING.md](SCANNING.md)** for the details: locale scope, method names
|
|
68
|
+
that collide with fact names, corpus aggregation, and the prose view.
|
|
69
|
+
|
|
70
|
+
## Proof-shape metrics
|
|
71
|
+
|
|
72
|
+
`query shape` measures the shape of individual proof steps — how big a step is,
|
|
73
|
+
how deeply nested, how many facts it holds at once, how much is re-said, and how
|
|
74
|
+
it is discharged. All source-level, no build.
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
query shape summary # per-theory aggregate table
|
|
78
|
+
query shape lemma <name> # one proof: every step
|
|
79
|
+
query -R AFP/thys shape census # per-proof JSONL over a whole corpus
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
See **[METRICS.md](METRICS.md)** for the command reference, the metric table, and
|
|
83
|
+
the JSONL record schema.
|
|
84
|
+
|
|
85
|
+
## Exit status
|
|
86
|
+
|
|
87
|
+
`0` the command ran; `1` the request could not be resolved (unknown theory or
|
|
88
|
+
path, no subcommand); `2` bad usage — an argparse error, or **a root that could
|
|
89
|
+
not be read**; `141` a downstream reader closed the pipe (`query shape census |
|
|
90
|
+
head`), as a shell reports for SIGPIPE.
|
|
91
|
+
|
|
92
|
+
A root that yields no theories is reported on stderr and never as an empty
|
|
93
|
+
success, so a script can tell a broken run from an honestly empty one:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
$ query -R /typo/path shape census
|
|
97
|
+
query: /typo/path: no such directory (given to -R/--root)
|
|
98
|
+
$ echo $?
|
|
99
|
+
2
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Installation
|
|
103
|
+
|
|
104
|
+
Requires Python 3.9 or greater. Installs a `query` command on your `PATH`, and
|
|
105
|
+
pulls `isabelle-layout` from PyPI.
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
pip install isabelle-query # from PyPI
|
|
109
|
+
pip install . # from a checkout
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
An editable install, for working on the tool itself:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
git clone https://github.com/ott2/isabelle-query
|
|
116
|
+
cd isabelle-query
|
|
117
|
+
python -m venv .venv && source .venv/bin/activate # optional but recommended
|
|
118
|
+
pip install -e .
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
| file | what |
|
|
124
|
+
|---|---|
|
|
125
|
+
| [SCANNING.md](SCANNING.md) | how `query` reads a project — what counts as a declaration, a citation, and a session |
|
|
126
|
+
| [METRICS.md](METRICS.md) | `query shape` command reference and metric definitions |
|
|
127
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | the CLI contract and where design decisions are recorded |
|
|
128
|
+
|
|
129
|
+
## Authors & license
|
|
130
|
+
|
|
131
|
+
By András Salamon, with Claude Opus 4.6, 4.7, 4.8, and 5. [MIT](LICENSE).
|