infoductor/Infoductor/Foundation/Edit.lean
Maximus Gorog ba0a49823b Phase A: Foundation lib — meta-mirror, Edit/Context, restructure, registries
Initial commit.  Six modules extracted from
cubical-transport-hott-lean4/CubicalTransport/Algebra/ and
re-namespaced as `Infoductor`:

- Foundation/Meta.lean         — MetaCType, MetaClassifier,
                                 MetaArtifact, MetaPosition
                                 (with manual mutual decEq for
                                 MetaClassifier's recursive
                                 lattice arms)
- Foundation/Edit.lean         — Edit monad + Context comonad +
                                 distributive law +
                                 MetaClassifier.atPosition
                                 with lattice laws as theorems
- Foundation/Restructure.lean  — universal `restructure` (5-field
                                 comp-shaped operation), 6 frozen
                                 aliases, headless apply +
                                 brokenRefs / selfConsistent /
                                 Edit.guarded
- Foundation/MacroAlias.lean   — @[macroAlias] attribute +
                                 EnvExtension registry
- Foundation/MetaPath.lean     — @[metaPath src target]
                                 attribute + registry +
                                 findPathsFromSource/ToTarget
- Foundation/Methodology.lean  — @[methodology classifier]
                                 attribute + registry +
                                 deriveByTransport (walks
                                 metaPath registry) +
                                 tryEntryAsClosed primitive +
                                 cubical_search reference
                                 demonstrator

Builds clean on lean4 v4.30.0-rc2, ten oleans.  No deps beyond
Lean stdlib.  Sub-libs Tactics / Pantograph / Runner are planned
under the same lakefile.toml but not yet implemented.

Pairs with Pantograph as the conductor sits atop the pantograph
on an electric train.  "Info-ductor" — conducts information
(structure / classifications / methodology) through a Lean
codebase.

Forgejo: http://maxgit.wg:3000/max/infoductor

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 07:20:36 -06:00

