Each meta-mirror value renders to a Lean expression that, when elaborated, reconstructs the same value. The bridge's loop now closes at the source level: an Edit's .cterm content can be written to a .lean file and parsed back via Lean's own parser/elaborator — no custom parser required. · nameToLeanSource — Lean.Name → constructor-call source. · MetaClassifier.toLeanSource — lattice → Infoductor.MetaClassifier.* calls. · MetaCTerm.toLeanSource — full structural mirror → constructor source. · MetaArtifact.toLeanSource — artifact layer wrapping the above. Foundation.Restructure's EditOp.apply for .cterm now uses MetaCTerm.toLeanSource instead of toString (repr t). The headless interpreter writes valid Lean source. The recursive helper for Lean.Name lives at Infoductor.nameToLeanSource rather than Lean.Name.toLeanSource — defining a Lean.Name.* function inside `namespace Infoductor` would otherwise create an Infoductor.Lean namespace and shadow the global Lean library. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
192 lines
8.4 KiB
Text
192 lines
8.4 KiB
Text
/-
|
||
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
|
||
| .cterm t =>
|
||
IO.eprintln s!"[restructure] writing {path} (decl {op.position.declName}) — meta-term"
|
||
-- Render the structural meta-term as Lean source that, when
|
||
-- elaborated, reconstructs the same `MetaCTerm`. Lean's own
|
||
-- parser/elaborator is the round-trip mechanism; no custom
|
||
-- parser required.
|
||
return buf.insert path (MetaCTerm.toLeanSource t)
|
||
|
||
/-- 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
|