feat: Add notes infastructure

This commit is contained in:
jstoobysmith 2024-12-04 13:37:23 +00:00
parent 6b42cdd4e6
commit 33ef43ed7d
11 changed files with 568 additions and 28 deletions

View file

@ -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

View file

@ -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

View 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

View 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

View 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