feat: Add notes infastructure
This commit is contained in:
parent
6b42cdd4e6
commit
33ef43ed7d
11 changed files with 568 additions and 28 deletions
|
@ -86,6 +86,14 @@ def Name.lineNumber (c : Name) : MetaM Nat := do
|
|||
| some decl => pure decl.range.pos.line
|
||||
| none => panic! s!"{c} is a declaration without position"
|
||||
|
||||
/-- Given a name, returns the file name corresponding to that declaration. -/
|
||||
def Name.fileName (c : Name) : MetaM Name := do
|
||||
let env ← getEnv
|
||||
let x := env.getModuleFor? c
|
||||
match x with
|
||||
| some c => pure c
|
||||
| none => panic! s!"{c} is a declaration without position"
|
||||
|
||||
/-- Returns the location of a name. -/
|
||||
def Name.location (c : Name) : MetaM String := do
|
||||
let env ← getEnv
|
||||
|
@ -109,6 +117,28 @@ def Name.hasDocString (c : Name) : MetaM Bool := do
|
|||
| some _ => pure true
|
||||
| none => pure false
|
||||
|
||||
|
||||
/-- Given a name, returns the source code defining that name. -/
|
||||
def Name.getDeclString (name : Name) : MetaM String := do
|
||||
let env ← getEnv
|
||||
let decl ← findDeclarationRanges? name
|
||||
match decl with
|
||||
| some decl =>
|
||||
let startLine := decl.range.pos
|
||||
let endLine := decl.range.endPos
|
||||
let fileName? := env.getModuleFor? name
|
||||
match fileName? with
|
||||
| some fileName =>
|
||||
let fileContent ← IO.FS.readFile { toString := (← Name.toFile fileName)}
|
||||
let fileMap := fileContent.toFileMap
|
||||
let startPos := fileMap.ofPosition startLine
|
||||
let endPos := fileMap.ofPosition endLine
|
||||
let text := fileMap.source.extract startPos endPos
|
||||
pure text
|
||||
| none =>
|
||||
pure ""
|
||||
| none => pure ""
|
||||
|
||||
/-- Number of definitions. -/
|
||||
def noDefs : MetaM Nat := do
|
||||
let imports ← allImports
|
||||
|
|
|
@ -54,6 +54,11 @@ syntax (name := informalAssignment) ident ":≈" str : informalAssignment
|
|||
/-- The syntax describing an informal assignment of `ident` to a list. -/
|
||||
syntax (name := informalAssignmentDeps) ident ":≈" "[" sepBy(term, ",") "]" : informalAssignment
|
||||
|
||||
/-!
|
||||
|
||||
## Syntax
|
||||
|
||||
-/
|
||||
/-- The syntax for the command informal_definition. -/
|
||||
syntax (name := informal_definition) "informal_definition " ident
|
||||
" where " (colGt informalAssignment)* : command
|
||||
|
@ -96,6 +101,42 @@ def $name : InformalDefinition := {
|
|||
dependencies := $(dep_def?.getD (← `([])))
|
||||
})
|
||||
|
||||
/-- The syntax for the command informal_definition. -/
|
||||
syntax (name := informal_definition_note) "informal_definition_note " ident
|
||||
" where " (colGt informalAssignment)* : command
|
||||
|
||||
macro "informal_definition_note " name:ident " where " assignments:informalAssignment* : command => do
|
||||
let mut math_def? : Option (TSyntax `term) := none
|
||||
let mut physics_def? : Option (TSyntax `term) := none
|
||||
let mut ref_def? : Option (TSyntax `term) := none
|
||||
let mut dep_def? : Option (TSyntax `term) := none
|
||||
for assignment in assignments do
|
||||
match assignment with
|
||||
| `(informalAssignment| physics :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for physics"
|
||||
physics_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignment| math :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for math"
|
||||
math_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignment| ref :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for ref"
|
||||
ref_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignmentDeps| deps :≈ [$deps,*]) =>
|
||||
dep_def? := some (← `([$deps,*]))
|
||||
| _ => Macro.throwError "invalid assignment syntax in informal_definition"
|
||||
unless math_def?.isSome do
|
||||
Macro.throwError "A 'math' assignments is required"
|
||||
`(
|
||||
/-- An informal definition. -/
|
||||
@[note_attr_informal]
|
||||
def $name : InformalDefinition := {
|
||||
name := $(Lean.quote name.getId),
|
||||
physics := $(physics_def?.getD (← `("No physics description provided"))),
|
||||
math := $(math_def?.getD (panic! "math not assigned")),
|
||||
ref := $(ref_def?.getD (← `("No references provided"))),
|
||||
dependencies := $(dep_def?.getD (← `([])))
|
||||
})
|
||||
|
||||
/-- The syntax for the command `informal_lemma`. -/
|
||||
syntax (name := informal_lemma) "informal_lemma " ident " where "
|
||||
(colGt informalAssignment)* : command
|
||||
|
@ -133,7 +174,6 @@ macro "informal_lemma " name:ident " where " assignments:informalAssignment* : c
|
|||
unless math_def?.isSome do
|
||||
Macro.throwError "A 'math' assignments is required"
|
||||
`(
|
||||
|
||||
/-- An informal lemma. -/
|
||||
def $name : InformalLemma := {
|
||||
name := $(Lean.quote name.getId),
|
||||
|
@ -144,6 +184,55 @@ def $name : InformalLemma := {
|
|||
dependencies := $(dep_def?.getD (← `([])))
|
||||
})
|
||||
|
||||
|
||||
/-- The syntax for the command `informal_lemma`. -/
|
||||
syntax (name := informal_lemma_note) "informal_lemma_note " ident " where "
|
||||
(colGt informalAssignment)* : command
|
||||
|
||||
/-- An informal lemma is a lemma which is not type checked, and is written
|
||||
as a string literal. It can be used to plan out sections for future formalization, or to
|
||||
include results which the formalization is not immediately known.
|
||||
Every informal lemma must included a
|
||||
`math :≈ "..."`
|
||||
entry, but can also include the following entries
|
||||
`physics :≈ "..."`, `proof :≈ "..."`, `ref :≈ "..."`, and `deps :≈ [...]`. -/
|
||||
macro "informal_lemma_note " name:ident " where " assignments:informalAssignment* : command => do
|
||||
let mut math_def? : Option (TSyntax `term) := none
|
||||
let mut physics_def? : Option (TSyntax `term) := none
|
||||
let mut proof_def? : Option (TSyntax `term) := none
|
||||
let mut ref_def? : Option (TSyntax `term) := none
|
||||
let mut dep_def? : Option (TSyntax `term) := none
|
||||
for assignment in assignments do
|
||||
match assignment with
|
||||
| `(informalAssignment| physics :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for physics"
|
||||
physics_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignment| math :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for math"
|
||||
math_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignment| proof :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for proof"
|
||||
proof_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignment| ref :≈ $val:str) =>
|
||||
let some strVal := val.raw.isStrLit? | Macro.throwError "Expected string literal for ref"
|
||||
ref_def? := some (← `($(Lean.quote strVal)))
|
||||
| `(informalAssignmentDeps| deps :≈ [$deps,*]) =>
|
||||
dep_def? := some (← `([$deps,*]))
|
||||
| _ => Macro.throwError "invalid assignment syntax"
|
||||
unless math_def?.isSome do
|
||||
Macro.throwError "A 'math' assignments is required"
|
||||
`(
|
||||
/-- An informal lemma. -/
|
||||
@[note_attr_informal]
|
||||
def $name : InformalLemma := {
|
||||
name := $(Lean.quote name.getId),
|
||||
physics := $(physics_def?.getD (← `("No physics description provided"))),
|
||||
math := $(math_def?.getD (panic! "math not assigned")),
|
||||
proof := $(proof_def?.getD (← `("No proof description provided"))),
|
||||
ref := $(ref_def?.getD (← `("No references provided"))),
|
||||
dependencies := $(dep_def?.getD (← `([])))
|
||||
})
|
||||
|
||||
/-- Is true if and only if a `ConstantInfo` corresponds to an `InformalLemma` or a
|
||||
`InformalDefinition`. -/
|
||||
def isInformal (c : ConstantInfo) : Bool :=
|
||||
|
@ -166,4 +255,21 @@ def isInformalDef (c : ConstantInfo) : Bool :=
|
|||
if c.type.isAppOf ``InformalDefinition then true else false
|
||||
| _ => false
|
||||
|
||||
/-- Takes a `ConstantInfo` corresponding to a `InformalLemma` and returns
|
||||
the corresponding `InformalLemma`. -/
|
||||
unsafe def constantInfoToInformalLemma (c : ConstantInfo) : MetaM InformalLemma := do
|
||||
match c with
|
||||
| ConstantInfo.defnInfo c =>
|
||||
Lean.Meta.evalExpr' InformalLemma ``InformalLemma c.value
|
||||
| _ => panic! "Passed constantInfoToInformalLemma a `ConstantInfo` that is not a `InformalLemma`"
|
||||
|
||||
/-- Takes a `ConstantInfo` corresponding to a `InformalDefinition` and returns
|
||||
the corresponding `InformalDefinition`. -/
|
||||
unsafe def constantInfoToInformalDefinition (c : ConstantInfo) : MetaM InformalDefinition := do
|
||||
match c with
|
||||
| ConstantInfo.defnInfo c =>
|
||||
Lean.Meta.evalExpr' InformalDefinition ``InformalDefinition c.value
|
||||
| _ => panic! "Passed constantInfoToInformalDefinition a
|
||||
`ConstantInfo` that is not a `InformalDefinition`"
|
||||
|
||||
end Informal
|
||||
|
|
121
HepLean/Meta/Notes/Basic.lean
Normal file
121
HepLean/Meta/Notes/Basic.lean
Normal file
|
@ -0,0 +1,121 @@
|
|||
/-
|
||||
Copyright (c) 2024 Joseph Tooby-Smith. All rights reserved.
|
||||
Released under Apache 2.0 license.
|
||||
Authors: Joseph Tooby-Smith
|
||||
-/
|
||||
import Batteries.Lean.HashSet
|
||||
import Lean
|
||||
import Mathlib.Lean.Expr.Basic
|
||||
import Mathlib.Lean.CoreM
|
||||
import ImportGraph.RequiredModules
|
||||
/-!
|
||||
|
||||
## Basic Lean meta programming commands
|
||||
|
||||
-/
|
||||
|
||||
namespace HepLean
|
||||
open Lean System Meta
|
||||
|
||||
-- Define the structure to hold note information, including module name
|
||||
structure NoteInfo where
|
||||
content : String
|
||||
fileName : Name
|
||||
line : Nat
|
||||
deriving Inhabited
|
||||
|
||||
-- Define the environment extension to store notes
|
||||
initialize noteExtension : SimplePersistentEnvExtension NoteInfo (Array NoteInfo) ←
|
||||
registerSimplePersistentEnvExtension {
|
||||
name := `noteExtension
|
||||
addEntryFn := fun arr nodeInfor => arr.push nodeInfor
|
||||
addImportedFn := fun es => es.foldl (· ++ ·) #[] -- Merge imported notes
|
||||
}
|
||||
|
||||
-- Define the syntax for the `note` command
|
||||
syntax (name := note_comment) "note " str : command
|
||||
|
||||
-- Define the elaboration behavior (do nothing)
|
||||
@[command_elab note_comment]
|
||||
def elabNote : Lean.Elab.Command.CommandElab := fun stx =>
|
||||
match stx with
|
||||
| `(note $s) => do
|
||||
|
||||
let str : String := s.getString
|
||||
let pos := stx.getPos?
|
||||
match pos with
|
||||
| some pos => do
|
||||
let env ← getEnv
|
||||
let fileMap ← getFileMap
|
||||
let filePos := fileMap.toPosition pos
|
||||
let line := filePos.line
|
||||
let modName := env.mainModule
|
||||
let noteInfo : NoteInfo := { content := str, fileName := modName, line := line }
|
||||
modifyEnv fun env => noteExtension.addEntry env noteInfo
|
||||
|
||||
| none => throwError "Invalid syntax for `note` command"
|
||||
| _ => throwError "Invalid syntax for `note` command"
|
||||
|
||||
|
||||
|
||||
/-!
|
||||
|
||||
## Note attribute
|
||||
|
||||
-/
|
||||
|
||||
initialize noteDeclExtension : SimplePersistentEnvExtension Name (Array Name) ←
|
||||
registerSimplePersistentEnvExtension {
|
||||
name := `noteDeclExtension
|
||||
addEntryFn := fun arr nodeInfor => arr.push nodeInfor
|
||||
addImportedFn := fun es => es.foldl (· ++ ·) #[] -- Merge imported notes
|
||||
}
|
||||
|
||||
initialize noteAttribute : Unit ←
|
||||
registerBuiltinAttribute {
|
||||
name := `note_attr
|
||||
descr := "Note attribute"
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
add := fun declName _ _ => do
|
||||
modifyEnv fun env => noteDeclExtension.addEntry env declName
|
||||
}
|
||||
|
||||
initialize noteInformalDeclExtension : SimplePersistentEnvExtension Name (Array Name) ←
|
||||
registerSimplePersistentEnvExtension {
|
||||
name := `noteInformalDeclExtension
|
||||
addEntryFn := fun arr nodeInfor => arr.push nodeInfor
|
||||
addImportedFn := fun es => es.foldl (· ++ ·) #[] -- Merge imported notes
|
||||
}
|
||||
|
||||
initialize noteInformalAttribute : Unit ←
|
||||
registerBuiltinAttribute {
|
||||
name := `note_attr_informal
|
||||
descr := "Informal note attribute"
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
add := fun declName _ _ => do
|
||||
modifyEnv fun env => noteInformalDeclExtension.addEntry env declName
|
||||
}
|
||||
|
||||
/-!
|
||||
|
||||
## Notes
|
||||
|
||||
-/
|
||||
|
||||
/-- A note consists of a title and a list of Lean files which make up the note. -/
|
||||
structure NoteFile where
|
||||
title : String
|
||||
abstract : String
|
||||
authors : List String
|
||||
files : List Name
|
||||
|
||||
namespace NoteFile
|
||||
|
||||
variable (N : NoteFile)
|
||||
|
||||
/-- All imports associated to a note. -/
|
||||
def imports : Array Import := (N.files.map fun f => {module := f}).toArray
|
||||
|
||||
end NoteFile
|
||||
|
||||
end HepLean
|
57
HepLean/Meta/Notes/HTMLNote.lean
Normal file
57
HepLean/Meta/Notes/HTMLNote.lean
Normal file
|
@ -0,0 +1,57 @@
|
|||
/-
|
||||
Copyright (c) 2024 Joseph Tooby-Smith. All rights reserved.
|
||||
Released under Apache 2.0 license.
|
||||
Authors: Joseph Tooby-Smith
|
||||
-/
|
||||
import HepLean.Meta.Notes.Basic
|
||||
import HepLean.Meta.Basic
|
||||
import HepLean.Meta.Informal
|
||||
/-!
|
||||
|
||||
## Turns a delaration into a html note structure.
|
||||
|
||||
-/
|
||||
|
||||
namespace HepLean
|
||||
open Lean System Meta
|
||||
|
||||
structure HTMLNote where
|
||||
fileName : Name
|
||||
content : String
|
||||
line : Nat
|
||||
|
||||
|
||||
|
||||
def noteInfoToHTMLNote (ni : NoteInfo) : MetaM HTMLNote := do
|
||||
let line := ni.line
|
||||
let decl := ni.content
|
||||
let fileName := ni.fileName
|
||||
pure { content := decl, fileName := fileName, line := line }
|
||||
|
||||
def formalToHTMLNote (name : Name) : MetaM HTMLNote := do
|
||||
let line ← Name.lineNumber name
|
||||
let decl ← Name.getDeclString name
|
||||
let fileName ← Name.fileName name
|
||||
let content := "<pre><code>" ++ decl ++ "</code></pre>"
|
||||
pure { content := content, fileName := fileName, line := line }
|
||||
|
||||
unsafe def informalToHTMLNote (name : Name) : MetaM HTMLNote := do
|
||||
let line ← Name.lineNumber name
|
||||
let decl ← Name.getDeclString name
|
||||
let fileName ← Name.fileName name
|
||||
let constInfo ← getConstInfo name
|
||||
let mut content := ""
|
||||
if Informal.isInformalDef constInfo then
|
||||
let X ← Informal.constantInfoToInformalDefinition constInfo
|
||||
content := "<div class=\"informal-def\">"
|
||||
++ "<a href=\"https://example.com\" class=\"button\">Improve/Formalize</a>"
|
||||
++"<b>Informal definition:</b> " ++ name.toString ++ "<br>"
|
||||
++ X.math.replace "\n" "<br>"
|
||||
++ "</div>"
|
||||
else if Informal.isInformalLemma constInfo then
|
||||
let X ← Informal.constantInfoToInformalLemma constInfo
|
||||
content := "Informal definition: " ++ name.toString ++ "\n" ++ X.math
|
||||
content := "Informal lemma: " ++ name.toString
|
||||
pure { content := content, fileName := fileName, line := line }
|
||||
|
||||
end HepLean
|
126
HepLean/Meta/Notes/ToHTML.lean
Normal file
126
HepLean/Meta/Notes/ToHTML.lean
Normal file
|
@ -0,0 +1,126 @@
|
|||
/-
|
||||
Copyright (c) 2024 Joseph Tooby-Smith. All rights reserved.
|
||||
Released under Apache 2.0 license.
|
||||
Authors: Joseph Tooby-Smith
|
||||
-/
|
||||
import HepLean.Meta.Notes.HTMLNote
|
||||
import HepLean.Meta.Basic
|
||||
/-!
|
||||
|
||||
## Turns a delaration into a html note structure.
|
||||
|
||||
-/
|
||||
|
||||
namespace HepLean
|
||||
open Lean System Meta
|
||||
|
||||
namespace NoteFile
|
||||
variable (N : NoteFile)
|
||||
|
||||
/-- Sorts `NoteInfo`'s by file name then by line number. -/
|
||||
def sortLE (ni1 ni2 : HTMLNote) : Bool :=
|
||||
if N.files.indexOf ni1.fileName ≠ N.files.indexOf ni2.fileName
|
||||
then N.files.indexOf ni1.fileName ≤ N.files.indexOf ni2.fileName
|
||||
else
|
||||
ni1.line ≤ ni2.line
|
||||
|
||||
/-- Returns a sorted list of NodeInfos for a file system. -/
|
||||
unsafe def getNodeInfo : MetaM (List HTMLNote) := do
|
||||
let env ← getEnv
|
||||
let allNotes := (noteExtension.getState env)
|
||||
let allDecl := (noteDeclExtension.getState env)
|
||||
let allInformalDecl := noteInformalDeclExtension.getState env
|
||||
let allNoteInfo := (← allNotes.mapM noteInfoToHTMLNote) ++ (← allDecl.mapM formalToHTMLNote)
|
||||
++ (← allInformalDecl.mapM informalToHTMLNote)
|
||||
let noteInfo := allNoteInfo.filter (fun x => x.fileName ∈ N.files)
|
||||
let noteInfoSort := noteInfo.toList.mergeSort N.sortLE
|
||||
pure noteInfoSort
|
||||
|
||||
def codeBlockHTML : String := "
|
||||
<meta charset=\"UTF-8\">
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||
<!-- Include Highlight.js CSS -->
|
||||
<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css\">
|
||||
<!-- Include Highlight.js -->
|
||||
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js\"></script>
|
||||
<!-- Enable Highlight.js -->
|
||||
<script>hljs.highlightAll();</script>
|
||||
<script>
|
||||
hljs.registerLanguage('lean', function(hljs) {
|
||||
return {
|
||||
keywords: 'def theorem axiomatic structure lemma informal_definition_note informal_lemma_note',
|
||||
contains: [
|
||||
hljs.COMMENT('--', '$'),
|
||||
hljs.C_NUMBER_MODE,
|
||||
// Operators (custom definition)
|
||||
{
|
||||
className: 'operator', // Define a class for styling
|
||||
begin: /[:=+\\-*/<>|&!~^{}]/ // Regex for operators
|
||||
}
|
||||
|
||||
]
|
||||
};
|
||||
});
|
||||
hljs.highlightAll();
|
||||
</script>
|
||||
"
|
||||
|
||||
def informalDefStyle : String :=
|
||||
"
|
||||
<style>
|
||||
.informal-def {
|
||||
position: relative; /* Establish a relative positioning context for the button */
|
||||
background-color: #f8d7da; /* Light red background */
|
||||
border: 2px solid #dc3545; /* Solid darker red border */
|
||||
margin: 10px; /* Space outside the block */
|
||||
padding: 20px; /* Space inside the block */
|
||||
}
|
||||
|
||||
.informal-def .button {
|
||||
position: absolute; /* Position relative to the parent */
|
||||
top: 5px; /* Distance from the top of the block */
|
||||
right: 5px; /* Distance from the right of the block */
|
||||
background-color: rgba(220, 53, 69, 0.4);
|
||||
color: white; /* White text */
|
||||
border: none; /* No border for the button */
|
||||
padding: 5px 10px; /* Padding for the button */
|
||||
text-decoration: none; /* Remove underline from the link */
|
||||
border-radius: 5px; /* Rounded corners for the button */
|
||||
font-size: 12px; /* Smaller font size */
|
||||
cursor: pointer; /* Pointer cursor on hover */
|
||||
}
|
||||
|
||||
.informal-def .button:hover {
|
||||
background-color: #0056b3; /* Darker blue on hover */
|
||||
}
|
||||
body {
|
||||
color: #000000; /* Change the default text color to dark gray */
|
||||
}
|
||||
</style>
|
||||
"
|
||||
|
||||
def headerHTML : String :=
|
||||
"---
|
||||
layout: default
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>" ++ codeBlockHTML ++ informalDefStyle ++ "</head>
|
||||
</head>
|
||||
<body>"
|
||||
|
||||
def titleHTML : String :=
|
||||
"<center><h1 style=\"font-size: 50px;\">" ++ N.title ++ "</h1></center>
|
||||
<center><b>Authors:</b> " ++ String.intercalate ", " N.authors ++ "</center>
|
||||
<center><b>Abstract:</b> " ++ N.abstract ++ "</center>"
|
||||
|
||||
def footerHTML : String :=
|
||||
"</body>
|
||||
</html>"
|
||||
|
||||
unsafe def toHTMLString : MetaM String := do
|
||||
let string := String.intercalate "\n" ((← N.getNodeInfo).map (fun x => x.content))
|
||||
pure (headerHTML ++ N.titleHTML ++ string ++ footerHTML)
|
||||
|
||||
end NoteFile
|
||||
end HepLean
|
Loading…
Add table
Add a link
Reference in a new issue