179 lines
7.6 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Infoductor.Foundation.Edit — Edit monad + Context comonad
============================================================
Phase B of `docs/ALGEBRA_PLAN.md`. Defines the abstract `Edit`
monad (a thread of source mutations) and the abstract `Context`
comonad (ambient information at a code position), plus the
distributive law that lets `Context`-aware functions produce
`Edit`-valued results.
This module is *pure data*: it does not depend on the Lean LSP,
Lean.Elab APIs, or any I/O backend. LSP-specific bindings
(a real `MakeEditLinkProps.ofReplaceRange` integration; a Code
Action provider; a `Lean.Server.CodeActionContext` consumer) live
in a separate `Algebra/EditLSP.lean` module and import this one.
The headless interpreter `runHeadless` applies an `Edit` to an
in-memory source buffer and emits the post-edit text. This is
what `lake exe algebra-restructure` uses (per ALGEBRA_PLAN §5.3
No-LSP fallback).
-/
import Infoductor.Foundation.Meta
namespace Infoductor
-- ── EditOp — the leaf of every edit ──────────────────────────────────────────
/-- A single source-mutation request. The `position` field names the
spot in source; `newContent` is what to write there; `oldContent`
(if known) is recorded for audit / undo.
`EditOp` is the meta-CTerm leaf — emitted by `restructure` as part
of an `Edit α` computation. -/
structure EditOp where
position : MetaPosition
newContent : MetaArtifact
oldContent : Option MetaArtifact := none
description : String := ""
deriving Inhabited
instance : Repr EditOp where
reprPrec e _ := s!"EditOp(at={repr e.position}, new={e.newContent.toString}, desc={e.description})"
-- ── The Edit monad ──────────────────────────────────────────────────────────
-- A computation that may emit zero or more `EditOp`s. Pure value α
-- is the result of the computation; the list is the side-effect.
/-- The Edit monad: a value-and-edit-list pair. Composition appends
edit lists; `pure` emits nothing.
Implementation note: we use the bare-bones `α × List EditOp`
representation rather than the IO-threaded `run :
CodeActionContext → IO (...)` form proposed in ALGEBRA_PLAN §3.1.
The IO and LSP threading are *implementation choices* that don't
affect the algebraic structure; lifting from this pure form into
the LSP form is a `MonadLift Edit (ReaderT … IO)` instance in
`EditLSP.lean`.
See `docs/ALGEBRA_PLAN.md` §3.1 for the design discussion. -/
structure Edit (α : Type) where
result : α
ops : List EditOp
deriving Inhabited
namespace Edit
/-- Lift a value into the trivial edit (zero ops). -/
def pure {α : Type} (a : α) : Edit α := { result := a, ops := [] }
/-- Sequence: concatenate edit lists. -/
def bind {α β : Type} (m : Edit α) (f : α → Edit β) : Edit β :=
let n := f m.result
{ result := n.result, ops := m.ops ++ n.ops }
/-- Emit a single edit op with no result. -/
def emit (op : EditOp) : Edit Unit := { result := (), ops := [op] }
/-- Emit several edit ops. -/
def emitMany (ops : List EditOp) : Edit Unit := { result := (), ops := ops }
instance : Monad Edit where
pure := Edit.pure
bind := Edit.bind
/-- Functor instance. Maps the result; leaves ops untouched. -/
instance : Functor Edit where
map f m := { result := f m.result, ops := m.ops }
end Edit
-- ── The Context comonad ─────────────────────────────────────────────────────
-- "What's around here?" At each meta-position, expose ambient info:
-- the current artifact, the position, the namespace, and whatever
-- additional metadata the elaborator pass populates.
/-- Ambient information at a meta-position.
`here` is the focal artifact; `pos` is its location; `siblings`
is a list of nearby artifacts that the restructuring might wish
to consult (e.g., to rename a definition consistently with its
consumers).
Per ALGEBRA_PLAN §3.1 the full Context carries `Lean.Environment`
and `QuestionGraph` fields too; those are populated only when
running inside the elaborator (post-import). The pure-data form
here works headless. -/
structure Context (α : Type) where
here : α
pos : MetaPosition
siblings : List MetaArtifact := []
deriving Inhabited
namespace Context
/-- Comonad `extract`: the focal artifact at the current position. -/
def extract {α : Type} (c : Context α) : α := c.here
/-- Comonad `extend`: relocate an `α` value to a context-dependent
`β` value, i.e. apply a context-aware function pointwise. -/
def extend {α β : Type} (f : Context α → β) (c : Context α) : Context β :=
{ here := f c, pos := c.pos, siblings := c.siblings }
instance : Functor Context where
map f c := { here := f c.here, pos := c.pos, siblings := c.siblings }
end Context
-- ── The distributive law: Context → Edit ────────────────────────────────────
-- The standard "comonad-to-monad" distributive setup. A context-
-- aware decision produces an edit; lifting it into a Context-keyed
-- Edit yields the contextualized edit.
/-- Lift a context-aware decision into an `Edit`. Per ALGEBRA_PLAN
§3.2 this is the *distributive law* between the Context comonad
and the Edit monad. -/
def Context.contextualEdit {α β : Type}
(decide : Context α → Edit β) (c : Context α) : Edit β :=
decide c
-- ── Classifier-at-position evaluation ───────────────────────────────────────
-- Decide whether a `MetaClassifier` matches a `MetaPosition`. Used
-- by `restructure` to pick `witness` vs `fallback`.
/-- Decide whether `φ` matches `pos`, syntactically. For
`.underAttribute` and `.dependencyOf` we conservatively answer
`false` here — those require Lean.Environment and live in the
LSP integration module.
The lattice operations `.meet` and `.join` are the obvious
Boolean combinations; `.always` is `true`, `.never` is `false`. -/
def MetaClassifier.atPosition : MetaClassifier → MetaPosition → Bool
| .always, _ => true
| .never, _ => false
| .atDecl n, p => decide (n = p.declName)
| .inFile s, p => decide (s = p.filePath)
| .underAttribute _, _ => false -- needs environment; resolved in LSP module
| .dependencyOf _, _ => false -- needs dep graph; resolved in LSP module
| .inNamespace n, p =>
-- syntactic prefix check: is `p.declName` lexically under `n`?
n.isPrefixOf p.declName
| .meet a b, p => a.atPosition p && b.atPosition p
| .join a b, p => a.atPosition p || b.atPosition p
/-- Lattice laws on `atPosition` — the meta-mirror of the cubical
face-formula laws (`FaceFormula.eval`). `meet` is conjunction,
`join` is disjunction, `always`/`never` are top/bottom. -/
theorem MetaClassifier.atPosition_always (p : MetaPosition) :
MetaClassifier.always.atPosition p = true := rfl
theorem MetaClassifier.atPosition_never (p : MetaPosition) :
MetaClassifier.never.atPosition p = false := rfl
theorem MetaClassifier.atPosition_meet (a b : MetaClassifier) (p : MetaPosition) :
(a.meet b).atPosition p = (a.atPosition p && b.atPosition p) := rfl
theorem MetaClassifier.atPosition_join (a b : MetaClassifier) (p : MetaPosition) :
(a.join b).atPosition p = (a.atPosition p || b.atPosition p) := rfl
end Infoductor