├── doc └── .keep ├── lean-toolchain ├── test ├── playground │ ├── Simple.lean │ ├── playground_3.lean │ ├── playground_2.lean │ ├── infoTree.lean │ ├── playground_1.lean │ ├── playground_3.lean.leanInk.expected │ ├── Simple.lean.leanInk.expected │ ├── Function.lean │ └── playground_2.lean.leanInk.expected ├── bugs │ ├── GH_15.lean │ └── GH_15.lean.leanInk.expected ├── example.zip ├── theorem_proving │ ├── 001.lean │ ├── 004.lean │ ├── 005.lean │ ├── 003.lean │ ├── 002.lean │ ├── 008.lean │ ├── 006.lean │ ├── 007.lean │ ├── 009.lean │ ├── 001.lean.leanInk.expected │ ├── 004.lean.leanInk.expected │ ├── INFO │ ├── 005.lean.leanInk.expected │ ├── 003.lean.leanInk.expected │ ├── 002.lean.leanInk.expected │ └── 007.lean.leanInk.expected └── bench │ ├── lakefile.lean │ └── lake-manifest.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 04_questions.md │ ├── 03_enhancement.md │ ├── 02_feature_request.md │ └── 01_bugs.md ├── pull_request_template.md └── workflows │ └── build.yml ├── LeanInk ├── Annotation.lean ├── CLI.lean ├── Analysis.lean ├── CLI │ ├── Result.lean │ ├── App.lean │ ├── Command.lean │ ├── Help.lean │ ├── Argument.lean │ └── Basic.lean ├── ListUtil.lean ├── Version.lean ├── Configuration.lean ├── Annotation │ ├── DataTypes.lean │ ├── Basic.lean │ ├── Util.lean │ └── Alectryon.lean ├── FileHelper.lean ├── Logger.lean └── Analysis │ ├── Analysis.lean │ ├── SemanticToken.lean │ ├── LeanContext.lean │ ├── Basic.lean │ └── DataTypes.lean ├── lake-manifest.json ├── LeanInk.lean ├── .gitignore ├── init.sh ├── Main.lean ├── lakefile.lean ├── README.md └── LICENSE /doc/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.6.0-rc1 -------------------------------------------------------------------------------- /test/playground/Simple.lean: -------------------------------------------------------------------------------- 1 | def add (x y : Nat) := x + y -------------------------------------------------------------------------------- /test/playground/playground_3.lean: -------------------------------------------------------------------------------- 1 | def x := 1 2 | #eval 1 + x -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /test/bugs/GH_15.lean: -------------------------------------------------------------------------------- 1 | example : Nat → Nat 2 | | 0 => 0 3 | | n + 1 => by exact n -------------------------------------------------------------------------------- /test/example.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leanprover/LeanInk/HEAD/test/example.zip -------------------------------------------------------------------------------- /test/theorem_proving/001.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := 2 | sorry -------------------------------------------------------------------------------- /LeanInk/Annotation.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Annotation.Alectryon 2 | import LeanInk.Annotation.Basic 3 | import LeanInk.Annotation.Util 4 | -------------------------------------------------------------------------------- /test/theorem_proving/004.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 2 | apply And.intro hp 3 | exact And.intro hq hp -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 7, 2 | "packagesDir": ".lake/packages", 3 | "packages": [], 4 | "name": "leanInk", 5 | "lakeDir": ".lake"} 6 | -------------------------------------------------------------------------------- /test/bench/lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | package basic { 5 | } 6 | 7 | @[default_target] 8 | lean_lib Basic { 9 | } 10 | -------------------------------------------------------------------------------- /test/bench/lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 7, 2 | "packagesDir": ".lake/packages", 3 | "packages": [], 4 | "name": "basic", 5 | "lakeDir": ".lake"} 6 | -------------------------------------------------------------------------------- /test/theorem_proving/005.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 2 | apply And.intro hp; exact And.intro hq hp 3 | 4 | #check And -------------------------------------------------------------------------------- /test/theorem_proving/003.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 2 | apply And.intro 3 | exact hp 4 | apply And.intro 5 | exact hq 6 | exact hp -------------------------------------------------------------------------------- /LeanInk/CLI.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.CLI.App 2 | import LeanInk.CLI.Argument 3 | import LeanInk.CLI.Basic 4 | import LeanInk.CLI.Command 5 | import LeanInk.CLI.Help 6 | import LeanInk.CLI.Result 7 | -------------------------------------------------------------------------------- /test/theorem_proving/002.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := 2 | by apply And.intro 3 | exact hp 4 | apply And.intro 5 | exact hq 6 | exact hp -------------------------------------------------------------------------------- /test/theorem_proving/008.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 2 | apply And.intro 3 | . exact hp 4 | . apply And.intro 5 | . exact hq 6 | . exact hp -------------------------------------------------------------------------------- /LeanInk.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.CLI 2 | import LeanInk.Analysis 3 | import LeanInk.Annotation 4 | import LeanInk.Configuration 5 | import LeanInk.FileHelper 6 | import LeanInk.ListUtil 7 | import LeanInk.Logger 8 | import LeanInk.Version 9 | -------------------------------------------------------------------------------- /test/theorem_proving/006.lean: -------------------------------------------------------------------------------- 1 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 2 | apply And.intro 3 | case left => exact hp 4 | case right => 5 | apply And.intro 6 | case left => exact hq 7 | case right => exact hp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | lake-packages/ 3 | 4 | /test/**/*.html 5 | /test/**/*.css 6 | /test/**/*.js 7 | /test/**/lean_packages 8 | /test/**/.git 9 | /test/**/*.leanInk 10 | /.vscode 11 | 12 | *.trace 13 | *.json 14 | !lake-manifest.json 15 | .lake -------------------------------------------------------------------------------- /LeanInk/Analysis.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Analysis.Analysis 2 | import LeanInk.Analysis.Basic 3 | import LeanInk.Analysis.DataTypes 4 | import LeanInk.Analysis.LeanContext 5 | import LeanInk.Analysis.SemanticToken 6 | import LeanInk.Analysis.InfoTreeTraversal 7 | -------------------------------------------------------------------------------- /test/playground/playground_2.lean: -------------------------------------------------------------------------------- 1 | /-| 2 | Hello World! 3 | -/ 4 | #print "Hello World!" 5 | 6 | /-| 7 | A literate comment! 8 | -/ 9 | 10 | theorem exampleTheorem (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 11 | apply And.intro 12 | . exact hp 13 | . sorry -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | git clone https://github.com/leanprover/LeanInk ./leanInk -q 3 | cd leanInk 4 | git fetch --tags -q 5 | latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) 6 | git checkout $latestTag -q 7 | lake script run install && cd .. && rm -rf ./leanInk 8 | -------------------------------------------------------------------------------- /test/theorem_proving/007.lean: -------------------------------------------------------------------------------- 1 | -- set_option trace.Elab.info true 2 | 3 | theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := by 4 | apply And.intro 5 | case right => 6 | apply And.intro 7 | case left => exact hq 8 | case right => exact hp 9 | case left => exact hp 10 | -------------------------------------------------------------------------------- /LeanInk/CLI/Result.lean: -------------------------------------------------------------------------------- 1 | namespace LeanInk.CLI 2 | 3 | -- Result 4 | inductive Result (error : Type) (result: Type) where 5 | | failure (err: error) 6 | | success (res: result) 7 | 8 | instance [ToString e] : ToString (Result e r) where 9 | toString 10 | | Result.failure error => s!"ERROR: {error}" 11 | | Result.success _ => "SUCCESS!" -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Notable Changes 5 | 6 | 7 | ## Additional Notes 8 | 9 | -------------------------------------------------------------------------------- /LeanInk/ListUtil.lean: -------------------------------------------------------------------------------- 1 | namespace LeanInk 2 | 3 | namespace List 4 | 5 | def sort [Inhabited α] (f: α -> α -> Bool) (xs : List α) : List α := (xs.toArray.qsort f).toList 6 | 7 | partial def mergeSortedLists (f: α -> α -> Bool) : List α -> List α -> List α 8 | | [], xs => xs 9 | | xs, [] => xs 10 | | x::xs, y::ys => 11 | if f x y then 12 | x::mergeSortedLists f xs (y::ys) 13 | else 14 | y::mergeSortedLists f (x::xs) ys 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04_questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U00002753 Question" 3 | about: Template for specific questions about low-level excerpts of the code or high-level features. 4 | labels: question 5 | 6 | --- 7 | 8 | ## Question 9 | 10 | 11 | ## Additional Notes 12 | 13 | -------------------------------------------------------------------------------- /LeanInk/Version.lean: -------------------------------------------------------------------------------- 1 | namespace LeanInk.Version 2 | 3 | def leanVersion : String := 4 | let versionString := s!"{Lean.version.major}.{Lean.version.minor}.{Lean.version.patch}" 5 | if Lean.version.isRelease then 6 | versionString 7 | else if !String.isEmpty Lean.version.specialDesc then 8 | versionString ++ s!"-" ++ Lean.version.specialDesc 9 | else 10 | versionString ++ s!"-unknown" 11 | 12 | def printLeanVersion : IO UInt32 := do 13 | IO.println leanVersion 14 | return 0 15 | -------------------------------------------------------------------------------- /LeanInk/CLI/App.lean: -------------------------------------------------------------------------------- 1 | namespace LeanInk.CLI 2 | 3 | -- APPLICATION INFO 4 | structure AppVersion where 5 | major : Nat 6 | minor : Nat 7 | patch : Nat 8 | suffix : String := "" 9 | 10 | instance : ToString AppVersion where 11 | toString (self : AppVersion) : String := s!"{self.major}.{self.minor}.{self.patch}{self.suffix}" 12 | 13 | structure AppInfo where 14 | name : String 15 | base : String 16 | version : AppVersion 17 | description : String 18 | 19 | namespace AppInfo 20 | def versionString (self : AppInfo) : String := s!"{self.name} ({self.version})" 21 | end AppInfo 22 | -------------------------------------------------------------------------------- /test/theorem_proving/009.lean: -------------------------------------------------------------------------------- 1 | example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by 2 | apply Iff.intro 3 | . intro h 4 | apply Or.elim (And.right h) 5 | . intro hq 6 | apply Or.inl 7 | apply And.intro 8 | . exact And.left h 9 | . exact hq 10 | . intro hr 11 | apply Or.inr 12 | apply And.intro 13 | . exact And.left h 14 | . exact hr 15 | . intro h 16 | apply Or.elim h 17 | . intro hpq 18 | apply And.intro 19 | . exact And.left hpq 20 | . apply Or.inl 21 | exact And.right hpq 22 | . intro hpr 23 | apply And.intro 24 | . exact And.left hpr 25 | . apply Or.inr 26 | exact And.right hpr -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U00002728 Enhancement" 3 | about: You think there is room for improvement of a specific feature? Then this template is for you! 4 | labels: enhancement 5 | 6 | --- 7 | 8 | ## Current behaviour 9 | 10 | 11 | ## Suggested behaviour 12 | 13 | 14 | ## Reasoning 15 | 16 | 17 | ## Additional notes 18 | 19 | -------------------------------------------------------------------------------- /test/playground/infoTree.lean: -------------------------------------------------------------------------------- 1 | import Lean 2 | 3 | open Lean.Elab 4 | 5 | structure A where 6 | val : Nat → Nat 7 | 8 | structure B where 9 | pair : A × A 10 | 11 | def f (x : Nat) : Nat × Nat := 12 | let y := ⟨x, x⟩ 13 | id y 14 | 15 | def h : (x y : Nat) → (b : Bool) → x + 0 = x := 16 | fun x y b => by 17 | simp 18 | 19 | def f2 : (x y : Nat) → (b : Bool) → Nat := 20 | fun x y b => 21 | let (z, w) := (x + y, x - y) 22 | let z1 := z + w 23 | z + z1 24 | 25 | def f3 (s : Nat × Array (Array Nat)) : Array Nat := 26 | s.2[1].push s.1 27 | 28 | def f4 (arg : B) : Nat := 29 | arg.pair.fst.val 0 30 | 31 | def f5 (x : Nat) : B := { 32 | pair := ({ val := id }, { val := id }) 33 | } 34 | 35 | open Nat in 36 | #print xor 37 | instance : Inhabited Nat where 38 | 39 | macro -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U00002b50 Feature" 3 | about: Use this template if you want to propose a new feature for LeanInk! 4 | labels: feature 5 | 6 | --- 7 | 8 | ## Description 9 | 10 | 13 | 14 | ## Detailed behaviour 15 | 16 | 20 | 21 | ## Testscenarios 22 | 25 | 26 | ## References 27 | 30 | -------------------------------------------------------------------------------- /LeanInk/Configuration.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Annotation.DataTypes 2 | 3 | namespace LeanInk 4 | 5 | open System 6 | open LeanInk.Annotation 7 | 8 | structure Configuration where 9 | inputFilePath : FilePath 10 | inputFileContents : String 11 | lakeFile : Option FilePath 12 | verbose : Bool 13 | prettifyOutput : Bool 14 | experimentalTypeInfo : Bool 15 | experimentalDocString : Bool 16 | experimentalSemanticType : Bool 17 | experimentalSorryConfig : Bool 18 | experimentalCalcConfig : Bool 19 | 20 | namespace Configuration 21 | def inputFileName (self : Configuration) : String := 22 | self.inputFilePath.toString 23 | end Configuration 24 | 25 | abbrev AnalysisM := ReaderT Configuration $ IO 26 | 27 | structure Output where 28 | name : String 29 | genOutput : List Annotation -> AnalysisM UInt32 30 | 31 | abbrev ExecM := ReaderT Configuration $ IO 32 | -------------------------------------------------------------------------------- /test/playground/playground_1.lean: -------------------------------------------------------------------------------- 1 | /-| 2 | ======= 3 | Title 4 | ======= 5 | 6 | Prose. *Emphasis*; **strong emphasis**; ``code``; `coq code`; `link `__. 7 | |-/ 8 | 9 | #check Bool 10 | 11 | #print "Hello World" 12 | 13 | example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by 14 | apply Iff.intro 15 | . intro h 16 | apply Or.elim (And.right h) 17 | . intro hq 18 | apply Or.inl 19 | apply And.intro 20 | . exact And.left h 21 | . exact hq 22 | . intro hr 23 | apply Or.inr 24 | apply And.intro 25 | . exact And.left h 26 | . exact hr 27 | . intro h 28 | apply Or.elim h 29 | . intro hpq 30 | apply And.intro 31 | . exact And.left hpq 32 | . apply Or.inl 33 | exact And.right hpq 34 | . intro hpr 35 | apply And.intro 36 | . exact And.left hpr 37 | . apply Or.inr 38 | exact And.right hpr -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_bugs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug" 3 | about: You found a bug within LeanInk? 4 | labels: bug 5 | 6 | --- 7 | 8 | ## Description 9 | 10 | 11 | ## Expected behaviour 12 | 13 | 14 | ## Reproducing the issue 15 | 19 | 20 | 21 | ## Environment information 22 | 23 | - Operating System: 24 | - Lean version: 25 | - LeanInk version: 26 | - Alectryon version: 27 | 28 | ## Suggested fix 29 | 30 | 31 | ## Additional Notes 32 | 33 | -------------------------------------------------------------------------------- /LeanInk/Annotation/DataTypes.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Analysis.DataTypes 2 | 3 | namespace LeanInk.Annotation 4 | 5 | open LeanInk.Analysis 6 | 7 | universe u 8 | 9 | /- COMPOUND -/ 10 | structure Compound (β : Type u) where 11 | headPos : String.Pos 12 | tailPos : Option String.Pos 13 | fragments : List (Nat × β) 14 | deriving Inhabited 15 | 16 | structure Annotation where 17 | sentence : Compound Sentence 18 | tokens : List (Compound Token) 19 | 20 | namespace Compound 21 | def getFragments (self : Compound b) : List b := self.fragments.map (λ f => f.2) 22 | 23 | def empty { x : Type u } (headPos : String.Pos) : Compound x := { headPos := headPos, tailPos := none, fragments := [] } 24 | end Compound 25 | 26 | instance {a : Type u} [ToString a] : ToString (Compound a) where 27 | toString (self : Compound a) : String := "" 28 | -------------------------------------------------------------------------------- /LeanInk/FileHelper.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Configuration 2 | import LeanInk.Logger 3 | 4 | import Lean.Data.Json 5 | import Lean.Data.Json.Printer 6 | 7 | namespace LeanInk 8 | 9 | open Lean 10 | open System 11 | 12 | def leanFileExtension := s!"lean" 13 | 14 | def isLeanFile (path : FilePath) : Bool := 15 | path.extension == leanFileExtension 16 | 17 | -- OUTPUT 18 | open IO.FS 19 | def createOutputFile (folderPath : FilePath) (fileName : String) (content : String) : AnalysisM Unit := do 20 | let dirEntry : DirEntry := { 21 | root := folderPath, 22 | fileName := fileName ++ ".leanInk" 23 | } 24 | let path := dirEntry.path 25 | IO.FS.writeFile path content 26 | logInfo s!"Results written to file: {path}!" 27 | 28 | def generateOutput { α : Type } [ToJson α] (fragments : Array α) : AnalysisM String := do 29 | if (← read).prettifyOutput then 30 | return (toJson fragments).pretty 31 | else 32 | return (toJson fragments).compress 33 | -------------------------------------------------------------------------------- /LeanInk/CLI/Command.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.CLI.Argument 2 | import LeanInk.CLI.Result 3 | 4 | namespace LeanInk.CLI 5 | 6 | -- COMMANDS 7 | structure Command where 8 | identifiers : List String 9 | help : String 10 | additionalUsageInfo : String := "" 11 | arguments : List Argument 12 | run: (List ResolvedArgument) -> (List String) -> IO UInt32 13 | 14 | structure ResolvedCommand where 15 | command: Command 16 | arguments: List ResolvedArgument 17 | 18 | -- VERSION COMMAND 19 | def versionCommand : CLI.Command := { 20 | identifiers := ["version", "-v"] 21 | help := "Returns the version info of this instance." 22 | arguments := [] 23 | run := λ _ _ => return 0 24 | } 25 | 26 | -- HELP COMMAND 27 | -- The help command is always available 28 | def helpCommand : Command := { 29 | identifiers := ["help", "-h"] 30 | help := "Displays a help page." 31 | additionalUsageInfo := "" 32 | arguments := [] 33 | run := λ _ _ => return 0 34 | } -------------------------------------------------------------------------------- /LeanInk/Logger.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Configuration 2 | 3 | open Lean 4 | 5 | namespace LeanInk 6 | namespace Logger 7 | 8 | -- Prints a message if in debug mode, otherwise does nothing 9 | def log [ToString a] (s : a) (isDebug: Bool := false) : IO Unit := do 10 | match isDebug with 11 | | true => IO.println s 12 | | false => return 13 | 14 | -- Prints a message if in debug mode, otherwise does nothing 15 | def logInfo [ToString a] (s : a) (isDebug: Bool := false) : IO Unit := do 16 | log s!"INFO: {s}" isDebug 17 | 18 | -- Prints a warning message if in debug mode, otherwise does nothing 19 | def logWarning [ToString a] (s : a) (isDebug: Bool := false) : IO Unit := do 20 | log s!"WARNING: {s}" isDebug 21 | 22 | -- Prints an error message 23 | def logError [ToString a] (s : a) (errorCode : UInt32 := 1) : IO UInt32 := do 24 | IO.println s!"ERROR({errorCode}): {s}" 25 | return errorCode 26 | 27 | end Logger 28 | 29 | -- Prints a message if in debug mode, otherwise does nothing 30 | def log [ToString a] (s : a) : AnalysisM Unit := do 31 | Logger.log s (← read).verbose 32 | 33 | def logInfo [ToString a] (s : a) : AnalysisM Unit := do 34 | Logger.logInfo s (← read).verbose 35 | 36 | -- Prints a warning message if in debug mode, otherwise does nothing 37 | def logWarning [ToString a] (s : a) : AnalysisM Unit := do 38 | Logger.logWarning s (← read).verbose 39 | 40 | -- Prints an error message 41 | def logError [ToString a] (s : a) (errorCode : UInt32 := 1) : IO UInt32 := do 42 | Logger.logError s errorCode 43 | -------------------------------------------------------------------------------- /LeanInk/CLI/Help.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.CLI.Command 2 | import LeanInk.CLI.App 3 | 4 | namespace LeanInk.CLI 5 | 6 | def header (app : AppInfo) : String := s!" 7 | {app.versionString} 8 | {app.description}\n\n" 9 | 10 | def generateRows (rowEntries : List (String × String)) : List String := 11 | match (rowEntries.map (λ x => x.1.length)).maximum? with 12 | | none => [] 13 | | some maxLength => rowEntries.map (λ x => 14 | let extendedKey := x.1.pushn ' ' (maxLength - x.1.length) 15 | s!"{extendedKey}\t{x.2}" 16 | ) 17 | 18 | def applyField (string title: String) (rows : List String) : String := 19 | if rows.isEmpty then 20 | string 21 | else 22 | let rows := rows.foldl (λ x y => x++"\n "++y) "" 23 | s!"{string} {title}:\n{rows}\n\n" 24 | 25 | def applyFieldWithRowPairs (string title: String) (rows : List (String × String)) : String := 26 | if rows.isEmpty then 27 | string 28 | else 29 | applyField string title (generateRows rows) 30 | 31 | def generateDefaultHelp (app : AppInfo) (commands : List Command) : String := 32 | let result := header app 33 | let result := applyField result "USAGE" [s!"{app.base} "] 34 | let result := applyFieldWithRowPairs result "COMMANDS" (commands.map (λ i => (s!"{i.identifiers}", i.help))) 35 | result 36 | 37 | def generateCommandHelp (app : AppInfo) (command : Command) : String := 38 | let result := header app 39 | let result := applyField result "USAGE" (command.identifiers.map (λ i => s!"{app.base} {i} {command.additionalUsageInfo}")) 40 | let result := applyFieldWithRowPairs result "ARGUMENTS" (command.arguments.map (λ i => (s!"{i.identifiers}", i.help))) 41 | let result := applyField result "DISCUSSION" [command.help] 42 | result -------------------------------------------------------------------------------- /LeanInk/Analysis/Analysis.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Analysis.DataTypes 2 | import LeanInk.Analysis.LeanContext 3 | import LeanInk.Analysis.SemanticToken 4 | import LeanInk.Analysis.InfoTreeTraversal 5 | 6 | import LeanInk.Configuration 7 | import LeanInk.Logger 8 | 9 | import Lean.Elab.Frontend 10 | import Lean.Elab.Import 11 | import Lean.Elab.Command 12 | import Lean.Parser 13 | 14 | import Lean.Util.Trace 15 | 16 | namespace LeanInk.Analysis 17 | 18 | open Lean 19 | open Lean.Elab 20 | 21 | def configureCommandState (env : Environment) (msg : MessageLog) : Command.State := 22 | { Command.mkState env msg with infoState := { enabled := true }} 23 | 24 | def analyzeInput : AnalysisM AnalysisResult := do 25 | let config := ← read 26 | let context := Parser.mkInputContext config.inputFileContents config.inputFileName 27 | let (header, state, messages) ← Parser.parseHeader context 28 | initializeSearchPaths header config 29 | let options := Options.empty.setBool `trace.Elab.info true 30 | let (environment, messages) ← processHeader header options messages context 0 31 | logInfo s!"Header: {environment.header.mainModule}" 32 | logInfo s!"Header: {environment.header.moduleNames}" 33 | if messages.hasErrors then 34 | for msg in messages.toList do 35 | if msg.severity == .error then 36 | let _ ← logError (← msg.toString) 37 | throw <| IO.userError "Errors during import; aborting" 38 | let commandState := configureCommandState environment messages 39 | let s ← IO.processCommands context state commandState 40 | let result ← resolveTacticList s.commandState.infoState.trees.toList 41 | let messages := s.commandState.messages.msgs.toList.filter (λ m => m.endPos.isSome ) 42 | return ← result.insertMessages messages context.fileMap 43 | -------------------------------------------------------------------------------- /LeanInk/Annotation/Basic.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Configuration 2 | import LeanInk.ListUtil 3 | import LeanInk.Logger 4 | 5 | import LeanInk.Annotation.Util 6 | import LeanInk.Annotation.DataTypes 7 | 8 | import LeanInk.Analysis.DataTypes 9 | import LeanInk.Analysis.Analysis 10 | 11 | namespace LeanInk.Annotation 12 | 13 | open LeanInk.Analysis 14 | 15 | /- 16 | Annotation 17 | -/ 18 | def tokensBetween (head : String.Pos) (tail : Option String.Pos) (compounds: List (Compound Token)) : List (Compound Token) := Id.run do 19 | let mut tokens : Array (Compound Token) := #[] 20 | for token in compounds do 21 | match (tail, token.tailPos) with 22 | | (_, none) => continue 23 | | (some tail, some tokenTail) => 24 | if token.headPos <= tail && tokenTail > head then 25 | tokens ← tokens.push token 26 | | (none, some tokenTail) => 27 | if tokenTail > head then 28 | tokens ← tokens.push token 29 | return tokens.toList 30 | 31 | def matchTokenToAnalysis (tokens : List (Compound Token)) (aux : List Annotation) : List (Compound Sentence) -> List Annotation 32 | | [] => aux 33 | | x::y::xs => 34 | let tokens := (tokens.dropWhile (λ t => x.headPos > t.tailPos.getD t.headPos)) 35 | matchTokenToAnalysis tokens (aux.append [{ sentence := x, tokens := tokensBetween x.headPos y.headPos tokens}]) (y::xs) 36 | | x::xs => 37 | let tokens := (tokens.dropWhile (λ t => x.headPos > t.tailPos.getD t.headPos)) 38 | matchTokenToAnalysis tokens (aux.append [{ sentence := x, tokens := tokensBetween x.headPos none tokens}]) xs 39 | 40 | def annotateFile (analysis : AnalysisResult) : AnalysisM (List Annotation) := do 41 | let compounds ← matchCompounds (toFragmentIntervals analysis.sentences) 42 | let tokens ← matchCompounds (toFragmentIntervals analysis.tokens) 43 | return matchTokenToAnalysis tokens [] compounds 44 | -------------------------------------------------------------------------------- /LeanInk/CLI/Argument.lean: -------------------------------------------------------------------------------- 1 | namespace LeanInk.CLI 2 | 3 | -- ARGUMENT INFO 4 | structure ArgumentInfo where 5 | identifiers : List String 6 | isOptional : Bool := true 7 | help : String 8 | deriving BEq 9 | 10 | structure Flag extends ArgumentInfo where 11 | -- There are no additional fields yet 12 | 13 | structure Environment extends ArgumentInfo where 14 | -- There are no additional fields yet 15 | 16 | -- ARGUMENT 17 | inductive Argument where 18 | | flag (i : Flag) 19 | | environment (i : Environment) 20 | 21 | namespace Argument 22 | def toArgumentInfo : Argument -> ArgumentInfo 23 | | flag i => i.toArgumentInfo 24 | | environment i => i.toArgumentInfo 25 | 26 | def identifiers (self: Argument) : List String := 27 | self.toArgumentInfo.identifiers 28 | 29 | def help (self: Argument) : String := 30 | self.toArgumentInfo.help 31 | 32 | def isOptional (self: Argument) : Bool := 33 | self.toArgumentInfo.isOptional 34 | end Argument 35 | 36 | instance : BEq Argument where 37 | beq (left right : Argument) : Bool := left.identifiers == right.identifiers 38 | 39 | -- RESOLVED ARGUMENT 40 | inductive ResolvedArgument where 41 | | flag (self: Flag) 42 | | env (self: Environment) (val: String) 43 | 44 | namespace ResolvedArgument 45 | def toArgumentInfo : ResolvedArgument -> ArgumentInfo 46 | | flag i => i.toArgumentInfo 47 | | env i _ => i.toArgumentInfo 48 | 49 | def identifiers (self: ResolvedArgument) : List String := 50 | self.toArgumentInfo.identifiers 51 | 52 | def help (self: ResolvedArgument) : String := 53 | self.toArgumentInfo.help 54 | 55 | def isOptional (self: ResolvedArgument) : Bool := 56 | self.toArgumentInfo.isOptional 57 | end ResolvedArgument 58 | 59 | instance : ToString ResolvedArgument where 60 | toString : ResolvedArgument -> String 61 | | ResolvedArgument.flag i => s!"\n[{i.identifiers}]" 62 | | ResolvedArgument.env i v => s!"\n[{i.identifiers}]={v}" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | name: ${{ matrix.name }} 14 | runs-on: ${{ matrix.os }} 15 | defaults: 16 | run: 17 | shell: ${{ matrix.shell || 'sh' }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - name: Ubuntu 23 | os: ubuntu-latest 24 | - name: macOS 25 | os: macos-latest 26 | - name: Windows 27 | os: windows-latest 28 | shell: pwsh 29 | steps: 30 | # Fix CR/LF mangling on Windows 31 | - name: Disable autocrlf in git 32 | run: git config --global core.autocrlf false 33 | 34 | # Checkout repository 35 | - uses: actions/checkout@v2 36 | 37 | # Install elan with nightly toolchain 38 | - name: Install Elan (macOS) 39 | if: matrix.os == 'macos-latest' 40 | run: | 41 | set -o pipefail 42 | curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- --default-toolchain none -y 43 | echo "$HOME/.elan/bin" >> $GITHUB_PATH 44 | 45 | - name: Install Elan (Ubuntu) 46 | if: matrix.os == 'ubuntu-latest' 47 | run: | 48 | curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- --default-toolchain none -y 49 | echo "$HOME/.elan/bin" >> $GITHUB_PATH 50 | 51 | - name: Install Elan (Windows) 52 | if: matrix.os == 'windows-latest' 53 | run: | 54 | curl -O --location https://raw.githubusercontent.com/leanprover/elan/master/elan-init.ps1 55 | .\elan-init.ps1 -NoPrompt 1 -DefaultToolchain none 56 | echo "$HOME\.elan\bin" >> $env:GITHUB_PATH 57 | 58 | # Build and test 59 | - name: Build LeanInk 60 | run: lake build 61 | 62 | # Upload artifact 63 | - name: Upload artifacts 64 | uses: actions/upload-artifact@v2 65 | with: 66 | name: ${{ matrix.os }} 67 | path: build 68 | 69 | - name: Run tests 70 | run: lake script run tests 71 | -------------------------------------------------------------------------------- /test/playground/playground_3.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "def", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": {"type": "Nat", "name": "x", "_type": "typeinfo"}, 15 | "semanticType": "Name.Variable", 16 | "raw": "x", 17 | "link": null, 18 | "docstring": null, 19 | "_type": "token"}, 20 | {"typeinfo": null, 21 | "semanticType": null, 22 | "raw": " := ", 23 | "link": null, 24 | "docstring": null, 25 | "_type": "token"}, 26 | {"typeinfo": {"type": "Nat", "name": "1", "_type": "typeinfo"}, 27 | "semanticType": null, 28 | "raw": "1", 29 | "link": null, 30 | "docstring": null, 31 | "_type": "token"}, 32 | {"typeinfo": null, 33 | "semanticType": null, 34 | "raw": "\n", 35 | "link": null, 36 | "docstring": null, 37 | "_type": "token"}], 38 | "_type": "text"}, 39 | {"messages": [{"contents": "2\n", "_type": "message"}], 40 | "goals": [], 41 | "contents": 42 | [{"typeinfo": null, 43 | "semanticType": "Keyword", 44 | "raw": "#eval", 45 | "link": null, 46 | "docstring": null, 47 | "_type": "token"}], 48 | "_type": "sentence"}, 49 | {"contents": 50 | [{"typeinfo": null, 51 | "semanticType": null, 52 | "raw": " ", 53 | "link": null, 54 | "docstring": null, 55 | "_type": "token"}, 56 | {"typeinfo": {"type": "Nat", "name": "1", "_type": "typeinfo"}, 57 | "semanticType": null, 58 | "raw": "1", 59 | "link": null, 60 | "docstring": null, 61 | "_type": "token"}, 62 | {"typeinfo": null, 63 | "semanticType": null, 64 | "raw": " + ", 65 | "link": null, 66 | "docstring": null, 67 | "_type": "token"}, 68 | {"typeinfo": {"type": "Nat", "name": "x", "_type": "typeinfo"}, 69 | "semanticType": null, 70 | "raw": "x", 71 | "link": null, 72 | "docstring": null, 73 | "_type": "token"}, 74 | {"typeinfo": null, 75 | "semanticType": null, 76 | "raw": " ", 77 | "link": null, 78 | "docstring": null, 79 | "_type": "token"}], 80 | "_type": "text"}] -------------------------------------------------------------------------------- /Main.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.CLI 2 | import LeanInk.Analysis 3 | import LeanInk.Version 4 | 5 | open LeanInk 6 | open LeanInk.CLI 7 | open LeanInk.CLI.Argument 8 | 9 | def app : AppInfo := { 10 | name := "LeanInk" 11 | base := "leanInk" 12 | version := { major := 1, minor := 0, patch := 0 } 13 | description := "LeanInk is a code analysis tool for Lean 4 that extracts proof tactic information. It's main goal is to ease the support for Lean 4 in Alectryon." 14 | } 15 | 16 | def analyzeCommand : Command := { 17 | identifiers := ["analyze", "a"] 18 | help := "Analyzes the given input file and outputs the results in Alectryons fragment json format." 19 | additionalUsageInfo := "" 20 | arguments := [ 21 | environment { 22 | identifiers := ["--lake"] 23 | help := "Specify path to lakefile.lean, so dependencies can be resolved during analysis. Default: none" 24 | }, 25 | flag { 26 | identifiers := ["--verbose"] 27 | help := "Enables verbose output." 28 | }, 29 | flag { 30 | identifiers := ["--prettify-output"] 31 | help := "Prettifies the .leanInk output file" 32 | }, 33 | flag { 34 | identifiers := ["--x-enable-type-info"] 35 | help := "Enables output of experimental type info support for Alectryon. Alectryon will show a hover popup with type information for certain tokens." 36 | }, 37 | flag { 38 | identifiers := ["--x-enable-docStrings"] 39 | help := "Enables output of experimental docStrings support for Alectryon. Alectryon will show a hover popup with the docString for tactics and terms if available." 40 | }, 41 | flag { 42 | identifiers := ["--x-enable-semantic-token"], 43 | help := "Enables output of experimental semantic token support for Alectryon. Alectryon uses this information to implement semantic syntax highlighting." 44 | }, 45 | flag { 46 | identifiers := ["--x-disable-sorry-info"], 47 | help := "Disables alectryon bubbles on blocks containing sorry's." 48 | }, 49 | flag { 50 | identifiers := ["--x-disable-calc-info"], 51 | help := "Disables alectryon bubbles on calc blocks." 52 | } 53 | ] 54 | run := Analysis.exec 55 | } 56 | 57 | def leanVersionCommand : Command := { 58 | identifiers := ["leanVersion", "lV"], 59 | help := "Returns the lean version supported by this leanInk instance.", 60 | arguments := [], 61 | run := λ _ _ => Version.printLeanVersion 62 | } 63 | 64 | def main : List String -> IO UInt32 := runCLI app [analyzeCommand, leanVersionCommand] 65 | -------------------------------------------------------------------------------- /LeanInk/Analysis/SemanticToken.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Analysis.DataTypes 2 | import LeanInk.Configuration 3 | import LeanInk.ListUtil 4 | 5 | import Lean.Elab 6 | import Lean.Syntax 7 | import Lean.Server 8 | 9 | namespace LeanInk.Analysis 10 | 11 | open Lean 12 | open Lean.Elab 13 | open Lean.Server 14 | 15 | structure SemanticTraversalInfo where 16 | stx : Syntax 17 | node : Option Info 18 | 19 | namespace SemanticTraversalInfo 20 | def genSemanticToken (stx : Syntax) (type : SemanticTokenType) : List Token := 21 | let headPos := (stx.getPos? false).getD 0 22 | let tailPos := (stx.getTailPos? false).getD 0 23 | if headPos >= tailPos then 24 | [] 25 | else 26 | [Token.semantic { semanticType := type, headPos := headPos, tailPos := tailPos }] 27 | 28 | def highlightIdentifier (globalTailPos : String.Pos) (info : SemanticTraversalInfo) : AnalysisM (List Token) := do 29 | match info.node with 30 | | some node => do 31 | if Info.isExpanded node then 32 | return [] 33 | match info.node with 34 | | Info.ofTermInfo info => do 35 | let genToken := genSemanticToken info.stx 36 | match info.expr with 37 | | Expr.fvar .. => return genToken SemanticTokenType.variable 38 | | _ => 39 | match info.stx.getPos? with 40 | | some pos => 41 | if pos > globalTailPos then 42 | return genToken SemanticTokenType.property 43 | else 44 | return [] 45 | | _ => pure [] 46 | | _ => pure [] 47 | | _ => pure [] 48 | 49 | def highlightKeyword (stx: Syntax) : AnalysisM (List Token) := do 50 | if let Syntax.atom _ val := stx then 51 | if (val.length > 0 && val.front.isAlpha) || (val.length > 1 && val.front = '#' && (val.get ⟨1⟩).isAlpha) then 52 | return genSemanticToken stx SemanticTokenType.keyword 53 | return [] 54 | 55 | partial def _resolveSemanticTokens (aux : List Token) (info : SemanticTraversalInfo) : AnalysisM (List Token) := do 56 | let stx := info.stx 57 | let headPos := (stx.getPos? false).getD 0 58 | let tailPos := (stx.getTailPos? false).getD 0 59 | if headPos >= tailPos then 60 | return [] 61 | else 62 | match stx with 63 | | `($e.$id:ident) => do 64 | let newToken := genSemanticToken id SemanticTokenType.property 65 | _resolveSemanticTokens (aux ++ newToken) { info with stx := e } 66 | | `($id:ident) => highlightIdentifier tailPos { info with stx := id } 67 | | _ => do 68 | if !(FileWorker.noHighlightKinds.contains stx.getKind) then 69 | let token ← highlightKeyword stx 70 | if stx.isOfKind choiceKind then 71 | _resolveSemanticTokens (aux ++ token) { info with stx := stx[0] } 72 | else 73 | let resolvedTokens := (← stx.getArgs.mapM (λ newStx => _resolveSemanticTokens [] { info with stx := newStx })).toList 74 | return resolvedTokens.foldl (List.mergeSortedLists (λ x y => Positional.headPos x < Positional.headPos y)) (aux ++ token) 75 | else 76 | return [] 77 | end SemanticTraversalInfo 78 | -------------------------------------------------------------------------------- /LeanInk/Analysis/LeanContext.lean: -------------------------------------------------------------------------------- 1 | import Lean.Util.Path 2 | import Lean.Parser.Module 3 | import Lean.Elab 4 | import Lean.Util.Paths 5 | 6 | import LeanInk.Logger 7 | import LeanInk.Configuration 8 | 9 | namespace LeanInk.Analysis 10 | 11 | open System 12 | open Lean 13 | 14 | -- LEAN 15 | def initializeLeanContext : IO Unit := do 16 | let leanPath ← Lean.findSysroot 17 | Lean.initSearchPath leanPath 18 | 19 | -- LAKE 20 | def lakefileName := "lakefile.lean" 21 | def lakefileNamev8 := "lakefile.toml" 22 | def lakeEnvName := "LAKE" 23 | def lakeCmdName := "lake" 24 | def lakePrintPathsCmd := "setup-file" 25 | 26 | def getLakePath : IO String := do 27 | match (← IO.getEnv lakeEnvName) with 28 | | some path => return path 29 | | none => return lakeCmdName 30 | 31 | structure SetupFileOutput where 32 | paths : LeanPaths 33 | -- ignore the rest 34 | deriving ToJson, FromJson 35 | 36 | open IO 37 | def initializeLakeContext (lakeFile : FilePath) (header : Syntax) : AnalysisM Unit := do 38 | if !(← lakeFile.pathExists) then 39 | throw <| IO.userError s!"lakefile does not exist: {lakeFile}" 40 | else if lakeFile.fileName != some lakefileName && lakeFile.fileName != some lakefileNamev8 then 41 | match lakeFile.fileName with 42 | | none => throw <| IO.userError s!"lakefile is not a valid file!" 43 | | some fileName => 44 | throw <| IO.userError s!"lakefile [{fileName}] not called: {lakefileName} || {lakefileNamev8}" 45 | else 46 | logInfo s!"Loading Lake Context with lakefile ({lakeFile})..." 47 | let imports := Lean.Elab.headerToImports header 48 | let arguments := #[lakePrintPathsCmd, (toString lakeFile)] ++ imports.map (toString ·.module) 49 | let lakeProcess ← Process.spawn { 50 | stdin := Process.Stdio.null 51 | stdout := Process.Stdio.piped 52 | stderr := Process.Stdio.inherit 53 | cmd := ← getLakePath 54 | args := arguments 55 | } 56 | let stdout := String.trim (← lakeProcess.stdout.readToEnd) 57 | match (← lakeProcess.wait) with 58 | | 0 => do 59 | let stdout := stdout.split (· == '\n') |>.getLast! 60 | match Json.parse stdout with 61 | | Except.error msg => throw <| IO.userError s!"Failed to parse lake output: {stdout}\nerror: {msg}" 62 | | Except.ok val => match fromJson? val with 63 | | Except.error msg => throw <| IO.userError s!"Failed to decode lake output: {stdout}\nerror: {msg}" 64 | | Except.ok output => do 65 | let output : SetupFileOutput := output 66 | let paths : LeanPaths := output.paths 67 | 68 | initializeLeanContext 69 | initSearchPath (← findSysroot) paths.oleanPath 70 | logInfo s!"{paths.oleanPath}" 71 | logInfo s!"Successfully loaded lake search paths" 72 | | 2 => logInfo s!"No search paths required!" 73 | | _ => throw <| IO.userError s!"Using lake failed! Make sure that lake is installed!" 74 | 75 | def initializeSearchPaths (header : Syntax) (config : Configuration) : AnalysisM Unit := do 76 | match config.lakeFile with 77 | | some lakeFile => do 78 | initializeLakeContext lakeFile header 79 | | none => initializeLeanContext 80 | -------------------------------------------------------------------------------- /LeanInk/Analysis/Basic.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Configuration 2 | import LeanInk.FileHelper 3 | import LeanInk.Annotation.DataTypes 4 | import LeanInk.Annotation.Alectryon 5 | import LeanInk.Logger 6 | import LeanInk.CLI 7 | 8 | import LeanInk.Analysis.Analysis 9 | 10 | import Lean.Util.Path 11 | 12 | namespace LeanInk.Analysis 13 | 14 | open LeanInk.Annotation 15 | open LeanInk.CLI 16 | open Lean 17 | open System 18 | 19 | private def _validateInputFile (file : FilePath) : Bool := isLeanFile file 20 | 21 | private def _buildConfiguration (arguments: List ResolvedArgument) (file: FilePath) : IO Configuration := do 22 | let contents ← IO.FS.readFile file 23 | return { 24 | inputFilePath := file 25 | inputFileContents := contents 26 | lakeFile := getLakeFile? arguments 27 | verbose := containsFlag arguments "--verbose" 28 | prettifyOutput := containsFlag arguments "--prettify-output" 29 | experimentalTypeInfo := containsFlag arguments "--x-enable-type-info" 30 | experimentalDocString := containsFlag arguments "--x-enable-docStrings" 31 | experimentalSemanticType := containsFlag arguments "--x-enable-semantic-token" 32 | experimentalSorryConfig := containsFlag arguments "--x-disable-sorry-info" 33 | experimentalCalcConfig := containsFlag arguments "--x-disable-calc-info" 34 | } 35 | where 36 | getLakeFile? (arguments : List ResolvedArgument) : Option FilePath := 37 | match environmentValue arguments "--lake" with 38 | | none => none 39 | | some string => some (FilePath.mk string) 40 | 41 | def runAnalysis (output : Output) : AnalysisM UInt32 := do 42 | let config ← read 43 | logInfo s!"Starting process with lean file: {config.inputFileName}" 44 | logInfo "Analyzing..." 45 | let result ← analyzeInput config 46 | logInfo "Annotating..." 47 | let annotation ← Annotation.annotateFile result 48 | logInfo "Outputting..." 49 | return ← output.genOutput annotation 50 | 51 | -- EXECUTION 52 | def execAuxM : AnalysisM UInt32 := do 53 | return ← runAnalysis { 54 | name := "Alectryon" 55 | genOutput := Alectryon.genOutput 56 | } 57 | 58 | def execAux (args: List ResolvedArgument) (file: String) : IO UInt32 := do 59 | if not (_validateInputFile file) then do 60 | Logger.logError s!"Provided file \"{file}\" is not lean file." 61 | else 62 | IO.println s!"Starting Analysis for: \"{file}\"" 63 | let config ← _buildConfiguration args file 64 | return ← (execAuxM.run config) 65 | 66 | /- 67 | `enableInitializersExecution` is usually only run from the C part of the 68 | frontend and needs to be used with care but it is required in order 69 | to work with custom user extensions correctly. 70 | -/ 71 | @[implemented_by enableInitializersExecution] 72 | private def enableInitializersExecutionWrapper : IO Unit := pure () 73 | 74 | def exec (args: List ResolvedArgument) : List String -> IO UInt32 75 | | [] => do Logger.logError s!"No input files provided" 76 | | files => do 77 | enableInitializersExecutionWrapper 78 | -- Span task for every file? 79 | for file in files do 80 | if (← execAux args file) != 0 then 81 | return ← Logger.logError s!"Analysis for \"{file}\" failed!" 82 | return 0 83 | -------------------------------------------------------------------------------- /test/playground/Simple.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "def", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": {"type": "Nat → Nat → Nat", "name": "add", "_type": "typeinfo"}, 15 | "semanticType": "Name.Variable", 16 | "raw": "add", 17 | "link": null, 18 | "docstring": null, 19 | "_type": "token"}, 20 | {"typeinfo": null, 21 | "semanticType": null, 22 | "raw": " (", 23 | "link": null, 24 | "docstring": null, 25 | "_type": "token"}, 26 | {"typeinfo": {"type": "Nat", "name": "x", "_type": "typeinfo"}, 27 | "semanticType": "Name.Variable", 28 | "raw": "x", 29 | "link": null, 30 | "docstring": null, 31 | "_type": "token"}, 32 | {"typeinfo": null, 33 | "semanticType": null, 34 | "raw": " ", 35 | "link": null, 36 | "docstring": null, 37 | "_type": "token"}, 38 | {"typeinfo": {"type": "Nat", "name": "y", "_type": "typeinfo"}, 39 | "semanticType": "Name.Variable", 40 | "raw": "y", 41 | "link": null, 42 | "docstring": null, 43 | "_type": "token"}, 44 | {"typeinfo": null, 45 | "semanticType": null, 46 | "raw": " : ", 47 | "link": null, 48 | "docstring": null, 49 | "_type": "token"}, 50 | {"typeinfo": {"type": "Type", "name": "Nat", "_type": "typeinfo"}, 51 | "semanticType": null, 52 | "raw": "Nat", 53 | "link": null, 54 | "docstring": 55 | "The type of natural numbers, starting at zero. It is defined as an\ninductive type freely generated by \"zero is a natural number\" and\n\"the successor of a natural number is a natural number\".\n\nYou can prove a theorem `P n` about `n : Nat` by `induction n`, which will\nexpect a proof of the theorem for `P 0`, and a proof of `P (succ i)` assuming\na proof of `P i`. The same method also works to define functions by recursion\non natural numbers: induction and recursion are two expressions of the same\noperation from Lean's point of view.\n\n```\nopen Nat\nexample (n : Nat) : n < succ n := by\n induction n with\n | zero =>\n show 0 < 1\n decide\n | succ i ih => -- ih : i < succ i\n show succ i < succ (succ i)\n exact Nat.succ_lt_succ ih\n```\n\nThis type is special-cased by both the kernel and the compiler:\n* The type of expressions contains \"`Nat` literals\" as a primitive constructor,\n and the kernel knows how to reduce zero/succ expressions to nat literals.\n* If implemented naively, this type would represent a numeral `n` in unary as a\n linked list with `n` links, which is horribly inefficient. Instead, the\n runtime itself has a special representation for `Nat` which stores numbers up\n to 2^63 directly and larger numbers use an arbitrary precision \"bignum\"\n library (usually [GMP](https://gmplib.org/)).\n", 56 | "_type": "token"}, 57 | {"typeinfo": null, 58 | "semanticType": null, 59 | "raw": ") := ", 60 | "link": null, 61 | "docstring": null, 62 | "_type": "token"}, 63 | {"typeinfo": {"type": "Nat", "name": "x", "_type": "typeinfo"}, 64 | "semanticType": "Name.Variable", 65 | "raw": "x", 66 | "link": null, 67 | "docstring": null, 68 | "_type": "token"}, 69 | {"typeinfo": null, 70 | "semanticType": null, 71 | "raw": " + ", 72 | "link": null, 73 | "docstring": null, 74 | "_type": "token"}, 75 | {"typeinfo": {"type": "Nat", "name": "y", "_type": "typeinfo"}, 76 | "semanticType": "Name.Variable", 77 | "raw": "y", 78 | "link": null, 79 | "docstring": null, 80 | "_type": "token"}], 81 | "_type": "text"}] -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | import Init.System.IO 3 | open System Lake DSL 4 | open System.FilePath IO IO.FS 5 | 6 | package leanInk 7 | 8 | lean_lib LeanInk 9 | 10 | @[default_target] 11 | lean_exe leanInk { 12 | root := `Main 13 | supportInterpreter := true 14 | } 15 | 16 | /-! Run the leanInk that is built locally to analyze the given test file. 17 | If there is a lakefile.lean present then pass the additional `--lake` option -/ 18 | def runLeanInk (leanInkExe: FilePath) (test : FilePath) : IO UInt32 := do 19 | let realPath ← realPath leanInkExe 20 | if ! (← leanInkExe.pathExists) then 21 | println s!"Could not find leanInk executable at {leanInkExe}" 22 | println s!"Please run `lake build` in the LeanInk directory" 23 | return 1 24 | 25 | if let some fileName := test.fileName then 26 | let mut args := #["analyze", fileName, "--x-enable-type-info", "--x-enable-docStrings", "--x-enable-semantic-token", "--prettify-output"] 27 | if let some dir := test.parent then 28 | let lakefile := dir / "lakefile.lean" 29 | if (← lakefile.pathExists) then 30 | println s!"Running test {test} using lake..." 31 | args := args ++ #["--lake", "lakefile.lean"] 32 | else 33 | println s!"Running test {test}..." 34 | 35 | let out ← Process.output { cmd := realPath.normalize.toString, args := args, cwd := test.parent } 36 | if out.exitCode = 0 then 37 | return 0 38 | else 39 | println s!"leanInk failed with {out.stdout} {out.stderr}" 40 | return out.exitCode 41 | return 1 42 | 43 | /-! Compare the text contents of two files -/ 44 | def runDiff (actual : FilePath) (expected : FilePath) : IO Bool := do 45 | let actualStr ← FS.readFile actual 46 | let expectedStr ← FS.readFile expected 47 | return actualStr.trim = expectedStr.trim 48 | 49 | def copyFile (src : FilePath) (dst : FilePath) : IO Unit := do 50 | FS.writeBinFile dst (← FS.readBinFile src) 51 | 52 | def indexOf? [BEq α] (xs : List α) (s : α) (start := 0): Option Nat := 53 | match xs with 54 | | [] => none 55 | | a :: tail => if a == s then some start else indexOf? tail s (start+1) 56 | 57 | /-! Walk the `test` folder looking for every `.lean` file that's not a `lakefile` or part of an 58 | `.lake/packages` and run `leanInk` on it. If `capture` is true then update the `.lean.leanInk.expected` 59 | file, otherwise compare the new output to the expected output and return an error if they are 60 | different. -/ 61 | def execute (leanInkExe: FilePath) (capture : Bool) : IO UInt32 := do 62 | let root : FilePath := "." / "test" 63 | let dirs ← walkDir root (enter := fun path => return path.fileName != "packages" ) 64 | let mut retVal : UInt32 := 0 65 | for test in dirs do 66 | if test.extension = "lean" && test.fileName != "lakefile.lean" then 67 | if let some fileName := test.fileName then 68 | let actual := test.withFileName (fileName ++ ".leanInk") 69 | let expected := test.withFileName (fileName ++ ".leanInk.expected") 70 | if (← expected.pathExists) then 71 | let rc ← runLeanInk leanInkExe test 72 | if rc ≠ 0 then 73 | return 1 74 | else if (capture) then 75 | println s!" UPDATING {expected}" 76 | copyFile actual expected 77 | else 78 | if (← runDiff actual expected) then 79 | println s!" SUCCESS" 80 | else 81 | println s!" FAILED: diff {expected} {actual}" 82 | retVal := retVal + 1 83 | else 84 | println s!" FAILED: expected output file is missing: {expected}" 85 | retVal := retVal + 1 86 | 87 | if retVal > 0 then 88 | println s!"FAILED: {retVal} tests failed!" 89 | return retVal 90 | 91 | def getLeanInkExePath : ScriptM (Option FilePath) := do 92 | let ws ← Lake.getWorkspace 93 | if let some exe := ws.findLeanExe? `leanInk then 94 | return exe.file 95 | return none 96 | 97 | script tests (args) do 98 | if args.length > 0 then 99 | println s!"Unexpected arguments: {args}" 100 | if let some leanInkExe ← getLeanInkExePath then 101 | println "Running diff tests for leanInk" 102 | execute leanInkExe False 103 | else 104 | println "Cannot find `leanInk` target path" 105 | return 1 106 | 107 | script capture (args) do 108 | if args.length > 0 then 109 | println s!"Unexpected arguments: {args}" 110 | if let some leanInkExe ← getLeanInkExePath then 111 | println "Updating .leanInk.expected output files" 112 | execute leanInkExe True 113 | else 114 | println "Cannot find `leanInk` target path" 115 | return 1 116 | -------------------------------------------------------------------------------- /LeanInk/CLI/Basic.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.CLI.Argument 2 | import LeanInk.CLI.Command 3 | import LeanInk.CLI.Result 4 | import LeanInk.CLI.Help 5 | 6 | namespace LeanInk.CLI 7 | 8 | inductive CLIError where 9 | | unknownCommand (arg: String): CLIError 10 | | noArgumentsProvided : CLIError 11 | | noCommandsProvided : CLIError 12 | 13 | namespace CLIError 14 | 15 | instance : ToString CLIError where 16 | toString 17 | | unknownCommand c => s!"Unknown command: {c}" 18 | | noArgumentsProvided => s!"Please choose a command for execution!" 19 | | noCommandsProvided => s!"Implementation error: No root commands available!" 20 | 21 | end CLIError 22 | 23 | -- HELPER METHODS 24 | def argument (args : List ResolvedArgument) (identifier : String) : Option ResolvedArgument := 25 | List.find? (λ x => x.identifiers.elem identifier) args 26 | 27 | def environmentValue (args : List ResolvedArgument) (identifier : String) : Option String := 28 | match argument args identifier with 29 | | ResolvedArgument.env _ a => a 30 | | _ => none 31 | 32 | def containsFlag (args : List ResolvedArgument) (identifier : String) : Bool := 33 | match argument args identifier with 34 | | ResolvedArgument.flag _ => true 35 | | _ => false 36 | 37 | -- METHODS 38 | open Result in 39 | /-- Resolves a command list given the available commands. 40 | 41 | Errors: 42 | - throws CLIError.noCommandsProvided if available commands is empty 43 | - throws CLIError.noArgumentsProvided if the argument list is empty 44 | - throws CLIError.unknownCommand if the first argument cannot be resolved to any of the available commands. 45 | -/ 46 | private def _resolveCommandList (available: List Command) (args: List String) : Result CLIError (Command × List String) := 47 | if available.isEmpty then 48 | failure CLIError.noCommandsProvided -- If no root commands are available we throw an error 49 | else 50 | match args with 51 | | [] => failure CLIError.noArgumentsProvided -- If no arguments were provided, we cannot resolve anything 52 | | a::as => 53 | match List.find? (λ x => x.identifiers.elem a) available with 54 | | none => failure (CLIError.unknownCommand a) 55 | | some c => success (c, as) 56 | 57 | private partial def resolveArgumentList (available: List Argument) (args: List String) : List ResolvedArgument × List String := 58 | if available.isEmpty then 59 | ([], args) 60 | else 61 | match args with 62 | | [] => ([], args) -- No arguments left 63 | | a::as => 64 | let argument := List.find? (λ x => x.identifiers.elem a) available 65 | match argument with 66 | | none => 67 | let (res, unres) := resolveArgumentList available as 68 | (res, a::unres) 69 | | some argument => 70 | match argument with 71 | | Argument.flag i => 72 | let resolvedArg := ResolvedArgument.flag i 73 | let (otherArgs, unresolved) := resolveArgumentList (available.erase argument) as 74 | (resolvedArg::otherArgs, unresolved) 75 | | Argument.environment i => 76 | match as with 77 | | [] => 78 | let (res, unres) := resolveArgumentList available as 79 | (res, a::unres) 80 | | b::bs => 81 | let resolvedArg := ResolvedArgument.env i b 82 | let (otherArgs, unresolved) := resolveArgumentList (available.erase argument) bs 83 | (resolvedArg::otherArgs, unresolved) 84 | 85 | 86 | def runHelp (app: AppInfo) (available: List Command) (arguments : List String) : IO UInt32 := do 87 | match _resolveCommandList available arguments with 88 | | Result.failure _ => do 89 | IO.println (generateDefaultHelp app available) 90 | return 1 91 | | Result.success (command, _) => do 92 | IO.println (generateCommandHelp app command) 93 | return 0 94 | 95 | -- ENTRY 96 | def runCLI (app: AppInfo) (commands: List Command) (args: List String) : IO UInt32 := do 97 | let commands := helpCommand::versionCommand::commands 98 | match _resolveCommandList commands args with -- We automatically add the help and version command internally for command resolution. 99 | | Result.failure _ => do 100 | IO.println (generateDefaultHelp app commands) 101 | return 1 102 | | Result.success result => do 103 | let (command, args) := result 104 | if command.identifiers == helpCommand.identifiers then 105 | return (← runHelp app commands args) -- We escape the actual command execution and handle the help command ourselves. 106 | else if command.identifiers == versionCommand.identifiers then 107 | IO.println app.versionString 108 | return 0 109 | else 110 | let (resArgs, unresArgs) := resolveArgumentList command.arguments args 111 | return (← command.run resArgs unresArgs) -------------------------------------------------------------------------------- /LeanInk/Annotation/Util.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Configuration 2 | import LeanInk.Logger 3 | 4 | import LeanInk.Analysis.Analysis 5 | import LeanInk.Analysis.DataTypes 6 | 7 | namespace LeanInk.Annotation 8 | 9 | open LeanInk.Analysis 10 | 11 | /- FRAGMENT INTERVAL -/ 12 | inductive FragmentInterval (a : Type u) where 13 | | head (pos: String.Pos) (fragment: a) (idx: Nat) 14 | | tail (pos: String.Pos) (fragment: a) (idx: Nat) 15 | deriving Inhabited 16 | 17 | namespace FragmentInterval 18 | def position : FragmentInterval a -> String.Pos 19 | | head p _ _ => p 20 | | tail p _ _ => p 21 | 22 | def idx : FragmentInterval a -> Nat 23 | | head _ _ idx => idx 24 | | tail _ _ idx => idx 25 | 26 | def fragment : FragmentInterval a -> a 27 | | head _ f _ => f 28 | | tail _ f _ => f 29 | 30 | def headPos [Positional a] (f : FragmentInterval a) : String.Pos := Positional.headPos (fragment f) 31 | def tailPos [Positional a] (f : FragmentInterval a) : String.Pos := Positional.tailPos (fragment f) 32 | 33 | def enumerateFragment (self : FragmentInterval a) : (Nat × a) := (self.idx, self.fragment) 34 | 35 | def isHead : FragmentInterval a -> Bool 36 | | head _ _ _ => true 37 | | tail _ _ _ => false 38 | 39 | def isTail : FragmentInterval a -> Bool 40 | | head _ _ _ => false 41 | | tail _ _ _ => true 42 | end FragmentInterval 43 | 44 | instance [ToString a] : ToString (FragmentInterval a) where 45 | toString (self : FragmentInterval a) : String := s!"" 46 | 47 | /- FUNCTIONS -/ 48 | def toFragmentIntervals { x : Type } [Positional x] [Inhabited x] [Inhabited x] (positionals : List x) : List (FragmentInterval x) := 49 | let indexedPositionals := positionals.enum.map (λ (idx, f) => [FragmentInterval.head (Positional.headPos f) f idx, FragmentInterval.tail (Positional.tailPos f) f idx]) 50 | let mergedPositionals := indexedPositionals.join 51 | List.sort (λ x y => x.position < y.position) mergedPositionals 52 | 53 | def maxTailPos (y : String.Pos) : Option String.Pos -> String.Pos 54 | | none => y 55 | | some x => if x < y then x else y 56 | 57 | @[inline] 58 | def _insertCompound [Positional a] [ToString a] (e : FragmentInterval a) (compounds : List (Compound a)) : AnalysisM (List (Compound a)) := do 59 | match compounds with 60 | | [] => do 61 | if e.isHead then 62 | let newCompound : Compound a := { headPos := e.position, tailPos := none, fragments := [e.enumerateFragment] } 63 | logInfo s!"NO COMPOUND -> GENERATING NEW FROM HEAD AT {e.position} -> {newCompound}" 64 | return [newCompound] 65 | else 66 | logInfo s!"FAILURE: Unexpected tail!" 67 | return [] 68 | | c::cs => do 69 | if e.isHead then 70 | if c.headPos == e.position then 71 | let updatedCompound := { c with tailPos := none, fragments := c.fragments.append [e.enumerateFragment] } 72 | logInfo s!"FOUND COMPOUND {c} -> UPDATING CURRENT WITH HEAD {e.idx} -> {updatedCompound}" 73 | return updatedCompound::cs 74 | else 75 | let oldCompound := { c with tailPos := e.position } 76 | let newCompound := { c with headPos := e.position, tailPos := none, fragments := c.fragments.append [e.enumerateFragment] } 77 | logInfo s!"FOUND COMPOUND {c} -> CREATING NEW COMPOUND WITH HEAD {e.idx} -> {newCompound}" 78 | return newCompound::oldCompound::cs 79 | else 80 | let newFragments := c.fragments.filter (λ x => x.1 != e.idx) -- Remove all fragments with the same idx 81 | let mut newTailPos := c.tailPos 82 | if newFragments.isEmpty then 83 | newTailPos := none 84 | if c.headPos == e.position then 85 | let updatedCompound := { c with tailPos := none, fragments := newFragments} 86 | logInfo s!"FOUND COMPOUND {c} -> UPDATING CURRENT WITH TAIL AT {e.position} -> {updatedCompound}" 87 | return updatedCompound::cs 88 | else 89 | /- 90 | It may be the case that the newFragments list isEmpty. This is totally fine as we need to 91 | insert text spacers later for the text. No we can simply generate a text fragment whenever a compound is empty. 92 | -/ 93 | let oldCompound := { c with tailPos := e.position } 94 | let newCompound := { headPos := e.position, tailPos := none, fragments := newFragments } 95 | logInfo s!"FOUND COMPOUND {c} -> CREATING NEW COMPOUND WITH TAIL {e.idx} -> {newCompound}" 96 | return newCompound::oldCompound::cs 97 | 98 | def matchCompounds [Positional a] [ToString a] (events : List (FragmentInterval a)) : AnalysisM (List (Compound a)) := do 99 | let mut compounds : List (Compound a) := [{ headPos := 0, tailPos := none, fragments := [] }] 100 | for e in events do 101 | compounds ← _insertCompound e compounds 102 | return compounds.reverse 103 | -------------------------------------------------------------------------------- /test/theorem_proving/001.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "theorem", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}], 14 | "_type": "text"}, 15 | {"messages": 16 | [{"contents": "Warning: declaration uses 'sorry'", "_type": "message"}], 17 | "goals": [], 18 | "contents": 19 | [{"typeinfo": 20 | {"type": "∀ (p q : Prop), p → q → p ∧ q ∧ p", 21 | "name": "test", 22 | "_type": "typeinfo"}, 23 | "semanticType": "Name.Variable", 24 | "raw": "test", 25 | "link": null, 26 | "docstring": null, 27 | "_type": "token"}], 28 | "_type": "sentence"}, 29 | {"contents": 30 | [{"typeinfo": null, 31 | "semanticType": null, 32 | "raw": " (", 33 | "link": null, 34 | "docstring": null, 35 | "_type": "token"}, 36 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 37 | "semanticType": "Name.Variable", 38 | "raw": "p", 39 | "link": null, 40 | "docstring": null, 41 | "_type": "token"}, 42 | {"typeinfo": null, 43 | "semanticType": null, 44 | "raw": " ", 45 | "link": null, 46 | "docstring": null, 47 | "_type": "token"}, 48 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 49 | "semanticType": "Name.Variable", 50 | "raw": "q", 51 | "link": null, 52 | "docstring": null, 53 | "_type": "token"}, 54 | {"typeinfo": null, 55 | "semanticType": null, 56 | "raw": " : ", 57 | "link": null, 58 | "docstring": null, 59 | "_type": "token"}, 60 | {"typeinfo": {"type": "Type", "name": "Prop", "_type": "typeinfo"}, 61 | "semanticType": null, 62 | "raw": "Prop", 63 | "link": null, 64 | "docstring": null, 65 | "_type": "token"}, 66 | {"typeinfo": null, 67 | "semanticType": null, 68 | "raw": ") (", 69 | "link": null, 70 | "docstring": null, 71 | "_type": "token"}, 72 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 73 | "semanticType": "Name.Variable", 74 | "raw": "hp", 75 | "link": null, 76 | "docstring": null, 77 | "_type": "token"}, 78 | {"typeinfo": null, 79 | "semanticType": null, 80 | "raw": " : ", 81 | "link": null, 82 | "docstring": null, 83 | "_type": "token"}, 84 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 85 | "semanticType": "Name.Variable", 86 | "raw": "p", 87 | "link": null, 88 | "docstring": null, 89 | "_type": "token"}, 90 | {"typeinfo": null, 91 | "semanticType": null, 92 | "raw": ") (", 93 | "link": null, 94 | "docstring": null, 95 | "_type": "token"}, 96 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 97 | "semanticType": "Name.Variable", 98 | "raw": "hq", 99 | "link": null, 100 | "docstring": null, 101 | "_type": "token"}, 102 | {"typeinfo": null, 103 | "semanticType": null, 104 | "raw": " : ", 105 | "link": null, 106 | "docstring": null, 107 | "_type": "token"}, 108 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 109 | "semanticType": "Name.Variable", 110 | "raw": "q", 111 | "link": null, 112 | "docstring": null, 113 | "_type": "token"}, 114 | {"typeinfo": null, 115 | "semanticType": null, 116 | "raw": ") : ", 117 | "link": null, 118 | "docstring": null, 119 | "_type": "token"}, 120 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 121 | "semanticType": "Name.Variable", 122 | "raw": "p", 123 | "link": null, 124 | "docstring": null, 125 | "_type": "token"}, 126 | {"typeinfo": null, 127 | "semanticType": null, 128 | "raw": " ∧ ", 129 | "link": null, 130 | "docstring": null, 131 | "_type": "token"}, 132 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 133 | "semanticType": "Name.Variable", 134 | "raw": "q", 135 | "link": null, 136 | "docstring": null, 137 | "_type": "token"}, 138 | {"typeinfo": null, 139 | "semanticType": null, 140 | "raw": " ∧ ", 141 | "link": null, 142 | "docstring": null, 143 | "_type": "token"}, 144 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 145 | "semanticType": "Name.Variable", 146 | "raw": "p", 147 | "link": null, 148 | "docstring": null, 149 | "_type": "token"}, 150 | {"typeinfo": null, 151 | "semanticType": null, 152 | "raw": " :=\n ", 153 | "link": null, 154 | "docstring": null, 155 | "_type": "token"}, 156 | {"typeinfo": {"type": "p ∧ q ∧ p", "name": "sorry", "_type": "typeinfo"}, 157 | "semanticType": null, 158 | "raw": "sorry", 159 | "link": null, 160 | "docstring": null, 161 | "_type": "token"}], 162 | "_type": "text"}] -------------------------------------------------------------------------------- /LeanInk/Analysis/DataTypes.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.ListUtil 2 | 3 | import Lean.Elab 4 | import Lean.Data.Lsp 5 | import Lean.Syntax 6 | import Lean.Server 7 | 8 | namespace LeanInk.Analysis 9 | 10 | open Lean 11 | open Lean.Elab 12 | open Lean.Meta 13 | 14 | /- Positional -/ 15 | class Positional (α : Type u) where 16 | headPos : α -> String.Pos 17 | tailPos : α -> String.Pos 18 | 19 | namespace Positional 20 | def length { α : Type u } [Positional α] (self : α) : String.Pos := (Positional.tailPos self) - (Positional.headPos self) 21 | 22 | def smallest? { α : Type u } [Positional α] (list : List α) : Option α := List.foldl (λ a y => 23 | let y : α := y -- We need to help the compiler a bit here otherwise it thinks `y : Option α` 24 | match a with 25 | | none => y 26 | | some x => if (Positional.length x) <= (Positional.length y) then x else y 27 | ) none list 28 | end Positional 29 | 30 | /- Fragment -/ 31 | /-- 32 | A `Fragment` is a simple structure that describes an interval within the source text. 33 | This is similar to the `Positional` type class. However the structure is used as a parent for other structures 34 | in the Analysis. As a result every `Fragment` automatically conforms to `Positional` 35 | -/ 36 | structure Fragment where 37 | headPos : String.Pos 38 | tailPos : String.Pos 39 | deriving Inhabited 40 | 41 | instance : Positional Fragment where 42 | headPos := Fragment.headPos 43 | tailPos := Fragment.tailPos 44 | 45 | /- Token -/ 46 | /-- 47 | `SemanticTokenInfo` describe the semantic info of the token, which can and should be used for semantic syntax highlighting. 48 | -/ 49 | inductive SemanticTokenType where 50 | | property 51 | | «variable» 52 | | keyword 53 | 54 | structure SemanticTokenInfo extends Fragment where 55 | semanticType: Option SemanticTokenType := none 56 | deriving Inhabited 57 | 58 | instance : Positional SemanticTokenInfo where 59 | headPos := (λ x => x.toFragment.headPos) 60 | tailPos := (λ x => x.toFragment.tailPos) 61 | 62 | /-- 63 | The `TypeTokenInfo` describes the metadata of a source text token that conforms 64 | to a specific type within our type system. It may also describe just a docstring associated with the token. 65 | We combine both these informatio together, as the docstring is naturally attached to the type. 66 | E.g.: A variable token `p` might conform to type `Nat` and has the docstring for `Nat` 67 | -/ 68 | structure TypeTokenInfo extends Fragment where 69 | type: Option String 70 | docString: Option String 71 | deriving Inhabited 72 | 73 | instance : Positional TypeTokenInfo where 74 | headPos := (λ x => x.toFragment.headPos) 75 | tailPos := (λ x => x.toFragment.tailPos) 76 | 77 | /-- 78 | A `Token` describes the metadata of a specific range of source text. 79 | E.g.: a `Token.type` describes for some variable `p` that it conforms to type `Nat`. 80 | For every category of metadata there should be an additonal constructor with specified `TokenInfo` to make sure the 81 | individual tokens can be analyzed independently and only based on one aspect of metadata. 82 | This also prevents from having tokens with a huge amount of `Option` fields. 83 | -/ 84 | inductive Token where 85 | | type (info: TypeTokenInfo) 86 | | semantic (info: SemanticTokenInfo) 87 | deriving Inhabited 88 | 89 | instance : ToString Token where 90 | toString : Token -> String 91 | | Token.type _ => "Type" 92 | | Token.semantic _ => "Semantic" 93 | 94 | namespace Token 95 | def toFragment : Token -> Fragment 96 | | type info => info.toFragment 97 | | semantic info => info.toFragment 98 | 99 | def toTypeTokenInfo? : Token -> Option TypeTokenInfo 100 | | type info => info 101 | | _ => none 102 | 103 | def toSemanticTokenInfo? : Token -> Option SemanticTokenInfo 104 | | semantic info => info 105 | | _ => none 106 | end Token 107 | 108 | instance : Positional Token where 109 | headPos := (λ x => x.toFragment.headPos) 110 | tailPos := (λ x => x.toFragment.tailPos) 111 | 112 | /- Tactics -/ 113 | structure Hypothesis where 114 | names : List String 115 | type : String 116 | body : String 117 | 118 | structure Goal where 119 | name : String 120 | conclusion : String 121 | hypotheses : List Hypothesis 122 | 123 | structure Tactic extends Fragment where 124 | goalsBefore : List Goal 125 | goalsAfter : List Goal 126 | deriving Inhabited 127 | 128 | instance : Positional Tactic where 129 | headPos := (λ x => x.toFragment.headPos) 130 | tailPos := (λ x => x.toFragment.tailPos) 131 | 132 | structure Message extends Fragment where 133 | msg: String 134 | deriving Inhabited 135 | 136 | /- Sentence -/ 137 | inductive Sentence where 138 | | tactic (info: Tactic) 139 | | message (info: Message) 140 | deriving Inhabited 141 | 142 | instance : ToString Sentence where -- TODO: Improve this 143 | toString : Sentence -> String 144 | | Sentence.tactic t => s!"Tactic {t.headPos}-{t.tailPos}" 145 | | Sentence.message _ => "Message" 146 | 147 | namespace Sentence 148 | def toFragment : Sentence -> Fragment 149 | | tactic info => info.toFragment 150 | | message info => info.toFragment 151 | 152 | def asTactic? : Sentence -> Option Tactic 153 | | tactic info => info 154 | | _ => none 155 | 156 | def asMessage? : Sentence -> Option Message 157 | | message info => info 158 | | _ => none 159 | end Sentence 160 | 161 | instance : Positional Sentence where 162 | headPos := (λ x => x.toFragment.headPos) 163 | tailPos := (λ x => x.toFragment.tailPos) 164 | 165 | /- InfoTree -/ 166 | def Info.isExpanded (self : Info) : Bool := 167 | let stx := Info.stx self 168 | match stx.getHeadInfo, stx.getTailInfo with 169 | | SourceInfo.original .., SourceInfo.original .. => false 170 | | _, _ => true 171 | -------------------------------------------------------------------------------- /test/playground/Function.lean: -------------------------------------------------------------------------------- 1 | /-| 2 | Copyright (c) 2014 Microsoft Corporation. All rights reserved. 3 | Released under Apache 2.0 license as described in the file LICENSE. 4 | Author: Leonardo de Moura, Jeremy Avigad, Haitao Zhang 5 | |-/ 6 | -- a port of core Lean `init/function.lean` 7 | 8 | /-! 9 | # General operations on functions 10 | -/ 11 | 12 | namespace Function 13 | 14 | variable {α : Sort u₁} {β : Sort u₂} {φ : Sort u₃} {δ : Sort u₄} {ζ : Sort u₁} 15 | 16 | @[reducible] def comp_right (f : β → β → β) (g : α → β) : β → α → β := 17 | λ b a => f b (g a) 18 | 19 | @[reducible] def comp_left (f : β → β → β) (g : α → β) : α → β → β := 20 | λ a b => f (g a) b 21 | 22 | /-- Given functions `f : β → β → φ` and `g : α → β`, produce a function `α → α → φ` that evaluates 23 | `g` on each argument, then applies `f` to the results. Can be used, e.g., to transfer a relation 24 | from `β` to `α`. -/ 25 | @[reducible] def on_fun (f : β → β → φ) (g : α → β) : α → α → φ := 26 | λ x y => f (g x) (g y) 27 | 28 | @[reducible] def combine (f : α → β → φ) (op : φ → δ → ζ) (g : α → β → δ) 29 | : α → β → ζ := 30 | λ x y => op (f x y) (g x y) 31 | 32 | @[reducible] def swap {φ : α → β → Sort u₃} (f : ∀ x y, φ x y) : ∀ y x, φ x y := 33 | λ y x => f x y 34 | 35 | @[reducible] def app {β : α → Sort u₂} (f : ∀ x, β x) (x : α) : β x := 36 | f x 37 | 38 | theorem left_id (f : α → β) : id ∘ f = f := rfl 39 | 40 | theorem right_id (f : α → β) : f ∘ id = f := rfl 41 | 42 | @[simp] theorem comp_app (f : β → φ) (g : α → β) (a : α) : (f ∘ g) a = f (g a) := rfl 43 | 44 | theorem comp.assoc (f : φ → δ) (g : β → φ) (h : α → β) : (f ∘ g) ∘ h = f ∘ (g ∘ h) := rfl 45 | 46 | @[simp] theorem comp.left_id (f : α → β) : id ∘ f = f := rfl 47 | 48 | @[simp] theorem comp.right_id (f : α → β) : f ∘ id = f := rfl 49 | 50 | theorem comp_const_right (f : β → φ) (b : β) : f ∘ (const α b) = const α (f b) := rfl 51 | 52 | /-- A function `f : α → β` is called injective if `f x = f y` implies `x = y`. -/ 53 | @[reducible] def injective (f : α → β) : Prop := ∀ {a₁ a₂}, f a₁ = f a₂ → a₁ = a₂ 54 | 55 | theorem injective.comp {g : β → φ} {f : α → β} (hg : injective g) (hf : injective f) : 56 | injective (g ∘ f) := 57 | λ h => hf (hg h) 58 | 59 | /-- A function `f : α → β` is calles surjective if every `b : β` is equal to `f a` 60 | for some `a : α`. -/ 61 | @[reducible] def surjective (f : α → β) : Prop := ∀ b, ∃ a, f a = b 62 | 63 | theorem surjective.comp {g : β → φ} {f : α → β} (hg : surjective g) (hf : surjective f) : 64 | surjective (g ∘ f) := 65 | λ (c : φ) => Exists.elim (hg c) (λ b hb => Exists.elim (hf b) (λ a ha => 66 | Exists.intro a (show g (f a) = c from (Eq.trans (congrArg g ha) hb)))) 67 | 68 | /-- A function is called bijective if it is both injective and surjective. -/ 69 | def bijective (f : α → β) := injective f ∧ surjective f 70 | 71 | theorem bijective.comp {g : β → φ} {f : α → β} : bijective g → bijective f → bijective (g ∘ f) 72 | | ⟨h_ginj, h_gsurj⟩, ⟨h_finj, h_fsurj⟩ => ⟨h_ginj.comp h_finj, h_gsurj.comp h_fsurj⟩ 73 | 74 | /-- `left_inverse g f` means that g is a left inverse to f. That is, `g ∘ f = id`. -/ 75 | def left_inverse (g : β → α) (f : α → β) : Prop := ∀ x, g (f x) = x 76 | 77 | /-- `has_left_inverse f` means that `f` has an unspecified left inverse. -/ 78 | def has_left_inverse (f : α → β) : Prop := ∃ finv : β → α, left_inverse finv f 79 | 80 | /-- `right_inverse g f` means that g is a right inverse to f. That is, `f ∘ g = id`. -/ 81 | def right_inverse (g : β → α) (f : α → β) : Prop := left_inverse f g 82 | 83 | /-- `has_right_inverse f` means that `f` has an unspecified right inverse. -/ 84 | def has_right_inverse (f : α → β) : Prop := ∃ finv : β → α, right_inverse finv f 85 | 86 | theorem left_inverse.injective {g : β → α} {f : α → β} : left_inverse g f → injective f := 87 | λ h a b hf => h a ▸ h b ▸ hf ▸ rfl 88 | 89 | theorem has_left_inverse.injective {f : α → β} : has_left_inverse f → injective f := 90 | λ h => Exists.elim h (λ finv inv => inv.injective) 91 | 92 | theorem right_inverse_of_injective_of_left_inverse {f : α → β} {g : β → α} 93 | (injf : injective f) (lfg : left_inverse f g) : 94 | right_inverse f g := 95 | λ x => injf $ lfg $ f x 96 | 97 | theorem right_inverse.surjective {f : α → β} {g : β → α} (h : right_inverse g f) : surjective f := 98 | λ y => ⟨g y, h y⟩ 99 | 100 | theorem has_right_inverse.surjective {f : α → β} : has_right_inverse f → surjective f 101 | | ⟨finv, inv⟩ => inv.surjective 102 | 103 | theorem left_inverse_of_surjective_of_right_inverse {f : α → β} {g : β → α} (surjf : surjective f) 104 | (rfg : right_inverse f g) : left_inverse f g := 105 | λ y => 106 | let ⟨x, hx⟩ := surjf y 107 | by rw [← hx, rfg] 108 | 109 | theorem injective_id : injective (@id α) := id 110 | 111 | theorem surjective_id : surjective (@id α) := λ a => ⟨a, rfl⟩ 112 | 113 | theorem bijective_id : bijective (@id α) := ⟨injective_id, surjective_id⟩ 114 | 115 | end Function 116 | 117 | namespace Function 118 | 119 | variable {α : Type u₁} {β : Type u₂} {φ : Type u₃} 120 | 121 | /-- Interpret a function on `α × β` as a function with two arguments. -/ 122 | @[inline] def curry : (α × β → φ) → α → β → φ := 123 | λ f a b => f (a, b) 124 | 125 | /-- Interpret a function with two arguments as a function on `α × β` -/ 126 | @[inline] def uncurry : (α → β → φ) → α × β → φ := 127 | λ f a => f a.1 a.2 128 | 129 | @[simp] theorem curry_uncurry (f : α → β → φ) : curry (uncurry f) = f := 130 | rfl 131 | 132 | @[simp] theorem uncurry_curry (f : α × β → φ) : uncurry (curry f) = f := 133 | funext (λ ⟨a, b⟩ => rfl) 134 | 135 | protected theorem left_inverse.id {g : β → α} {f : α → β} (h : left_inverse g f) : g ∘ f = id := 136 | funext h 137 | 138 | protected theorem right_inverse.id {g : β → α} {f : α → β} (h : right_inverse g f) : f ∘ g = id := 139 | funext h 140 | 141 | end Function 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![LeanInkLogo](https://user-images.githubusercontent.com/24965150/145307859-30350f23-4f7f-4aab-a1ab-34889ad44d9a.png) 2 | 3 | [![CI](https://github.com/leanprover/LeanInk/actions/workflows/build.yml/badge.svg)](https://github.com/insightmind/LeanInk/actions/workflows/build.yml) 4 | [![LƎⱯN - 4](https://img.shields.io/static/v1?label=LƎⱯN&message=4&color=black)](https://github.com/leanprover/lean4) 5 | 6 | LeanInk is a command line helper tool for [Alectryon](https://github.com/cpitclaudel/alectryon) which aims to ease the integration and support of [Lean 4](https://github.com/leanprover/lean4). 7 | Alectryon uses the information provided by LeanInk to create a static code visualization for Lean 4 code. 8 | For more information about Alectryon make sure to take a look at their repository. 9 | 10 | > The official version of Alectryon does not yet support LeanInk, as LeanInk is still in active development. Please use our [Alectryon Fork](https://github.com/insightmind/alectryon/tree/lean4) to test LeanInk. 11 | 12 | # Installation 13 | 14 | You can either build LeanInk and install it yourself as shown below, or you can use the build script to install a LeanInk release version: 15 | 16 | ```bash 17 | sh -c "$(curl https://raw.githubusercontent.com/leanprover/LeanInk/main/init.sh -sSf)" 18 | ``` 19 | 20 | ## Building from source 21 | 22 | Before you can build LeanInk from source make sure to install the latest version of [Lean 4](https://github.com/leanprover/lean4) using `elan`. 23 | This will also automatically install the [Lake](https://github.com/leanprover/lake) package manager. 24 | 25 | ```bash 26 | git clone https://github.com/leanprover/LeanInk 27 | cd LeanInk 28 | lake build 29 | ``` 30 | 31 | To install this built version it is recommended you simply add the `LeanInk/build/bin` folder to your PATH environment. 32 | 33 | # Usage 34 | 35 | Analyzing a simple lean program `Input.lean` is very straightforward. To do so you simply use the `analyze` command (shorthand `a`) and provide LeanInk the input file. 36 | 37 | ```bash 38 | leanInk analyze Input.lean 39 | # OR 40 | leanInk a Input.lean 41 | ``` 42 | 43 | The `analyze` command will generate an output file `Input.lean.leanInk` with the annotate lean program, encoded using Alectryons fragment json format. (For more information about the json format take a look at [Alectryon.lean](https://github.com/leanprover/LeanInk/blob/main/LeanInk/Annotation/Alectryon.lean)) 44 | 45 | --- 46 | 47 | If your lean program has external dependencies and uses Lake as its package manager you can use the `--lake` argument to provide the lakefile. 48 | 49 | ```bash 50 | leanInk analyze Input.lean --lake lakefile.lean 51 | ``` 52 | 53 | LeanInk will then fetch any dependencies if necessary. 54 | 55 | --- 56 | 57 | You can also analyze multiple files sequentially (concurrent analysis should be possible but is currently out of scope, feel free to contribute!): 58 | 59 | ```bash 60 | leanInk analyze Input1.lean Input2.lean 61 | ``` 62 | 63 | This will create `Input1.leanink` and `Input2.leanink` respectively. However if you want to provide a lake should be valid for both input files, as you can only provide a single lake file. 64 | 65 | --- 66 | 67 | To get the supported Lean 4 version of your instance of LeanInk you can do the following: 68 | 69 | ```bash 70 | leanInk leanVersion 71 | # OR 72 | leanInk lV 73 | ``` 74 | 75 | ## Usage in Alectryon 76 | 77 | Alectryon automatically integrates LeanInk internally to analyze a Lean 4 code file or a documentation file with Lean 4 code blocks. 78 | To embed Lean 4 in code blocks you have to use the `lean4::` directive for reStructuredText and `{lean4}` directive for myST markdown files. This is to distinguish the support of Lean 3 and Lean 4 in Alectryon. 79 | 80 | For more information about Alectryon make sure to take a look at their repository. 81 | 82 | # Development 83 | 84 | ## Updating 85 | 86 | When updating the `lean-toolchain`, please also: 87 | * update `test/dep/lean-toolchain` to match, 88 | * change the Mathlib commit in `test/dep.lakefile.lean` to a commit of Mathlib using the same toolchain, 89 | * run `lake update` in `test/dep/` to update the `lake-manifest.json`. 90 | 91 | ## Experimental Features 92 | 93 | ### Additional Type Hover Metadata 94 | The following flags are experimental and used to display additional information about a source text token in Alectryon. However this feature in Alectryon is still in active development and available here: [AlectryonFork:typeid](https://github.com/insightmind/alectryon/tree/typeid): 95 | 96 | - `--x-enable-type-info` flag enables extraction of type information 97 | - `--x-enable-docStrings` flag enables extraction of doc strings 98 | - `--x-enable-semantic-token` flag enables extraction of semantic toke types for semantic syntax highlighting support 99 | 100 | ## Running Tests 101 | There are some aspects you might want to take note of when attempting to develop a feature or fix a bug in LeanInk. 102 | 103 | LeanInk uses simple diffing tests to make sure the core functionality works as expected. These tests are located in the `./test` folder. 104 | 105 | You can run these tests using `lake script run tests`. This will run LeanInk for every `.lean`, that's not a `lakefile` or part of an `lean_package`. It will compare the output of LeanInk to the expected output within the `.lean.leanInk.expected` file. 106 | 107 | To capture a new expected output file you can either run `lake script run capture` to capture the output for all files or use leanInk itself to generate an output for a single file and rename it afterwards. Be sure to carefully examine the git diff before committing the new expected baselines. 108 | 109 | # Contributing 110 | 111 | LeanInk enforces the same [Contribution Guidelines](https://github.com/leanprover/lean4/blob/master/CONTRIBUTING.md) as Lean 4. Before contributing, make sure to read it. 112 | 113 | We also highly encourage you to sign your commits. 114 | -------------------------------------------------------------------------------- /test/bugs/GH_15.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": {"type": "Nat → Nat", "name": "example", "_type": "typeinfo"}, 3 | "semanticType": "Keyword", 4 | "raw": "example", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " : ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": {"type": "Type", "name": "Nat", "_type": "typeinfo"}, 15 | "semanticType": null, 16 | "raw": "Nat", 17 | "link": null, 18 | "docstring": 19 | "The type of natural numbers, starting at zero. It is defined as an\ninductive type freely generated by \"zero is a natural number\" and\n\"the successor of a natural number is a natural number\".\n\nYou can prove a theorem `P n` about `n : Nat` by `induction n`, which will\nexpect a proof of the theorem for `P 0`, and a proof of `P (succ i)` assuming\na proof of `P i`. The same method also works to define functions by recursion\non natural numbers: induction and recursion are two expressions of the same\noperation from Lean's point of view.\n\n```\nopen Nat\nexample (n : Nat) : n < succ n := by\n induction n with\n | zero =>\n show 0 < 1\n decide\n | succ i ih => -- ih : i < succ i\n show succ i < succ (succ i)\n exact Nat.succ_lt_succ ih\n```\n\nThis type is special-cased by both the kernel and the compiler:\n* The type of expressions contains \"`Nat` literals\" as a primitive constructor,\n and the kernel knows how to reduce zero/succ expressions to nat literals.\n* If implemented naively, this type would represent a numeral `n` in unary as a\n linked list with `n` links, which is horribly inefficient. Instead, the\n runtime itself has a special representation for `Nat` which stores numbers up\n to 2^63 directly and larger numbers use an arbitrary precision \"bignum\"\n library (usually [GMP](https://gmplib.org/)).\n", 20 | "_type": "token"}, 21 | {"typeinfo": null, 22 | "semanticType": null, 23 | "raw": " → ", 24 | "link": null, 25 | "docstring": null, 26 | "_type": "token"}, 27 | {"typeinfo": {"type": "Type", "name": "Nat", "_type": "typeinfo"}, 28 | "semanticType": null, 29 | "raw": "Nat", 30 | "link": null, 31 | "docstring": 32 | "The type of natural numbers, starting at zero. It is defined as an\ninductive type freely generated by \"zero is a natural number\" and\n\"the successor of a natural number is a natural number\".\n\nYou can prove a theorem `P n` about `n : Nat` by `induction n`, which will\nexpect a proof of the theorem for `P 0`, and a proof of `P (succ i)` assuming\na proof of `P i`. The same method also works to define functions by recursion\non natural numbers: induction and recursion are two expressions of the same\noperation from Lean's point of view.\n\n```\nopen Nat\nexample (n : Nat) : n < succ n := by\n induction n with\n | zero =>\n show 0 < 1\n decide\n | succ i ih => -- ih : i < succ i\n show succ i < succ (succ i)\n exact Nat.succ_lt_succ ih\n```\n\nThis type is special-cased by both the kernel and the compiler:\n* The type of expressions contains \"`Nat` literals\" as a primitive constructor,\n and the kernel knows how to reduce zero/succ expressions to nat literals.\n* If implemented naively, this type would represent a numeral `n` in unary as a\n linked list with `n` links, which is horribly inefficient. Instead, the\n runtime itself has a special representation for `Nat` which stores numbers up\n to 2^63 directly and larger numbers use an arbitrary precision \"bignum\"\n library (usually [GMP](https://gmplib.org/)).\n", 33 | "_type": "token"}, 34 | {"typeinfo": null, 35 | "semanticType": null, 36 | "raw": "\n | ", 37 | "link": null, 38 | "docstring": null, 39 | "_type": "token"}, 40 | {"typeinfo": {"type": "Nat", "name": "0", "_type": "typeinfo"}, 41 | "semanticType": null, 42 | "raw": "0", 43 | "link": null, 44 | "docstring": null, 45 | "_type": "token"}, 46 | {"typeinfo": null, 47 | "semanticType": null, 48 | "raw": " => ", 49 | "link": null, 50 | "docstring": null, 51 | "_type": "token"}, 52 | {"typeinfo": {"type": "Nat", "name": "0", "_type": "typeinfo"}, 53 | "semanticType": null, 54 | "raw": "0", 55 | "link": null, 56 | "docstring": null, 57 | "_type": "token"}, 58 | {"typeinfo": null, 59 | "semanticType": null, 60 | "raw": "\n | ", 61 | "link": null, 62 | "docstring": null, 63 | "_type": "token"}, 64 | {"typeinfo": {"type": "Nat", "name": "n", "_type": "typeinfo"}, 65 | "semanticType": "Name.Variable", 66 | "raw": "n", 67 | "link": null, 68 | "docstring": null, 69 | "_type": "token"}, 70 | {"typeinfo": null, 71 | "semanticType": null, 72 | "raw": " + 1 => ", 73 | "link": null, 74 | "docstring": null, 75 | "_type": "token"}], 76 | "_type": "text"}, 77 | {"messages": [], 78 | "goals": 79 | [{"name": "", 80 | "hypotheses": [], 81 | "conclusion": "Goals accomplished! 🐙", 82 | "_type": "goal"}], 83 | "contents": 84 | [{"typeinfo": null, 85 | "semanticType": "Keyword", 86 | "raw": "by", 87 | "link": null, 88 | "docstring": 89 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 90 | "_type": "token"}], 91 | "_type": "sentence"}, 92 | {"messages": [], 93 | "goals": 94 | [{"name": "", 95 | "hypotheses": 96 | [{"type": "Nat", "names": ["n"], "body": "", "_type": "hypothesis"}], 97 | "conclusion": "Nat", 98 | "_type": "goal"}], 99 | "contents": 100 | [{"typeinfo": null, 101 | "semanticType": null, 102 | "raw": " ", 103 | "link": null, 104 | "docstring": 105 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 106 | "_type": "token"}], 107 | "_type": "sentence"}, 108 | {"messages": [], 109 | "goals": 110 | [{"name": "", 111 | "hypotheses": [], 112 | "conclusion": "Goals accomplished! 🐙", 113 | "_type": "goal"}], 114 | "contents": 115 | [{"typeinfo": null, 116 | "semanticType": "Keyword", 117 | "raw": "exact", 118 | "link": null, 119 | "docstring": 120 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 121 | "_type": "token"}, 122 | {"typeinfo": null, 123 | "semanticType": null, 124 | "raw": " ", 125 | "link": null, 126 | "docstring": 127 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 128 | "_type": "token"}, 129 | {"typeinfo": {"type": "Nat", "name": "n", "_type": "typeinfo"}, 130 | "semanticType": "Name.Variable", 131 | "raw": "n", 132 | "link": null, 133 | "docstring": null, 134 | "_type": "token"}], 135 | "_type": "sentence"}, 136 | {"contents": [], "_type": "text"}] -------------------------------------------------------------------------------- /LeanInk/Annotation/Alectryon.lean: -------------------------------------------------------------------------------- 1 | import LeanInk.Annotation.Basic 2 | import LeanInk.Logger 3 | import LeanInk.Analysis.DataTypes 4 | import LeanInk.Annotation.Util 5 | import LeanInk.FileHelper 6 | 7 | import Lean.Data.Json 8 | import Lean.Data.Json.FromToJson 9 | import Lean.Data.Lsp 10 | 11 | namespace LeanInk.Annotation.Alectryon 12 | 13 | open Lean 14 | open LeanInk.Analysis 15 | 16 | structure TypeInfo where 17 | _type : String := "typeinfo" 18 | name : String 19 | type : String 20 | deriving ToJson 21 | 22 | structure Token where 23 | _type : String := "token" 24 | raw : String 25 | typeinfo : Option TypeInfo := Option.none 26 | link : Option String := Option.none 27 | docstring : Option String := Option.none 28 | semanticType : Option String := Option.none 29 | deriving ToJson 30 | 31 | /-- 32 | Support type for experimental --experimental-type-tokens feature 33 | If flag not set please only use the string case. 34 | -/ 35 | inductive Contents where 36 | | string (value : String) 37 | | experimentalTokens (value : Array Token) 38 | 39 | instance : ToJson Contents where 40 | toJson 41 | | Contents.string v => toJson v 42 | | Contents.experimentalTokens v => toJson v 43 | 44 | structure Hypothesis where 45 | _type : String := "hypothesis" 46 | names : List String 47 | body : String 48 | type : String 49 | deriving ToJson 50 | 51 | structure Goal where 52 | _type : String := "goal" 53 | name : String 54 | conclusion : String 55 | hypotheses : Array Hypothesis 56 | deriving ToJson 57 | 58 | structure Message where 59 | _type : String := "message" 60 | contents : String 61 | deriving ToJson 62 | 63 | structure Sentence where 64 | _type : String := "sentence" 65 | contents : Contents 66 | messages : Array Message 67 | goals : Array Goal 68 | deriving ToJson 69 | 70 | structure Text where 71 | _type : String := "text" 72 | contents : Contents 73 | deriving ToJson 74 | 75 | /-- 76 | We need a custom ToJson implementation for Alectryons fragments. 77 | 78 | For example we have following fragment: 79 | ``` 80 | Fragment.text { contents := "Test" } 81 | ``` 82 | 83 | We want to serialize this to: 84 | ``` 85 | [{"contents": "Test", "_type": "text"}] 86 | ``` 87 | 88 | instead of: 89 | ``` 90 | [{"text": {"contents": "Test", "_type": "text"}}] 91 | ``` 92 | 93 | This is because of the way Alectryon decodes the json files. It uses the _type field to 94 | determine the namedTuple type with Alectryon. 95 | -/ 96 | inductive Fragment where 97 | | text (value : Text) 98 | | sentence (value : Sentence) 99 | 100 | instance : ToJson Fragment where 101 | toJson 102 | | Fragment.text v => toJson v 103 | | Fragment.sentence v => toJson v 104 | 105 | /- 106 | Token Generation 107 | -/ 108 | 109 | def genTypeInfo? (getContents : String.Pos -> String.Pos -> Option String) (token : Analysis.TypeTokenInfo) : AnalysisM (Option TypeInfo) := do 110 | match token.type with 111 | | some type => do 112 | let headPos := Positional.headPos token 113 | let tailPos := Positional.tailPos token 114 | match getContents headPos tailPos with 115 | | none => pure none 116 | | "" => pure none 117 | | some x => return some { name := x, type := type } 118 | | none => pure none 119 | 120 | def genSemanticTokenValue : Option SemanticTokenInfo -> AnalysisM (Option String) 121 | | none => pure none 122 | | some info => 123 | match info.semanticType with 124 | | SemanticTokenType.property => pure (some "Name.Attribute") 125 | | SemanticTokenType.keyword => pure (some "Keyword") 126 | | SemanticTokenType.variable => pure (some "Name.Variable") 127 | | _ => pure none 128 | 129 | def genToken (token : Compound Analysis.Token) (contents : Option String) (getContents : String.Pos -> String.Pos -> Option String) : AnalysisM (Option Token) := do 130 | match contents with 131 | | none => return none 132 | | "" => return none 133 | | some contents => do 134 | let typeTokens := token.getFragments.filterMap (λ x => x.toTypeTokenInfo?) 135 | let semanticTokens := token.getFragments.filterMap (λ x => x.toSemanticTokenInfo?) 136 | let semanticToken := Positional.smallest? semanticTokens 137 | let semanticTokenType ← genSemanticTokenValue semanticToken 138 | match (Positional.smallest? typeTokens) with 139 | | none => do 140 | return some { raw := contents, semanticType := semanticTokenType } 141 | | some token => do 142 | return some { raw := contents, typeinfo := ← genTypeInfo? getContents token, link := none, docstring := token.docString, semanticType := semanticTokenType } 143 | 144 | def extractContents (offset : String.Pos) (contents : String) (head tail: String.Pos) : Option String := 145 | if head >= tail then 146 | none 147 | else 148 | contents.extract (head - offset) (tail - offset) 149 | 150 | def minPos (x y : String.Pos) := if x < y then x else y 151 | def maxPos (x y : String.Pos) := if x > y then x else y 152 | 153 | partial def genTokens (contents : String) (head : String.Pos) (offset : String.Pos) (compounds : List (Compound Analysis.Token)) : AnalysisM (List Token) := do 154 | let textTail := ⟨contents.utf8ByteSize⟩ + offset 155 | let mut head : String.Pos := head 156 | let mut tokens : List Token := [] 157 | for x in compounds do 158 | let extract := extractContents offset contents 159 | let tail := x.tailPos.getD textTail 160 | if x.headPos <= head then 161 | let text := extract head tail 162 | head := tail 163 | logInfo s!"Text-B1: {text}" 164 | match (← genToken x text extract) with 165 | | none => logInfo s!"Empty 1 {text} {x.headPos} {tail}" 166 | | some fragment => tokens := fragment::tokens 167 | else 168 | let text := extract head x.headPos 169 | head := x.headPos 170 | logInfo s!"Text-B2: {text}" 171 | match text with 172 | | none => logInfo s!"Empty 1 {text} {x.headPos} {tail}" 173 | | some text => tokens := { raw := text }::tokens 174 | match extractContents offset contents head (⟨contents.utf8ByteSize⟩ + offset) with 175 | | none => return tokens.reverse 176 | | some x => return ({ raw := x }::tokens).reverse 177 | 178 | /- 179 | Fragment Generation 180 | -/ 181 | 182 | def genHypothesis (hypothesis : Analysis.Hypothesis) : Hypothesis := { 183 | names := hypothesis.names 184 | body := hypothesis.body 185 | type := hypothesis.type 186 | } 187 | 188 | def genGoal (goal : Analysis.Goal) : Goal := { 189 | name := goal.name 190 | conclusion := goal.conclusion 191 | hypotheses := (goal.hypotheses.map genHypothesis).toArray 192 | } 193 | 194 | def genGoals (beforeNode: Bool) (tactic : Analysis.Tactic) : List Goal := 195 | if beforeNode then 196 | tactic.goalsBefore.map (λ g => genGoal g) 197 | else 198 | tactic.goalsAfter.map (λ g => genGoal g) 199 | 200 | def genMessages (message : Analysis.Message) : Message := { contents := message.msg } 201 | 202 | def isComment (contents : String) : Bool := 203 | let contents := contents.trim 204 | contents.startsWith "--" || contents.startsWith "/-" 205 | 206 | def genFragment (annotation : Annotation) (globalTailPos : String.Pos) (contents : String) : AnalysisM Alectryon.Fragment := do 207 | let config ← read 208 | /- When contents is a comment, the goals are duplicates -/ 209 | if (isComment contents) then 210 | return Fragment.text { contents := Contents.string contents } 211 | if annotation.sentence.fragments.isEmpty then 212 | if config.experimentalTypeInfo ∨ config.experimentalDocString then 213 | let headPos := annotation.sentence.headPos 214 | let tokens ← genTokens contents headPos headPos annotation.tokens 215 | return Fragment.text { contents := Contents.experimentalTokens tokens.toArray } 216 | else 217 | return Fragment.text { contents := Contents.string contents } 218 | else 219 | let tactics : List Analysis.Tactic := annotation.sentence.getFragments.filterMap (λ f => f.asTactic?) 220 | let messages : List Analysis.Message := annotation.sentence.getFragments.filterMap (λ f => f.asMessage?) 221 | let mut goals : List Goal := [] 222 | if let (some tactic) := Positional.smallest? tactics then 223 | let useBefore : Bool := tactic.tailPos > globalTailPos 224 | goals := genGoals useBefore tactic 225 | let mut fragmentContents : Contents := Contents.string contents 226 | if config.experimentalTypeInfo ∨ config.experimentalDocString then 227 | let headPos := annotation.sentence.headPos 228 | let tokens ← genTokens contents headPos headPos annotation.tokens 229 | fragmentContents := Contents.experimentalTokens tokens.toArray 230 | return Fragment.sentence { 231 | contents := fragmentContents 232 | goals := goals.toArray 233 | messages := (messages.map genMessages).toArray 234 | } 235 | 236 | /- 237 | Expects a list of sorted CompoundFragments (sorted by headPos). 238 | Generates AlectryonFragments for the given CompoundFragments and input file content. 239 | -/ 240 | def annotateFileWithCompounds (l : List Alectryon.Fragment) (contents : String) : List Annotation -> AnalysisM (List Fragment) 241 | | [] => pure l 242 | | x::[] => do 243 | let fragment ← genFragment x ⟨contents.utf8ByteSize⟩ (contents.extract x.sentence.headPos ⟨contents.utf8ByteSize⟩) 244 | return l.append [fragment] 245 | | x::y::ys => do 246 | let fragment ← genFragment x y.sentence.headPos (contents.extract x.sentence.headPos (y.sentence.headPos)) 247 | return (← annotateFileWithCompounds (l.append [fragment]) contents (y::ys)) 248 | 249 | def genOutput (annotation : List Annotation) : AnalysisM UInt32 := do 250 | let config := (← read) 251 | let fragments ← annotateFileWithCompounds [] config.inputFileContents annotation 252 | let rawContents ← generateOutput fragments.toArray 253 | createOutputFile (← IO.currentDir) config.inputFileName rawContents 254 | return 0 255 | -------------------------------------------------------------------------------- /test/theorem_proving/004.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "theorem", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": 15 | {"type": "∀ (p q : Prop), p → q → p ∧ q ∧ p", 16 | "name": "test", 17 | "_type": "typeinfo"}, 18 | "semanticType": "Name.Variable", 19 | "raw": "test", 20 | "link": null, 21 | "docstring": null, 22 | "_type": "token"}, 23 | {"typeinfo": null, 24 | "semanticType": null, 25 | "raw": " (", 26 | "link": null, 27 | "docstring": null, 28 | "_type": "token"}, 29 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 30 | "semanticType": "Name.Variable", 31 | "raw": "p", 32 | "link": null, 33 | "docstring": null, 34 | "_type": "token"}, 35 | {"typeinfo": null, 36 | "semanticType": null, 37 | "raw": " ", 38 | "link": null, 39 | "docstring": null, 40 | "_type": "token"}, 41 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 42 | "semanticType": "Name.Variable", 43 | "raw": "q", 44 | "link": null, 45 | "docstring": null, 46 | "_type": "token"}, 47 | {"typeinfo": null, 48 | "semanticType": null, 49 | "raw": " : ", 50 | "link": null, 51 | "docstring": null, 52 | "_type": "token"}, 53 | {"typeinfo": {"type": "Type", "name": "Prop", "_type": "typeinfo"}, 54 | "semanticType": null, 55 | "raw": "Prop", 56 | "link": null, 57 | "docstring": null, 58 | "_type": "token"}, 59 | {"typeinfo": null, 60 | "semanticType": null, 61 | "raw": ") (", 62 | "link": null, 63 | "docstring": null, 64 | "_type": "token"}, 65 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 66 | "semanticType": "Name.Variable", 67 | "raw": "hp", 68 | "link": null, 69 | "docstring": null, 70 | "_type": "token"}, 71 | {"typeinfo": null, 72 | "semanticType": null, 73 | "raw": " : ", 74 | "link": null, 75 | "docstring": null, 76 | "_type": "token"}, 77 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 78 | "semanticType": "Name.Variable", 79 | "raw": "p", 80 | "link": null, 81 | "docstring": null, 82 | "_type": "token"}, 83 | {"typeinfo": null, 84 | "semanticType": null, 85 | "raw": ") (", 86 | "link": null, 87 | "docstring": null, 88 | "_type": "token"}, 89 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 90 | "semanticType": "Name.Variable", 91 | "raw": "hq", 92 | "link": null, 93 | "docstring": null, 94 | "_type": "token"}, 95 | {"typeinfo": null, 96 | "semanticType": null, 97 | "raw": " : ", 98 | "link": null, 99 | "docstring": null, 100 | "_type": "token"}, 101 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 102 | "semanticType": "Name.Variable", 103 | "raw": "q", 104 | "link": null, 105 | "docstring": null, 106 | "_type": "token"}, 107 | {"typeinfo": null, 108 | "semanticType": null, 109 | "raw": ") : ", 110 | "link": null, 111 | "docstring": null, 112 | "_type": "token"}, 113 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 114 | "semanticType": "Name.Variable", 115 | "raw": "p", 116 | "link": null, 117 | "docstring": null, 118 | "_type": "token"}, 119 | {"typeinfo": null, 120 | "semanticType": null, 121 | "raw": " ∧ ", 122 | "link": null, 123 | "docstring": null, 124 | "_type": "token"}, 125 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 126 | "semanticType": "Name.Variable", 127 | "raw": "q", 128 | "link": null, 129 | "docstring": null, 130 | "_type": "token"}, 131 | {"typeinfo": null, 132 | "semanticType": null, 133 | "raw": " ∧ ", 134 | "link": null, 135 | "docstring": null, 136 | "_type": "token"}, 137 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 138 | "semanticType": "Name.Variable", 139 | "raw": "p", 140 | "link": null, 141 | "docstring": null, 142 | "_type": "token"}, 143 | {"typeinfo": null, 144 | "semanticType": null, 145 | "raw": " := ", 146 | "link": null, 147 | "docstring": null, 148 | "_type": "token"}], 149 | "_type": "text"}, 150 | {"messages": [], 151 | "goals": 152 | [{"name": "", 153 | "hypotheses": [], 154 | "conclusion": "Goals accomplished! 🐙", 155 | "_type": "goal"}], 156 | "contents": 157 | [{"typeinfo": null, 158 | "semanticType": "Keyword", 159 | "raw": "by", 160 | "link": null, 161 | "docstring": 162 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 163 | "_type": "token"}], 164 | "_type": "sentence"}, 165 | {"messages": [], 166 | "goals": 167 | [{"name": "", 168 | "hypotheses": 169 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 170 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 171 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 172 | "conclusion": "p ∧ q ∧ p", 173 | "_type": "goal"}], 174 | "contents": 175 | [{"typeinfo": null, 176 | "semanticType": null, 177 | "raw": "\n ", 178 | "link": null, 179 | "docstring": 180 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 181 | "_type": "token"}], 182 | "_type": "sentence"}, 183 | {"messages": [], 184 | "goals": 185 | [{"name": "", 186 | "hypotheses": 187 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 188 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 189 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 190 | "conclusion": "q ∧ p", 191 | "_type": "goal"}], 192 | "contents": 193 | [{"typeinfo": null, 194 | "semanticType": "Keyword", 195 | "raw": "apply", 196 | "link": null, 197 | "docstring": 198 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 199 | "_type": "token"}, 200 | {"typeinfo": null, 201 | "semanticType": null, 202 | "raw": " ", 203 | "link": null, 204 | "docstring": 205 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 206 | "_type": "token"}, 207 | {"typeinfo": 208 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 209 | "name": "And.intro", 210 | "_type": "typeinfo"}, 211 | "semanticType": null, 212 | "raw": "And.intro", 213 | "link": null, 214 | "docstring": 215 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 216 | "_type": "token"}, 217 | {"typeinfo": null, 218 | "semanticType": null, 219 | "raw": " ", 220 | "link": null, 221 | "docstring": 222 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 223 | "_type": "token"}, 224 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 225 | "semanticType": "Name.Variable", 226 | "raw": "hp", 227 | "link": null, 228 | "docstring": null, 229 | "_type": "token"}], 230 | "_type": "sentence"}, 231 | {"messages": [], 232 | "goals": 233 | [{"name": "", 234 | "hypotheses": 235 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 236 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 237 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 238 | "conclusion": "p ∧ q ∧ p", 239 | "_type": "goal"}], 240 | "contents": 241 | [{"typeinfo": null, 242 | "semanticType": null, 243 | "raw": "\n ", 244 | "link": null, 245 | "docstring": 246 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 247 | "_type": "token"}], 248 | "_type": "sentence"}, 249 | {"messages": [], 250 | "goals": 251 | [{"name": "", 252 | "hypotheses": [], 253 | "conclusion": "Goals accomplished! 🐙", 254 | "_type": "goal"}], 255 | "contents": 256 | [{"typeinfo": null, 257 | "semanticType": "Keyword", 258 | "raw": "exact", 259 | "link": null, 260 | "docstring": 261 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 262 | "_type": "token"}, 263 | {"typeinfo": null, 264 | "semanticType": null, 265 | "raw": " ", 266 | "link": null, 267 | "docstring": 268 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 269 | "_type": "token"}, 270 | {"typeinfo": 271 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 272 | "name": "And.intro", 273 | "_type": "typeinfo"}, 274 | "semanticType": null, 275 | "raw": "And.intro", 276 | "link": null, 277 | "docstring": 278 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 279 | "_type": "token"}, 280 | {"typeinfo": null, 281 | "semanticType": null, 282 | "raw": " ", 283 | "link": null, 284 | "docstring": 285 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 286 | "_type": "token"}, 287 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 288 | "semanticType": "Name.Variable", 289 | "raw": "hq", 290 | "link": null, 291 | "docstring": null, 292 | "_type": "token"}, 293 | {"typeinfo": null, 294 | "semanticType": null, 295 | "raw": " ", 296 | "link": null, 297 | "docstring": 298 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 299 | "_type": "token"}, 300 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 301 | "semanticType": "Name.Variable", 302 | "raw": "hp", 303 | "link": null, 304 | "docstring": null, 305 | "_type": "token"}], 306 | "_type": "sentence"}, 307 | {"contents": [], "_type": "text"}] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/theorem_proving/INFO: -------------------------------------------------------------------------------- 1 | The tests files in this folders are example scripts of the "Theorem Proving in Lean 4" (https://github.com/leanprover/theorem_proving_in_lean4) 2 | 3 | License: 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | END OF TERMS AND CONDITIONS 181 | 182 | APPENDIX: How to apply the Apache License to your work. 183 | 184 | To apply the Apache License to your work, attach the following 185 | boilerplate notice, with the fields enclosed by brackets "{}" 186 | replaced with your own identifying information. (Don't include 187 | the brackets!) The text should be enclosed in the appropriate 188 | comment syntax for the file format. We also recommend that a 189 | file or class name and description of purpose be included on the 190 | same "printed page" as the copyright notice for easier 191 | identification within third-party archives. 192 | 193 | Copyright {yyyy} {name of copyright owner} 194 | 195 | Licensed under the Apache License, Version 2.0 (the "License"); 196 | you may not use this file except in compliance with the License. 197 | You may obtain a copy of the License at 198 | 199 | http://www.apache.org/licenses/LICENSE-2.0 200 | 201 | Unless required by applicable law or agreed to in writing, software 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 204 | See the License for the specific language governing permissions and 205 | limitations under the License. -------------------------------------------------------------------------------- /test/theorem_proving/005.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "theorem", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": 15 | {"type": "∀ (p q : Prop), p → q → p ∧ q ∧ p", 16 | "name": "test", 17 | "_type": "typeinfo"}, 18 | "semanticType": "Name.Variable", 19 | "raw": "test", 20 | "link": null, 21 | "docstring": null, 22 | "_type": "token"}, 23 | {"typeinfo": null, 24 | "semanticType": null, 25 | "raw": " (", 26 | "link": null, 27 | "docstring": null, 28 | "_type": "token"}, 29 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 30 | "semanticType": "Name.Variable", 31 | "raw": "p", 32 | "link": null, 33 | "docstring": null, 34 | "_type": "token"}, 35 | {"typeinfo": null, 36 | "semanticType": null, 37 | "raw": " ", 38 | "link": null, 39 | "docstring": null, 40 | "_type": "token"}, 41 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 42 | "semanticType": "Name.Variable", 43 | "raw": "q", 44 | "link": null, 45 | "docstring": null, 46 | "_type": "token"}, 47 | {"typeinfo": null, 48 | "semanticType": null, 49 | "raw": " : ", 50 | "link": null, 51 | "docstring": null, 52 | "_type": "token"}, 53 | {"typeinfo": {"type": "Type", "name": "Prop", "_type": "typeinfo"}, 54 | "semanticType": null, 55 | "raw": "Prop", 56 | "link": null, 57 | "docstring": null, 58 | "_type": "token"}, 59 | {"typeinfo": null, 60 | "semanticType": null, 61 | "raw": ") (", 62 | "link": null, 63 | "docstring": null, 64 | "_type": "token"}, 65 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 66 | "semanticType": "Name.Variable", 67 | "raw": "hp", 68 | "link": null, 69 | "docstring": null, 70 | "_type": "token"}, 71 | {"typeinfo": null, 72 | "semanticType": null, 73 | "raw": " : ", 74 | "link": null, 75 | "docstring": null, 76 | "_type": "token"}, 77 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 78 | "semanticType": "Name.Variable", 79 | "raw": "p", 80 | "link": null, 81 | "docstring": null, 82 | "_type": "token"}, 83 | {"typeinfo": null, 84 | "semanticType": null, 85 | "raw": ") (", 86 | "link": null, 87 | "docstring": null, 88 | "_type": "token"}, 89 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 90 | "semanticType": "Name.Variable", 91 | "raw": "hq", 92 | "link": null, 93 | "docstring": null, 94 | "_type": "token"}, 95 | {"typeinfo": null, 96 | "semanticType": null, 97 | "raw": " : ", 98 | "link": null, 99 | "docstring": null, 100 | "_type": "token"}, 101 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 102 | "semanticType": "Name.Variable", 103 | "raw": "q", 104 | "link": null, 105 | "docstring": null, 106 | "_type": "token"}, 107 | {"typeinfo": null, 108 | "semanticType": null, 109 | "raw": ") : ", 110 | "link": null, 111 | "docstring": null, 112 | "_type": "token"}, 113 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 114 | "semanticType": "Name.Variable", 115 | "raw": "p", 116 | "link": null, 117 | "docstring": null, 118 | "_type": "token"}, 119 | {"typeinfo": null, 120 | "semanticType": null, 121 | "raw": " ∧ ", 122 | "link": null, 123 | "docstring": null, 124 | "_type": "token"}, 125 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 126 | "semanticType": "Name.Variable", 127 | "raw": "q", 128 | "link": null, 129 | "docstring": null, 130 | "_type": "token"}, 131 | {"typeinfo": null, 132 | "semanticType": null, 133 | "raw": " ∧ ", 134 | "link": null, 135 | "docstring": null, 136 | "_type": "token"}, 137 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 138 | "semanticType": "Name.Variable", 139 | "raw": "p", 140 | "link": null, 141 | "docstring": null, 142 | "_type": "token"}, 143 | {"typeinfo": null, 144 | "semanticType": null, 145 | "raw": " := ", 146 | "link": null, 147 | "docstring": null, 148 | "_type": "token"}], 149 | "_type": "text"}, 150 | {"messages": [], 151 | "goals": 152 | [{"name": "", 153 | "hypotheses": [], 154 | "conclusion": "Goals accomplished! 🐙", 155 | "_type": "goal"}], 156 | "contents": 157 | [{"typeinfo": null, 158 | "semanticType": "Keyword", 159 | "raw": "by", 160 | "link": null, 161 | "docstring": 162 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 163 | "_type": "token"}], 164 | "_type": "sentence"}, 165 | {"messages": [], 166 | "goals": 167 | [{"name": "", 168 | "hypotheses": 169 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 170 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 171 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 172 | "conclusion": "p ∧ q ∧ p", 173 | "_type": "goal"}], 174 | "contents": 175 | [{"typeinfo": null, 176 | "semanticType": null, 177 | "raw": "\n ", 178 | "link": null, 179 | "docstring": 180 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 181 | "_type": "token"}], 182 | "_type": "sentence"}, 183 | {"messages": [], 184 | "goals": 185 | [{"name": "", 186 | "hypotheses": 187 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 188 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 189 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 190 | "conclusion": "q ∧ p", 191 | "_type": "goal"}], 192 | "contents": 193 | [{"typeinfo": null, 194 | "semanticType": "Keyword", 195 | "raw": "apply", 196 | "link": null, 197 | "docstring": 198 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 199 | "_type": "token"}, 200 | {"typeinfo": null, 201 | "semanticType": null, 202 | "raw": " ", 203 | "link": null, 204 | "docstring": 205 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 206 | "_type": "token"}, 207 | {"typeinfo": 208 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 209 | "name": "And.intro", 210 | "_type": "typeinfo"}, 211 | "semanticType": null, 212 | "raw": "And.intro", 213 | "link": null, 214 | "docstring": 215 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 216 | "_type": "token"}, 217 | {"typeinfo": null, 218 | "semanticType": null, 219 | "raw": " ", 220 | "link": null, 221 | "docstring": 222 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 223 | "_type": "token"}, 224 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 225 | "semanticType": "Name.Variable", 226 | "raw": "hp", 227 | "link": null, 228 | "docstring": null, 229 | "_type": "token"}], 230 | "_type": "sentence"}, 231 | {"messages": [], 232 | "goals": 233 | [{"name": "", 234 | "hypotheses": 235 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 236 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 237 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 238 | "conclusion": "q ∧ p", 239 | "_type": "goal"}], 240 | "contents": 241 | [{"typeinfo": null, 242 | "semanticType": null, 243 | "raw": ";", 244 | "link": null, 245 | "docstring": 246 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 247 | "_type": "token"}], 248 | "_type": "sentence"}, 249 | {"messages": [], 250 | "goals": 251 | [{"name": "", 252 | "hypotheses": 253 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 254 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 255 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 256 | "conclusion": "p ∧ q ∧ p", 257 | "_type": "goal"}], 258 | "contents": 259 | [{"typeinfo": null, 260 | "semanticType": null, 261 | "raw": " ", 262 | "link": null, 263 | "docstring": 264 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 265 | "_type": "token"}], 266 | "_type": "sentence"}, 267 | {"messages": [], 268 | "goals": 269 | [{"name": "", 270 | "hypotheses": [], 271 | "conclusion": "Goals accomplished! 🐙", 272 | "_type": "goal"}], 273 | "contents": 274 | [{"typeinfo": null, 275 | "semanticType": "Keyword", 276 | "raw": "exact", 277 | "link": null, 278 | "docstring": 279 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 280 | "_type": "token"}, 281 | {"typeinfo": null, 282 | "semanticType": null, 283 | "raw": " ", 284 | "link": null, 285 | "docstring": 286 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 287 | "_type": "token"}, 288 | {"typeinfo": 289 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 290 | "name": "And.intro", 291 | "_type": "typeinfo"}, 292 | "semanticType": null, 293 | "raw": "And.intro", 294 | "link": null, 295 | "docstring": 296 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 297 | "_type": "token"}, 298 | {"typeinfo": null, 299 | "semanticType": null, 300 | "raw": " ", 301 | "link": null, 302 | "docstring": 303 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 304 | "_type": "token"}, 305 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 306 | "semanticType": "Name.Variable", 307 | "raw": "hq", 308 | "link": null, 309 | "docstring": null, 310 | "_type": "token"}, 311 | {"typeinfo": null, 312 | "semanticType": null, 313 | "raw": " ", 314 | "link": null, 315 | "docstring": 316 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 317 | "_type": "token"}, 318 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 319 | "semanticType": "Name.Variable", 320 | "raw": "hp", 321 | "link": null, 322 | "docstring": null, 323 | "_type": "token"}], 324 | "_type": "sentence"}, 325 | {"contents": 326 | [{"typeinfo": null, 327 | "semanticType": null, 328 | "raw": "\n\n", 329 | "link": null, 330 | "docstring": null, 331 | "_type": "token"}], 332 | "_type": "text"}, 333 | {"messages": [{"contents": "And (a b : Prop) : Prop", "_type": "message"}], 334 | "goals": [], 335 | "contents": 336 | [{"typeinfo": null, 337 | "semanticType": "Keyword", 338 | "raw": "#check", 339 | "link": null, 340 | "docstring": null, 341 | "_type": "token"}], 342 | "_type": "sentence"}, 343 | {"contents": 344 | [{"typeinfo": null, 345 | "semanticType": null, 346 | "raw": " ", 347 | "link": null, 348 | "docstring": null, 349 | "_type": "token"}, 350 | {"typeinfo": 351 | {"type": "Prop → Prop → Prop", "name": "And", "_type": "typeinfo"}, 352 | "semanticType": null, 353 | "raw": "And", 354 | "link": null, 355 | "docstring": 356 | "`And a b`, or `a ∧ b`, is the conjunction of propositions. It can be\nconstructed and destructed like a pair: if `ha : a` and `hb : b` then\n`⟨ha, hb⟩ : a ∧ b`, and if `h : a ∧ b` then `h.left : a` and `h.right : b`.\n", 357 | "_type": "token"}], 358 | "_type": "text"}] -------------------------------------------------------------------------------- /test/playground/playground_2.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": "/-| \nHello World!\n-/\n", "_type": "text"}, 2 | {"messages": [{"contents": "Hello World!", "_type": "message"}], 3 | "goals": [], 4 | "contents": 5 | [{"typeinfo": null, 6 | "semanticType": "Keyword", 7 | "raw": "#print", 8 | "link": null, 9 | "docstring": null, 10 | "_type": "token"}], 11 | "_type": "sentence"}, 12 | {"contents": 13 | [{"typeinfo": null, 14 | "semanticType": null, 15 | "raw": " \"Hello World!\"\n\n/-|\nA literate comment!\n-/\n\n", 16 | "link": null, 17 | "docstring": null, 18 | "_type": "token"}, 19 | {"typeinfo": null, 20 | "semanticType": "Keyword", 21 | "raw": "theorem", 22 | "link": null, 23 | "docstring": null, 24 | "_type": "token"}, 25 | {"typeinfo": null, 26 | "semanticType": null, 27 | "raw": " ", 28 | "link": null, 29 | "docstring": null, 30 | "_type": "token"}], 31 | "_type": "text"}, 32 | {"messages": 33 | [{"contents": "Warning: declaration uses 'sorry'", "_type": "message"}], 34 | "goals": [], 35 | "contents": 36 | [{"typeinfo": 37 | {"type": "∀ (p q : Prop), p → q → p ∧ q ∧ p", 38 | "name": "exampleTheorem", 39 | "_type": "typeinfo"}, 40 | "semanticType": "Name.Variable", 41 | "raw": "exampleTheorem", 42 | "link": null, 43 | "docstring": null, 44 | "_type": "token"}], 45 | "_type": "sentence"}, 46 | {"contents": 47 | [{"typeinfo": null, 48 | "semanticType": null, 49 | "raw": " (", 50 | "link": null, 51 | "docstring": null, 52 | "_type": "token"}, 53 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 54 | "semanticType": "Name.Variable", 55 | "raw": "p", 56 | "link": null, 57 | "docstring": null, 58 | "_type": "token"}, 59 | {"typeinfo": null, 60 | "semanticType": null, 61 | "raw": " ", 62 | "link": null, 63 | "docstring": null, 64 | "_type": "token"}, 65 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 66 | "semanticType": "Name.Variable", 67 | "raw": "q", 68 | "link": null, 69 | "docstring": null, 70 | "_type": "token"}, 71 | {"typeinfo": null, 72 | "semanticType": null, 73 | "raw": " : ", 74 | "link": null, 75 | "docstring": null, 76 | "_type": "token"}, 77 | {"typeinfo": {"type": "Type", "name": "Prop", "_type": "typeinfo"}, 78 | "semanticType": null, 79 | "raw": "Prop", 80 | "link": null, 81 | "docstring": null, 82 | "_type": "token"}, 83 | {"typeinfo": null, 84 | "semanticType": null, 85 | "raw": ") (", 86 | "link": null, 87 | "docstring": null, 88 | "_type": "token"}, 89 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 90 | "semanticType": "Name.Variable", 91 | "raw": "hp", 92 | "link": null, 93 | "docstring": null, 94 | "_type": "token"}, 95 | {"typeinfo": null, 96 | "semanticType": null, 97 | "raw": " : ", 98 | "link": null, 99 | "docstring": null, 100 | "_type": "token"}, 101 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 102 | "semanticType": "Name.Variable", 103 | "raw": "p", 104 | "link": null, 105 | "docstring": null, 106 | "_type": "token"}, 107 | {"typeinfo": null, 108 | "semanticType": null, 109 | "raw": ") (", 110 | "link": null, 111 | "docstring": null, 112 | "_type": "token"}, 113 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 114 | "semanticType": "Name.Variable", 115 | "raw": "hq", 116 | "link": null, 117 | "docstring": null, 118 | "_type": "token"}, 119 | {"typeinfo": null, 120 | "semanticType": null, 121 | "raw": " : ", 122 | "link": null, 123 | "docstring": null, 124 | "_type": "token"}, 125 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 126 | "semanticType": "Name.Variable", 127 | "raw": "q", 128 | "link": null, 129 | "docstring": null, 130 | "_type": "token"}, 131 | {"typeinfo": null, 132 | "semanticType": null, 133 | "raw": ") : ", 134 | "link": null, 135 | "docstring": null, 136 | "_type": "token"}, 137 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 138 | "semanticType": "Name.Variable", 139 | "raw": "p", 140 | "link": null, 141 | "docstring": null, 142 | "_type": "token"}, 143 | {"typeinfo": null, 144 | "semanticType": null, 145 | "raw": " ∧ ", 146 | "link": null, 147 | "docstring": null, 148 | "_type": "token"}, 149 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 150 | "semanticType": "Name.Variable", 151 | "raw": "q", 152 | "link": null, 153 | "docstring": null, 154 | "_type": "token"}, 155 | {"typeinfo": null, 156 | "semanticType": null, 157 | "raw": " ∧ ", 158 | "link": null, 159 | "docstring": null, 160 | "_type": "token"}, 161 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 162 | "semanticType": "Name.Variable", 163 | "raw": "p", 164 | "link": null, 165 | "docstring": null, 166 | "_type": "token"}, 167 | {"typeinfo": null, 168 | "semanticType": null, 169 | "raw": " := ", 170 | "link": null, 171 | "docstring": null, 172 | "_type": "token"}], 173 | "_type": "text"}, 174 | {"messages": [], 175 | "goals": 176 | [{"name": "", 177 | "hypotheses": [], 178 | "conclusion": "Goals accomplished! 🐙", 179 | "_type": "goal"}], 180 | "contents": 181 | [{"typeinfo": null, 182 | "semanticType": "Keyword", 183 | "raw": "by", 184 | "link": null, 185 | "docstring": 186 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 187 | "_type": "token"}], 188 | "_type": "sentence"}, 189 | {"messages": [], 190 | "goals": 191 | [{"name": "", 192 | "hypotheses": 193 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 194 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 195 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 196 | "conclusion": "p ∧ q ∧ p", 197 | "_type": "goal"}], 198 | "contents": 199 | [{"typeinfo": null, 200 | "semanticType": null, 201 | "raw": "\n ", 202 | "link": null, 203 | "docstring": 204 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 205 | "_type": "token"}], 206 | "_type": "sentence"}, 207 | {"messages": [], 208 | "goals": 209 | [{"name": "left", 210 | "hypotheses": 211 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 212 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 213 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 214 | "conclusion": "p", 215 | "_type": "goal"}, 216 | {"name": "right", 217 | "hypotheses": 218 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 219 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 220 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 221 | "conclusion": "q ∧ p", 222 | "_type": "goal"}], 223 | "contents": 224 | [{"typeinfo": null, 225 | "semanticType": "Keyword", 226 | "raw": "apply", 227 | "link": null, 228 | "docstring": 229 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 230 | "_type": "token"}, 231 | {"typeinfo": null, 232 | "semanticType": null, 233 | "raw": " ", 234 | "link": null, 235 | "docstring": 236 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 237 | "_type": "token"}, 238 | {"typeinfo": 239 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 240 | "name": "And.intro", 241 | "_type": "typeinfo"}, 242 | "semanticType": null, 243 | "raw": "And.intro", 244 | "link": null, 245 | "docstring": 246 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 247 | "_type": "token"}], 248 | "_type": "sentence"}, 249 | {"messages": [], 250 | "goals": 251 | [{"name": "", 252 | "hypotheses": 253 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 254 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 255 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 256 | "conclusion": "p ∧ q ∧ p", 257 | "_type": "goal"}], 258 | "contents": 259 | [{"typeinfo": null, 260 | "semanticType": null, 261 | "raw": "\n ", 262 | "link": null, 263 | "docstring": 264 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 265 | "_type": "token"}], 266 | "_type": "sentence"}, 267 | {"messages": [], 268 | "goals": 269 | [{"name": "left", 270 | "hypotheses": 271 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 272 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 273 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 274 | "conclusion": "p", 275 | "_type": "goal"}], 276 | "contents": 277 | [{"typeinfo": null, 278 | "semanticType": null, 279 | "raw": ".", 280 | "link": null, 281 | "docstring": 282 | "`· tac` focuses on the main goal and tries to solve it using `tac`, or else fails. ", 283 | "_type": "token"}], 284 | "_type": "sentence"}, 285 | {"messages": [], 286 | "goals": 287 | [{"name": "left", 288 | "hypotheses": 289 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 290 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 291 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 292 | "conclusion": "p", 293 | "_type": "goal"}, 294 | {"name": "right", 295 | "hypotheses": 296 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 297 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 298 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 299 | "conclusion": "q ∧ p", 300 | "_type": "goal"}], 301 | "contents": 302 | [{"typeinfo": null, 303 | "semanticType": null, 304 | "raw": " ", 305 | "link": null, 306 | "docstring": 307 | "`· tac` focuses on the main goal and tries to solve it using `tac`, or else fails. ", 308 | "_type": "token"}], 309 | "_type": "sentence"}, 310 | {"messages": [], 311 | "goals": 312 | [{"name": "", 313 | "hypotheses": [], 314 | "conclusion": "Goals accomplished! 🐙", 315 | "_type": "goal"}], 316 | "contents": 317 | [{"typeinfo": null, 318 | "semanticType": "Keyword", 319 | "raw": "exact", 320 | "link": null, 321 | "docstring": 322 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 323 | "_type": "token"}, 324 | {"typeinfo": null, 325 | "semanticType": null, 326 | "raw": " ", 327 | "link": null, 328 | "docstring": 329 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 330 | "_type": "token"}, 331 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 332 | "semanticType": "Name.Variable", 333 | "raw": "hp", 334 | "link": null, 335 | "docstring": null, 336 | "_type": "token"}], 337 | "_type": "sentence"}, 338 | {"messages": [], 339 | "goals": 340 | [{"name": "", 341 | "hypotheses": 342 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 343 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 344 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 345 | "conclusion": "p ∧ q ∧ p", 346 | "_type": "goal"}], 347 | "contents": 348 | [{"typeinfo": null, 349 | "semanticType": null, 350 | "raw": "\n ", 351 | "link": null, 352 | "docstring": 353 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 354 | "_type": "token"}], 355 | "_type": "sentence"}, 356 | {"messages": [], 357 | "goals": 358 | [{"name": "right", 359 | "hypotheses": 360 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 361 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 362 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 363 | "conclusion": "q ∧ p", 364 | "_type": "goal"}], 365 | "contents": 366 | [{"typeinfo": null, 367 | "semanticType": null, 368 | "raw": ".", 369 | "link": null, 370 | "docstring": 371 | "`· tac` focuses on the main goal and tries to solve it using `tac`, or else fails. ", 372 | "_type": "token"}], 373 | "_type": "sentence"}, 374 | {"messages": [], 375 | "goals": 376 | [{"name": "right", 377 | "hypotheses": 378 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 379 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 380 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 381 | "conclusion": "q ∧ p", 382 | "_type": "goal"}], 383 | "contents": 384 | [{"typeinfo": null, 385 | "semanticType": null, 386 | "raw": " ", 387 | "link": null, 388 | "docstring": 389 | "`· tac` focuses on the main goal and tries to solve it using `tac`, or else fails. ", 390 | "_type": "token"}], 391 | "_type": "sentence"}, 392 | {"messages": [], 393 | "goals": 394 | [{"name": "", 395 | "hypotheses": [], 396 | "conclusion": "Goals accomplished! 🐙", 397 | "_type": "goal"}], 398 | "contents": 399 | [{"typeinfo": null, 400 | "semanticType": "Keyword", 401 | "raw": "sorry", 402 | "link": null, 403 | "docstring": 404 | "The `sorry` tactic closes the goal using `sorryAx`. This is intended for stubbing out incomplete\nparts of a proof while still having a syntactically correct proof skeleton. Lean will give\na warning whenever a proof uses `sorry`, so you aren't likely to miss it, but\nyou can double check if a theorem depends on `sorry` by using\n`#print axioms my_thm` and looking for `sorryAx` in the axiom list.\n", 405 | "_type": "token"}], 406 | "_type": "sentence"}, 407 | {"contents": [], "_type": "text"}] -------------------------------------------------------------------------------- /test/theorem_proving/003.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "theorem", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": 15 | {"type": "∀ (p q : Prop), p → q → p ∧ q ∧ p", 16 | "name": "test", 17 | "_type": "typeinfo"}, 18 | "semanticType": "Name.Variable", 19 | "raw": "test", 20 | "link": null, 21 | "docstring": null, 22 | "_type": "token"}, 23 | {"typeinfo": null, 24 | "semanticType": null, 25 | "raw": " (", 26 | "link": null, 27 | "docstring": null, 28 | "_type": "token"}, 29 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 30 | "semanticType": "Name.Variable", 31 | "raw": "p", 32 | "link": null, 33 | "docstring": null, 34 | "_type": "token"}, 35 | {"typeinfo": null, 36 | "semanticType": null, 37 | "raw": " ", 38 | "link": null, 39 | "docstring": null, 40 | "_type": "token"}, 41 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 42 | "semanticType": "Name.Variable", 43 | "raw": "q", 44 | "link": null, 45 | "docstring": null, 46 | "_type": "token"}, 47 | {"typeinfo": null, 48 | "semanticType": null, 49 | "raw": " : ", 50 | "link": null, 51 | "docstring": null, 52 | "_type": "token"}, 53 | {"typeinfo": {"type": "Type", "name": "Prop", "_type": "typeinfo"}, 54 | "semanticType": null, 55 | "raw": "Prop", 56 | "link": null, 57 | "docstring": null, 58 | "_type": "token"}, 59 | {"typeinfo": null, 60 | "semanticType": null, 61 | "raw": ") (", 62 | "link": null, 63 | "docstring": null, 64 | "_type": "token"}, 65 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 66 | "semanticType": "Name.Variable", 67 | "raw": "hp", 68 | "link": null, 69 | "docstring": null, 70 | "_type": "token"}, 71 | {"typeinfo": null, 72 | "semanticType": null, 73 | "raw": " : ", 74 | "link": null, 75 | "docstring": null, 76 | "_type": "token"}, 77 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 78 | "semanticType": "Name.Variable", 79 | "raw": "p", 80 | "link": null, 81 | "docstring": null, 82 | "_type": "token"}, 83 | {"typeinfo": null, 84 | "semanticType": null, 85 | "raw": ") (", 86 | "link": null, 87 | "docstring": null, 88 | "_type": "token"}, 89 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 90 | "semanticType": "Name.Variable", 91 | "raw": "hq", 92 | "link": null, 93 | "docstring": null, 94 | "_type": "token"}, 95 | {"typeinfo": null, 96 | "semanticType": null, 97 | "raw": " : ", 98 | "link": null, 99 | "docstring": null, 100 | "_type": "token"}, 101 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 102 | "semanticType": "Name.Variable", 103 | "raw": "q", 104 | "link": null, 105 | "docstring": null, 106 | "_type": "token"}, 107 | {"typeinfo": null, 108 | "semanticType": null, 109 | "raw": ") : ", 110 | "link": null, 111 | "docstring": null, 112 | "_type": "token"}, 113 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 114 | "semanticType": "Name.Variable", 115 | "raw": "p", 116 | "link": null, 117 | "docstring": null, 118 | "_type": "token"}, 119 | {"typeinfo": null, 120 | "semanticType": null, 121 | "raw": " ∧ ", 122 | "link": null, 123 | "docstring": null, 124 | "_type": "token"}, 125 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 126 | "semanticType": "Name.Variable", 127 | "raw": "q", 128 | "link": null, 129 | "docstring": null, 130 | "_type": "token"}, 131 | {"typeinfo": null, 132 | "semanticType": null, 133 | "raw": " ∧ ", 134 | "link": null, 135 | "docstring": null, 136 | "_type": "token"}, 137 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 138 | "semanticType": "Name.Variable", 139 | "raw": "p", 140 | "link": null, 141 | "docstring": null, 142 | "_type": "token"}, 143 | {"typeinfo": null, 144 | "semanticType": null, 145 | "raw": " := ", 146 | "link": null, 147 | "docstring": null, 148 | "_type": "token"}], 149 | "_type": "text"}, 150 | {"messages": [], 151 | "goals": 152 | [{"name": "", 153 | "hypotheses": [], 154 | "conclusion": "Goals accomplished! 🐙", 155 | "_type": "goal"}], 156 | "contents": 157 | [{"typeinfo": null, 158 | "semanticType": "Keyword", 159 | "raw": "by", 160 | "link": null, 161 | "docstring": 162 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 163 | "_type": "token"}], 164 | "_type": "sentence"}, 165 | {"messages": [], 166 | "goals": 167 | [{"name": "", 168 | "hypotheses": 169 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 170 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 171 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 172 | "conclusion": "p ∧ q ∧ p", 173 | "_type": "goal"}], 174 | "contents": 175 | [{"typeinfo": null, 176 | "semanticType": null, 177 | "raw": "\n ", 178 | "link": null, 179 | "docstring": 180 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 181 | "_type": "token"}], 182 | "_type": "sentence"}, 183 | {"messages": [], 184 | "goals": 185 | [{"name": "left", 186 | "hypotheses": 187 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 188 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 189 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 190 | "conclusion": "p", 191 | "_type": "goal"}, 192 | {"name": "right", 193 | "hypotheses": 194 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 195 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 196 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 197 | "conclusion": "q ∧ p", 198 | "_type": "goal"}], 199 | "contents": 200 | [{"typeinfo": null, 201 | "semanticType": "Keyword", 202 | "raw": "apply", 203 | "link": null, 204 | "docstring": 205 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 206 | "_type": "token"}, 207 | {"typeinfo": null, 208 | "semanticType": null, 209 | "raw": " ", 210 | "link": null, 211 | "docstring": 212 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 213 | "_type": "token"}, 214 | {"typeinfo": 215 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 216 | "name": "And.intro", 217 | "_type": "typeinfo"}, 218 | "semanticType": null, 219 | "raw": "And.intro", 220 | "link": null, 221 | "docstring": 222 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 223 | "_type": "token"}], 224 | "_type": "sentence"}, 225 | {"messages": [], 226 | "goals": 227 | [{"name": "", 228 | "hypotheses": 229 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 230 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 231 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 232 | "conclusion": "p ∧ q ∧ p", 233 | "_type": "goal"}], 234 | "contents": 235 | [{"typeinfo": null, 236 | "semanticType": null, 237 | "raw": "\n ", 238 | "link": null, 239 | "docstring": 240 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 241 | "_type": "token"}], 242 | "_type": "sentence"}, 243 | {"messages": [], 244 | "goals": 245 | [{"name": "right", 246 | "hypotheses": 247 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 248 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 249 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 250 | "conclusion": "q ∧ p", 251 | "_type": "goal"}], 252 | "contents": 253 | [{"typeinfo": null, 254 | "semanticType": "Keyword", 255 | "raw": "exact", 256 | "link": null, 257 | "docstring": 258 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 259 | "_type": "token"}, 260 | {"typeinfo": null, 261 | "semanticType": null, 262 | "raw": " ", 263 | "link": null, 264 | "docstring": 265 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 266 | "_type": "token"}, 267 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 268 | "semanticType": "Name.Variable", 269 | "raw": "hp", 270 | "link": null, 271 | "docstring": null, 272 | "_type": "token"}], 273 | "_type": "sentence"}, 274 | {"messages": [], 275 | "goals": 276 | [{"name": "", 277 | "hypotheses": 278 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 279 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 280 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 281 | "conclusion": "p ∧ q ∧ p", 282 | "_type": "goal"}], 283 | "contents": 284 | [{"typeinfo": null, 285 | "semanticType": null, 286 | "raw": "\n ", 287 | "link": null, 288 | "docstring": 289 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 290 | "_type": "token"}], 291 | "_type": "sentence"}, 292 | {"messages": [], 293 | "goals": 294 | [{"name": "right.left", 295 | "hypotheses": 296 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 297 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 298 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 299 | "conclusion": "q", 300 | "_type": "goal"}, 301 | {"name": "right.right", 302 | "hypotheses": 303 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 304 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 305 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 306 | "conclusion": "p", 307 | "_type": "goal"}], 308 | "contents": 309 | [{"typeinfo": null, 310 | "semanticType": "Keyword", 311 | "raw": "apply", 312 | "link": null, 313 | "docstring": 314 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 315 | "_type": "token"}, 316 | {"typeinfo": null, 317 | "semanticType": null, 318 | "raw": " ", 319 | "link": null, 320 | "docstring": 321 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 322 | "_type": "token"}, 323 | {"typeinfo": 324 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 325 | "name": "And.intro", 326 | "_type": "typeinfo"}, 327 | "semanticType": null, 328 | "raw": "And.intro", 329 | "link": null, 330 | "docstring": 331 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 332 | "_type": "token"}], 333 | "_type": "sentence"}, 334 | {"messages": [], 335 | "goals": 336 | [{"name": "", 337 | "hypotheses": 338 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 339 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 340 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 341 | "conclusion": "p ∧ q ∧ p", 342 | "_type": "goal"}], 343 | "contents": 344 | [{"typeinfo": null, 345 | "semanticType": null, 346 | "raw": "\n ", 347 | "link": null, 348 | "docstring": 349 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 350 | "_type": "token"}], 351 | "_type": "sentence"}, 352 | {"messages": [], 353 | "goals": 354 | [{"name": "right.right", 355 | "hypotheses": 356 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 357 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 358 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 359 | "conclusion": "p", 360 | "_type": "goal"}], 361 | "contents": 362 | [{"typeinfo": null, 363 | "semanticType": "Keyword", 364 | "raw": "exact", 365 | "link": null, 366 | "docstring": 367 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 368 | "_type": "token"}, 369 | {"typeinfo": null, 370 | "semanticType": null, 371 | "raw": " ", 372 | "link": null, 373 | "docstring": 374 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 375 | "_type": "token"}, 376 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 377 | "semanticType": "Name.Variable", 378 | "raw": "hq", 379 | "link": null, 380 | "docstring": null, 381 | "_type": "token"}], 382 | "_type": "sentence"}, 383 | {"messages": [], 384 | "goals": 385 | [{"name": "", 386 | "hypotheses": 387 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 388 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 389 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 390 | "conclusion": "p ∧ q ∧ p", 391 | "_type": "goal"}], 392 | "contents": 393 | [{"typeinfo": null, 394 | "semanticType": null, 395 | "raw": "\n ", 396 | "link": null, 397 | "docstring": 398 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 399 | "_type": "token"}], 400 | "_type": "sentence"}, 401 | {"messages": [], 402 | "goals": 403 | [{"name": "", 404 | "hypotheses": [], 405 | "conclusion": "Goals accomplished! 🐙", 406 | "_type": "goal"}], 407 | "contents": 408 | [{"typeinfo": null, 409 | "semanticType": "Keyword", 410 | "raw": "exact", 411 | "link": null, 412 | "docstring": 413 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 414 | "_type": "token"}, 415 | {"typeinfo": null, 416 | "semanticType": null, 417 | "raw": " ", 418 | "link": null, 419 | "docstring": 420 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 421 | "_type": "token"}, 422 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 423 | "semanticType": "Name.Variable", 424 | "raw": "hp", 425 | "link": null, 426 | "docstring": null, 427 | "_type": "token"}], 428 | "_type": "sentence"}, 429 | {"contents": [], "_type": "text"}] -------------------------------------------------------------------------------- /test/theorem_proving/002.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | [{"typeinfo": null, 3 | "semanticType": "Keyword", 4 | "raw": "theorem", 5 | "link": null, 6 | "docstring": null, 7 | "_type": "token"}, 8 | {"typeinfo": null, 9 | "semanticType": null, 10 | "raw": " ", 11 | "link": null, 12 | "docstring": null, 13 | "_type": "token"}, 14 | {"typeinfo": 15 | {"type": "∀ (p q : Prop), p → q → p ∧ q ∧ p", 16 | "name": "test", 17 | "_type": "typeinfo"}, 18 | "semanticType": "Name.Variable", 19 | "raw": "test", 20 | "link": null, 21 | "docstring": null, 22 | "_type": "token"}, 23 | {"typeinfo": null, 24 | "semanticType": null, 25 | "raw": " (", 26 | "link": null, 27 | "docstring": null, 28 | "_type": "token"}, 29 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 30 | "semanticType": "Name.Variable", 31 | "raw": "p", 32 | "link": null, 33 | "docstring": null, 34 | "_type": "token"}, 35 | {"typeinfo": null, 36 | "semanticType": null, 37 | "raw": " ", 38 | "link": null, 39 | "docstring": null, 40 | "_type": "token"}, 41 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 42 | "semanticType": "Name.Variable", 43 | "raw": "q", 44 | "link": null, 45 | "docstring": null, 46 | "_type": "token"}, 47 | {"typeinfo": null, 48 | "semanticType": null, 49 | "raw": " : ", 50 | "link": null, 51 | "docstring": null, 52 | "_type": "token"}, 53 | {"typeinfo": {"type": "Type", "name": "Prop", "_type": "typeinfo"}, 54 | "semanticType": null, 55 | "raw": "Prop", 56 | "link": null, 57 | "docstring": null, 58 | "_type": "token"}, 59 | {"typeinfo": null, 60 | "semanticType": null, 61 | "raw": ") (", 62 | "link": null, 63 | "docstring": null, 64 | "_type": "token"}, 65 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 66 | "semanticType": "Name.Variable", 67 | "raw": "hp", 68 | "link": null, 69 | "docstring": null, 70 | "_type": "token"}, 71 | {"typeinfo": null, 72 | "semanticType": null, 73 | "raw": " : ", 74 | "link": null, 75 | "docstring": null, 76 | "_type": "token"}, 77 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 78 | "semanticType": "Name.Variable", 79 | "raw": "p", 80 | "link": null, 81 | "docstring": null, 82 | "_type": "token"}, 83 | {"typeinfo": null, 84 | "semanticType": null, 85 | "raw": ") (", 86 | "link": null, 87 | "docstring": null, 88 | "_type": "token"}, 89 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 90 | "semanticType": "Name.Variable", 91 | "raw": "hq", 92 | "link": null, 93 | "docstring": null, 94 | "_type": "token"}, 95 | {"typeinfo": null, 96 | "semanticType": null, 97 | "raw": " : ", 98 | "link": null, 99 | "docstring": null, 100 | "_type": "token"}, 101 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 102 | "semanticType": "Name.Variable", 103 | "raw": "q", 104 | "link": null, 105 | "docstring": null, 106 | "_type": "token"}, 107 | {"typeinfo": null, 108 | "semanticType": null, 109 | "raw": ") : ", 110 | "link": null, 111 | "docstring": null, 112 | "_type": "token"}, 113 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 114 | "semanticType": "Name.Variable", 115 | "raw": "p", 116 | "link": null, 117 | "docstring": null, 118 | "_type": "token"}, 119 | {"typeinfo": null, 120 | "semanticType": null, 121 | "raw": " ∧ ", 122 | "link": null, 123 | "docstring": null, 124 | "_type": "token"}, 125 | {"typeinfo": {"type": "Prop", "name": "q", "_type": "typeinfo"}, 126 | "semanticType": "Name.Variable", 127 | "raw": "q", 128 | "link": null, 129 | "docstring": null, 130 | "_type": "token"}, 131 | {"typeinfo": null, 132 | "semanticType": null, 133 | "raw": " ∧ ", 134 | "link": null, 135 | "docstring": null, 136 | "_type": "token"}, 137 | {"typeinfo": {"type": "Prop", "name": "p", "_type": "typeinfo"}, 138 | "semanticType": "Name.Variable", 139 | "raw": "p", 140 | "link": null, 141 | "docstring": null, 142 | "_type": "token"}, 143 | {"typeinfo": null, 144 | "semanticType": null, 145 | "raw": " :=\n ", 146 | "link": null, 147 | "docstring": null, 148 | "_type": "token"}], 149 | "_type": "text"}, 150 | {"messages": [], 151 | "goals": 152 | [{"name": "", 153 | "hypotheses": [], 154 | "conclusion": "Goals accomplished! 🐙", 155 | "_type": "goal"}], 156 | "contents": 157 | [{"typeinfo": null, 158 | "semanticType": "Keyword", 159 | "raw": "by", 160 | "link": null, 161 | "docstring": 162 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 163 | "_type": "token"}], 164 | "_type": "sentence"}, 165 | {"messages": [], 166 | "goals": 167 | [{"name": "", 168 | "hypotheses": 169 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 170 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 171 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 172 | "conclusion": "p ∧ q ∧ p", 173 | "_type": "goal"}], 174 | "contents": 175 | [{"typeinfo": null, 176 | "semanticType": null, 177 | "raw": " ", 178 | "link": null, 179 | "docstring": 180 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 181 | "_type": "token"}], 182 | "_type": "sentence"}, 183 | {"messages": [], 184 | "goals": 185 | [{"name": "left", 186 | "hypotheses": 187 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 188 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 189 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 190 | "conclusion": "p", 191 | "_type": "goal"}, 192 | {"name": "right", 193 | "hypotheses": 194 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 195 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 196 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 197 | "conclusion": "q ∧ p", 198 | "_type": "goal"}], 199 | "contents": 200 | [{"typeinfo": null, 201 | "semanticType": "Keyword", 202 | "raw": "apply", 203 | "link": null, 204 | "docstring": 205 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 206 | "_type": "token"}, 207 | {"typeinfo": null, 208 | "semanticType": null, 209 | "raw": " ", 210 | "link": null, 211 | "docstring": 212 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 213 | "_type": "token"}, 214 | {"typeinfo": 215 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 216 | "name": "And.intro", 217 | "_type": "typeinfo"}, 218 | "semanticType": null, 219 | "raw": "And.intro", 220 | "link": null, 221 | "docstring": 222 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 223 | "_type": "token"}], 224 | "_type": "sentence"}, 225 | {"messages": [], 226 | "goals": 227 | [{"name": "", 228 | "hypotheses": 229 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 230 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 231 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 232 | "conclusion": "p ∧ q ∧ p", 233 | "_type": "goal"}], 234 | "contents": 235 | [{"typeinfo": null, 236 | "semanticType": null, 237 | "raw": "\n ", 238 | "link": null, 239 | "docstring": 240 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 241 | "_type": "token"}], 242 | "_type": "sentence"}, 243 | {"messages": [], 244 | "goals": 245 | [{"name": "right", 246 | "hypotheses": 247 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 248 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 249 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 250 | "conclusion": "q ∧ p", 251 | "_type": "goal"}], 252 | "contents": 253 | [{"typeinfo": null, 254 | "semanticType": "Keyword", 255 | "raw": "exact", 256 | "link": null, 257 | "docstring": 258 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 259 | "_type": "token"}, 260 | {"typeinfo": null, 261 | "semanticType": null, 262 | "raw": " ", 263 | "link": null, 264 | "docstring": 265 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 266 | "_type": "token"}, 267 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 268 | "semanticType": "Name.Variable", 269 | "raw": "hp", 270 | "link": null, 271 | "docstring": null, 272 | "_type": "token"}], 273 | "_type": "sentence"}, 274 | {"messages": [], 275 | "goals": 276 | [{"name": "", 277 | "hypotheses": 278 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 279 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 280 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 281 | "conclusion": "p ∧ q ∧ p", 282 | "_type": "goal"}], 283 | "contents": 284 | [{"typeinfo": null, 285 | "semanticType": null, 286 | "raw": "\n ", 287 | "link": null, 288 | "docstring": 289 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 290 | "_type": "token"}], 291 | "_type": "sentence"}, 292 | {"messages": [], 293 | "goals": 294 | [{"name": "right.left", 295 | "hypotheses": 296 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 297 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 298 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 299 | "conclusion": "q", 300 | "_type": "goal"}, 301 | {"name": "right.right", 302 | "hypotheses": 303 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 304 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 305 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 306 | "conclusion": "p", 307 | "_type": "goal"}], 308 | "contents": 309 | [{"typeinfo": null, 310 | "semanticType": "Keyword", 311 | "raw": "apply", 312 | "link": null, 313 | "docstring": 314 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 315 | "_type": "token"}, 316 | {"typeinfo": null, 317 | "semanticType": null, 318 | "raw": " ", 319 | "link": null, 320 | "docstring": 321 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 322 | "_type": "token"}, 323 | {"typeinfo": 324 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 325 | "name": "And.intro", 326 | "_type": "typeinfo"}, 327 | "semanticType": null, 328 | "raw": "And.intro", 329 | "link": null, 330 | "docstring": 331 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 332 | "_type": "token"}], 333 | "_type": "sentence"}, 334 | {"messages": [], 335 | "goals": 336 | [{"name": "", 337 | "hypotheses": 338 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 339 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 340 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 341 | "conclusion": "p ∧ q ∧ p", 342 | "_type": "goal"}], 343 | "contents": 344 | [{"typeinfo": null, 345 | "semanticType": null, 346 | "raw": "\n ", 347 | "link": null, 348 | "docstring": 349 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 350 | "_type": "token"}], 351 | "_type": "sentence"}, 352 | {"messages": [], 353 | "goals": 354 | [{"name": "right.right", 355 | "hypotheses": 356 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 357 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 358 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 359 | "conclusion": "p", 360 | "_type": "goal"}], 361 | "contents": 362 | [{"typeinfo": null, 363 | "semanticType": "Keyword", 364 | "raw": "exact", 365 | "link": null, 366 | "docstring": 367 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 368 | "_type": "token"}, 369 | {"typeinfo": null, 370 | "semanticType": null, 371 | "raw": " ", 372 | "link": null, 373 | "docstring": 374 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 375 | "_type": "token"}, 376 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 377 | "semanticType": "Name.Variable", 378 | "raw": "hq", 379 | "link": null, 380 | "docstring": null, 381 | "_type": "token"}], 382 | "_type": "sentence"}, 383 | {"messages": [], 384 | "goals": 385 | [{"name": "", 386 | "hypotheses": 387 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 388 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 389 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 390 | "conclusion": "p ∧ q ∧ p", 391 | "_type": "goal"}], 392 | "contents": 393 | [{"typeinfo": null, 394 | "semanticType": null, 395 | "raw": "\n ", 396 | "link": null, 397 | "docstring": 398 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 399 | "_type": "token"}], 400 | "_type": "sentence"}, 401 | {"messages": [], 402 | "goals": 403 | [{"name": "", 404 | "hypotheses": [], 405 | "conclusion": "Goals accomplished! 🐙", 406 | "_type": "goal"}], 407 | "contents": 408 | [{"typeinfo": null, 409 | "semanticType": "Keyword", 410 | "raw": "exact", 411 | "link": null, 412 | "docstring": 413 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 414 | "_type": "token"}, 415 | {"typeinfo": null, 416 | "semanticType": null, 417 | "raw": " ", 418 | "link": null, 419 | "docstring": 420 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 421 | "_type": "token"}, 422 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 423 | "semanticType": "Name.Variable", 424 | "raw": "hp", 425 | "link": null, 426 | "docstring": null, 427 | "_type": "token"}], 428 | "_type": "sentence"}, 429 | {"contents": [], "_type": "text"}] -------------------------------------------------------------------------------- /test/theorem_proving/007.lean.leanInk.expected: -------------------------------------------------------------------------------- 1 | [{"contents": 2 | "-- set_option trace.Elab.info true\n\ntheorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p := ", 3 | "_type": "text"}, 4 | {"messages": [], 5 | "goals": 6 | [{"name": "", 7 | "hypotheses": [], 8 | "conclusion": "Goals accomplished! 🐙", 9 | "_type": "goal"}], 10 | "contents": 11 | [{"typeinfo": null, 12 | "semanticType": "Keyword", 13 | "raw": "by", 14 | "link": null, 15 | "docstring": 16 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 17 | "_type": "token"}], 18 | "_type": "sentence"}, 19 | {"messages": [], 20 | "goals": 21 | [{"name": "", 22 | "hypotheses": 23 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 24 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 25 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 26 | "conclusion": "p ∧ q ∧ p", 27 | "_type": "goal"}], 28 | "contents": 29 | [{"typeinfo": null, 30 | "semanticType": null, 31 | "raw": "\n ", 32 | "link": null, 33 | "docstring": 34 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 35 | "_type": "token"}], 36 | "_type": "sentence"}, 37 | {"messages": [], 38 | "goals": 39 | [{"name": "left", 40 | "hypotheses": 41 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 42 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 43 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 44 | "conclusion": "p", 45 | "_type": "goal"}, 46 | {"name": "right", 47 | "hypotheses": 48 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 49 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 50 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 51 | "conclusion": "q ∧ p", 52 | "_type": "goal"}], 53 | "contents": 54 | [{"typeinfo": null, 55 | "semanticType": "Keyword", 56 | "raw": "apply", 57 | "link": null, 58 | "docstring": 59 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 60 | "_type": "token"}, 61 | {"typeinfo": null, 62 | "semanticType": null, 63 | "raw": " ", 64 | "link": null, 65 | "docstring": 66 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 67 | "_type": "token"}, 68 | {"typeinfo": 69 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 70 | "name": "And.intro", 71 | "_type": "typeinfo"}, 72 | "semanticType": null, 73 | "raw": "And.intro", 74 | "link": null, 75 | "docstring": 76 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 77 | "_type": "token"}], 78 | "_type": "sentence"}, 79 | {"messages": [], 80 | "goals": 81 | [{"name": "", 82 | "hypotheses": 83 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 84 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 85 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 86 | "conclusion": "p ∧ q ∧ p", 87 | "_type": "goal"}], 88 | "contents": 89 | [{"typeinfo": null, 90 | "semanticType": null, 91 | "raw": "\n ", 92 | "link": null, 93 | "docstring": 94 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 95 | "_type": "token"}], 96 | "_type": "sentence"}, 97 | {"messages": [], 98 | "goals": 99 | [{"name": "", 100 | "hypotheses": 101 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 102 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 103 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 104 | "conclusion": "q ∧ p", 105 | "_type": "goal"}], 106 | "contents": 107 | [{"typeinfo": null, 108 | "semanticType": "Keyword", 109 | "raw": "case", 110 | "link": null, 111 | "docstring": 112 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 113 | "_type": "token"}, 114 | {"typeinfo": null, 115 | "semanticType": null, 116 | "raw": " right =>\n ", 117 | "link": null, 118 | "docstring": 119 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 120 | "_type": "token"}], 121 | "_type": "sentence"}, 122 | {"messages": [], 123 | "goals": 124 | [{"name": "left", 125 | "hypotheses": 126 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 127 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 128 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 129 | "conclusion": "q", 130 | "_type": "goal"}, 131 | {"name": "right", 132 | "hypotheses": 133 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 134 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 135 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 136 | "conclusion": "p", 137 | "_type": "goal"}], 138 | "contents": 139 | [{"typeinfo": null, 140 | "semanticType": "Keyword", 141 | "raw": "apply", 142 | "link": null, 143 | "docstring": 144 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 145 | "_type": "token"}, 146 | {"typeinfo": null, 147 | "semanticType": null, 148 | "raw": " ", 149 | "link": null, 150 | "docstring": 151 | "`apply e` tries to match the current goal against the conclusion of `e`'s type.\nIf it succeeds, then the tactic returns as many subgoals as the number of premises that\nhave not been fixed by type inference or type class resolution.\nNon-dependent premises are added before dependent ones.\n\nThe `apply` tactic uses higher-order pattern matching, type class resolution,\nand first-order unification with dependent types.\n", 152 | "_type": "token"}, 153 | {"typeinfo": 154 | {"type": "∀ {a b : Prop}, a → b → a ∧ b", 155 | "name": "And.intro", 156 | "_type": "typeinfo"}, 157 | "semanticType": null, 158 | "raw": "And.intro", 159 | "link": null, 160 | "docstring": 161 | "`And.intro : a → b → a ∧ b` is the constructor for the And operation. ", 162 | "_type": "token"}], 163 | "_type": "sentence"}, 164 | {"messages": [], 165 | "goals": 166 | [{"name": "", 167 | "hypotheses": 168 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 169 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 170 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 171 | "conclusion": "q ∧ p", 172 | "_type": "goal"}], 173 | "contents": 174 | [{"typeinfo": null, 175 | "semanticType": null, 176 | "raw": "\n ", 177 | "link": null, 178 | "docstring": 179 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 180 | "_type": "token"}], 181 | "_type": "sentence"}, 182 | {"messages": [], 183 | "goals": 184 | [{"name": "", 185 | "hypotheses": 186 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 187 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 188 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 189 | "conclusion": "q", 190 | "_type": "goal"}], 191 | "contents": 192 | [{"typeinfo": null, 193 | "semanticType": "Keyword", 194 | "raw": "case", 195 | "link": null, 196 | "docstring": 197 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 198 | "_type": "token"}, 199 | {"typeinfo": null, 200 | "semanticType": null, 201 | "raw": " left => ", 202 | "link": null, 203 | "docstring": 204 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 205 | "_type": "token"}], 206 | "_type": "sentence"}, 207 | {"messages": [], 208 | "goals": 209 | [{"name": "", 210 | "hypotheses": [], 211 | "conclusion": "Goals accomplished! 🐙", 212 | "_type": "goal"}], 213 | "contents": 214 | [{"typeinfo": null, 215 | "semanticType": "Keyword", 216 | "raw": "exact", 217 | "link": null, 218 | "docstring": 219 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 220 | "_type": "token"}, 221 | {"typeinfo": null, 222 | "semanticType": null, 223 | "raw": " ", 224 | "link": null, 225 | "docstring": 226 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 227 | "_type": "token"}, 228 | {"typeinfo": {"type": "q", "name": "hq", "_type": "typeinfo"}, 229 | "semanticType": "Name.Variable", 230 | "raw": "hq", 231 | "link": null, 232 | "docstring": null, 233 | "_type": "token"}], 234 | "_type": "sentence"}, 235 | {"messages": [], 236 | "goals": 237 | [{"name": "", 238 | "hypotheses": 239 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 240 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 241 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 242 | "conclusion": "q ∧ p", 243 | "_type": "goal"}], 244 | "contents": 245 | [{"typeinfo": null, 246 | "semanticType": null, 247 | "raw": "\n ", 248 | "link": null, 249 | "docstring": 250 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 251 | "_type": "token"}], 252 | "_type": "sentence"}, 253 | {"messages": [], 254 | "goals": 255 | [{"name": "", 256 | "hypotheses": 257 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 258 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 259 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 260 | "conclusion": "p", 261 | "_type": "goal"}], 262 | "contents": 263 | [{"typeinfo": null, 264 | "semanticType": "Keyword", 265 | "raw": "case", 266 | "link": null, 267 | "docstring": 268 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 269 | "_type": "token"}, 270 | {"typeinfo": null, 271 | "semanticType": null, 272 | "raw": " right => ", 273 | "link": null, 274 | "docstring": 275 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 276 | "_type": "token"}], 277 | "_type": "sentence"}, 278 | {"messages": [], 279 | "goals": 280 | [{"name": "", 281 | "hypotheses": [], 282 | "conclusion": "Goals accomplished! 🐙", 283 | "_type": "goal"}], 284 | "contents": 285 | [{"typeinfo": null, 286 | "semanticType": "Keyword", 287 | "raw": "exact", 288 | "link": null, 289 | "docstring": 290 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 291 | "_type": "token"}, 292 | {"typeinfo": null, 293 | "semanticType": null, 294 | "raw": " ", 295 | "link": null, 296 | "docstring": 297 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 298 | "_type": "token"}, 299 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 300 | "semanticType": "Name.Variable", 301 | "raw": "hp", 302 | "link": null, 303 | "docstring": null, 304 | "_type": "token"}], 305 | "_type": "sentence"}, 306 | {"messages": [], 307 | "goals": 308 | [{"name": "", 309 | "hypotheses": 310 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 311 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 312 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 313 | "conclusion": "p ∧ q ∧ p", 314 | "_type": "goal"}], 315 | "contents": 316 | [{"typeinfo": null, 317 | "semanticType": null, 318 | "raw": "\n ", 319 | "link": null, 320 | "docstring": 321 | "`by tac` constructs a term of the expected type by running the tactic(s) `tac`. ", 322 | "_type": "token"}], 323 | "_type": "sentence"}, 324 | {"messages": [], 325 | "goals": 326 | [{"name": "", 327 | "hypotheses": 328 | [{"type": "Prop", "names": ["p", "q"], "body": "", "_type": "hypothesis"}, 329 | {"type": "p", "names": ["hp"], "body": "", "_type": "hypothesis"}, 330 | {"type": "q", "names": ["hq"], "body": "", "_type": "hypothesis"}], 331 | "conclusion": "p", 332 | "_type": "goal"}], 333 | "contents": 334 | [{"typeinfo": null, 335 | "semanticType": "Keyword", 336 | "raw": "case", 337 | "link": null, 338 | "docstring": 339 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 340 | "_type": "token"}, 341 | {"typeinfo": null, 342 | "semanticType": null, 343 | "raw": " left => ", 344 | "link": null, 345 | "docstring": 346 | "* `case tag => tac` focuses on the goal with case name `tag` and solves it using `tac`,\n or else fails.\n* `case tag x₁ ... xₙ => tac` additionally renames the `n` most recent hypotheses\n with inaccessible names to the given names.\n* `case tag₁ | tag₂ => tac` is equivalent to `(case tag₁ => tac); (case tag₂ => tac)`.\n", 347 | "_type": "token"}], 348 | "_type": "sentence"}, 349 | {"messages": [], 350 | "goals": 351 | [{"name": "", 352 | "hypotheses": [], 353 | "conclusion": "Goals accomplished! 🐙", 354 | "_type": "goal"}], 355 | "contents": 356 | [{"typeinfo": null, 357 | "semanticType": "Keyword", 358 | "raw": "exact", 359 | "link": null, 360 | "docstring": 361 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 362 | "_type": "token"}, 363 | {"typeinfo": null, 364 | "semanticType": null, 365 | "raw": " ", 366 | "link": null, 367 | "docstring": 368 | "`exact e` closes the main goal if its target type matches that of `e`.\n", 369 | "_type": "token"}, 370 | {"typeinfo": {"type": "p", "name": "hp", "_type": "typeinfo"}, 371 | "semanticType": "Name.Variable", 372 | "raw": "hp", 373 | "link": null, 374 | "docstring": null, 375 | "_type": "token"}], 376 | "_type": "sentence"}, 377 | {"contents": 378 | [{"typeinfo": null, 379 | "semanticType": null, 380 | "raw": "\n", 381 | "link": null, 382 | "docstring": null, 383 | "_type": "token"}], 384 | "_type": "text"}] --------------------------------------------------------------------------------