/- Infoductor.Foundation.Restructure — the universal macro ========================================================== Phase B of `docs/ALGEBRA_PLAN.md`. Defines the `restructure` operation — a function with the same five-field shape as `comp i A φ u t`, lifted to the meta-source-code level. Per ALGEBRA_PLAN §0: > One macro. Built from `comp`. Aliases accrue by usage; tactics > are search over a library that grows under structural-Path > declarations alone. All 32 enumerated macros from the original sketch are *frozen partial applications* of this single `restructure`; the alias layer in `Algebra/MacroAlias.lean` lets users (or the system) name recurring patterns. -/ import Infoductor.Foundation.Edit namespace Infoductor -- ── The universal `restructure` operation ─────────────────────────────────── /-- `restructure i ctx φ witness fallback` — the universal source- mutation operation. Five fields, each mirroring a field of `comp i A φ u t`: · `i : MetaPosition` — where in source. · `ctx : MetaCType` — meta-type of the artifact. · `φ : MetaClassifier` — when this restructuring applies. · `witness : MetaArtifact` — new content valid on φ. · `fallback : MetaArtifact` — existing content off-φ. The result is an `Edit Unit`: a single `EditOp` whose content is `witness` (when `φ` matches at `i`) or `fallback` (otherwise). The `ctx` argument is currently used only for documentation / diagnostics — Phase C's `@[macroAlias]` attribute consumes it when binding aliases. Future Phase D' may use it to dispatch different rendering / preview modes in the widget. -/ def restructure (i : MetaPosition) (_ctx : MetaCType) (φ : MetaClassifier) (witness fallback : MetaArtifact) : Edit Unit := let active := φ.atPosition i let chosen := if active then witness else fallback let label := if active then "hit" else "miss" Edit.emit { position := i , newContent := chosen , oldContent := none , description := s!"restructure φ={label} at {repr i}" } -- ── The 32 macros as frozen aliases ───────────────────────────────────────── -- Per ALGEBRA_PLAN §2.3 every "macro" in the original 32-item list -- is a frozen partial application. These are the foundational -- aliases; a downstream user / agent can register more via -- `@[macroAlias]` (Phase C). /-- Frozen alias: `transport_artifact i ctx w` — `restructure` with `φ = .always`, `witness = fallback = w`. Always applies; just relocates `w` to `i`. -/ @[reducible] def transport_artifact (i : MetaPosition) (ctx : MetaCType) (w : MetaArtifact) : Edit Unit := restructure i ctx .always w w /-- Frozen alias: `relocate_invariant i src dst` — moves a file's content from `src` to `dst`, classifier set to "in source file." -/ @[reducible] def relocate_invariant (decl : Lean.Name) (src dst : String) : Edit Unit := restructure { declName := decl, filePath := dst, range := none } .file (.inFile src) (.refTo decl) .empty /-- Frozen alias: `rename_throughout x y` — emit a rename edit for declaration `x` (witness on `.atDecl x`, fallback `.empty`). -/ @[reducible] def rename_throughout (oldName newName : Lean.Name) : Edit Unit := restructure { declName := oldName, filePath := "", range := none } .definition (.atDecl oldName) (.refTo newName) .empty /-- Frozen alias: `define_question_shape S` — register a new question schema as an inductive declaration. -/ @[reducible] def define_question_shape (declName : Lean.Name) (witness : MetaArtifact) : Edit Unit := restructure { declName := declName, filePath := "", range := none } .inductive_ .always witness .empty /-- Frozen alias: `compose_proof_fragments` — pure `restructure` with no freezing. This is the "raw" `restructure` exposed under a documentary name; identical in behaviour. -/ @[reducible] def compose_proof_fragments (i : MetaPosition) (ctx : MetaCType) (φ : MetaClassifier) (w f : MetaArtifact) : Edit Unit := restructure i ctx φ w f /-- Frozen alias: `materialize` — emit a piece of raw Lean source at a position. The leaf operation; everything else composes from here. -/ @[reducible] def materialize (i : MetaPosition) (ctx : MetaCType) (src : String) : Edit Unit := restructure i ctx .always (.source src) .empty -- ── Headless interpreter: apply edits to an in-memory buffer ──────────────── -- Phase 5.3 No-LSP fallback: same operations as `lake exe -- algebra-restructure` subcommands. Useful for batch CI runs. /-- A simple in-memory source buffer. Maps file paths to current contents. `applyOp` mutates the buffer in place. -/ abbrev SourceBuffer := Std.HashMap String String /-- Apply a single `EditOp` to a source buffer. For now this is a crude full-file overwrite when `position.range` is `none`; range- based partial overwrites are scheduled for the LSP-backed integration in `Algebra/EditLSP.lean`. Returns the updated buffer. Diagnostic lines printed to `IO.stderr` describe what changed. -/ def EditOp.apply (op : EditOp) (buf : SourceBuffer) : IO SourceBuffer := do let path := op.position.filePath match op.newContent with | .source s => IO.eprintln s!"[restructure] writing {path} (decl {op.position.declName})" return buf.insert path s | .empty => IO.eprintln s!"[restructure] removing {path} (decl {op.position.declName})" return buf.erase path | .refTo n => IO.eprintln s!"[restructure] {path}: rename / link → {n}" return buf | .declAt _ => IO.eprintln s!"[restructure] {path}: emit syntax (LSP-bound; headless skipped)" return buf /-- Run an `Edit` headless: apply every emitted op in order to an initial source buffer. Returns the final buffer plus the computation's value. -/ def Edit.runHeadless {α : Type} (e : Edit α) (initial : SourceBuffer) : IO (α × SourceBuffer) := do let mut buf := initial for op in e.ops do buf ← op.apply buf return (e.result, buf) -- ── Soundness guard scaffold ──────────────────────────────────────────────── -- ALGEBRA_PLAN §3.3: every Edit passes through `preserve_typing`. -- The full integration requires invoking `lean` on a fresh source -- buffer; the data-level guard here checks that no edit deletes -- a declaration that other edits in the same batch reference. /-- The names referenced by an `EditOp` (as a `MetaArtifact.refTo`). -/ def EditOp.referenced : EditOp → List Lean.Name | { newContent := .refTo n, .. } => [n] | _ => [] /-- The decl-name *removed* by an `EditOp` (when content is `.empty`). -/ def EditOp.removed : EditOp → List Lean.Name | { newContent := .empty, position := p, .. } => [p.declName] | _ => [] /-- A "broken reference" predicate: does this batch reference any name that the same batch removes? The structural check that runs in any environment, headless or not. -/ def Edit.brokenRefs {α : Type} (e : Edit α) : List Lean.Name := let removed := e.ops.flatMap EditOp.removed let referenced := e.ops.flatMap EditOp.referenced referenced.filter (· ∈ removed) /-- The structural soundness check: an `Edit` is *self-consistent* if no op references a name another op in the batch removes. Full type-checking of the post-edit buffer is the LSP-integrated layer's job (`Algebra/EditLSP.lean`). -/ def Edit.selfConsistent {α : Type} (e : Edit α) : Bool := e.brokenRefs.isEmpty /-- Guarded wrapper: aborts the edit if its self-consistency check fails. Mirrors the `Edit.guarded` shape in ALGEBRA_PLAN §3.3. -/ def Edit.guarded {α : Type} [Inhabited α] (e : Edit α) : Except String (Edit α) := if e.selfConsistent then .ok e else .error s!"restructure batch breaks references: {repr e.brokenRefs}" end Infoductor