Skip to content

Commit effc925

Browse files
authored
Merge pull request #62 from trishullab/feature/itp-interface-backward-compat
Made tactic parser backwards compatible till Lean 4 v4.15.0
2 parents ee5de39 + 5d2d03d commit effc925

File tree

11 files changed

+91
-105
lines changed

11 files changed

+91
-105
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pip install itp-interface
1616
```
1717

1818
2. Run the following command to prepare the REPL for Lean 4. The default version is 4.24.0. You can change the version by setting the `LEAN_VERSION` environment variable. If no version is set, then 4.24.0 is used.
19-
>NOTE: The Lean 4 version must match the version of the Lean 4 project you are working with.
19+
>NOTE: The Lean 4 version must match the version of the Lean 4 project you are working with. `itp-interface` **supports Lean 4 version >= 4.15.0 and <= 4.24.0**. (It has been tested till version 4.24.0, but might as well work for future versions too, if the future versions are completely backwards-compatible).
20+
2021
```bash
2122
install-lean-repl
2223
# To use a different Lean version, set LEAN_VERSION before running:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ requires = [
55
build-backend = "hatchling.build"
66
[project]
77
name = "itp_interface"
8-
version = "1.1.15"
8+
version = "1.1.16"
99
authors = [
1010
{ name="Amitayush Thakur", email="[email protected]" },
1111
]

src/itp_interface/main/install.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import os
22
import random
33
import string
4+
import logging
5+
import traceback
46
from itp_interface.tools.tactic_parser import build_tactic_parser_if_needed
57

68
file_path = os.path.abspath(__file__)
79

10+
logging.basicConfig(level=logging.INFO)
11+
# Create a console logger
12+
logger = logging.getLogger(__name__)
813

914
def generate_random_string(length, allowed_chars=None):
1015
if allowed_chars is None:
@@ -25,7 +30,12 @@ def install_itp_interface():
2530
print("Lean toolchain version for tactic_parser: ", lean_toolchain_content)
2631
print(f"LEAN_VERSION set: {os.environ.get('LEAN_VERSION', 'Not Set')}")
2732
print("Building itp_interface")
28-
build_tactic_parser_if_needed()
33+
try:
34+
build_tactic_parser_if_needed(logger)
35+
except Exception:
36+
# print the stack trace
37+
traceback.print_exc()
38+
raise
2939

3040
def install_lean_repl():
3141
print("Updating Lean")

src/itp_interface/tools/dynamic_lean4_proof_exec.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@ def cancel_tactic_till_line(self, tactic_line_num: int, no_backtracking: bool =
159159
if line_num >= tactic_line_num:
160160
del self.run_state.line_tactic_map[line_num]
161161
line_proof_context_map_keys = list(self.run_state.line_proof_context_map.keys())
162-
for line_num in line_proof_context_map_keys:
163-
if line_num >= tactic_line_num:
164-
del self.run_state.line_proof_context_map[line_num]
165162
if not no_backtracking:
166163
self.proof_context = self.run_state.line_proof_context_map[tactic_line_num]
167164
self.line_num = tactic_line_num
168165
cancelled_some_tactics = self._backtrack_tactic_line(tactic_line_num)
169166
self._proof_running = self.proof_context is not None
167+
for line_num in line_proof_context_map_keys:
168+
if line_num >= tactic_line_num:
169+
del self.run_state.line_proof_context_map[line_num]
170170
return cancelled_some_tactics

src/itp_interface/tools/tactic_parser.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -249,35 +249,6 @@ class FileDependencyAnalysis(BaseModel):
249249
def __repr__(self) -> str:
250250
return f"FileDependencyAnalysis({self.module_name}, {len(self.declarations)} decls)"
251251

252-
# def to_dict(self) -> Dict:
253-
# """Convert to dictionary matching the JSON format."""
254-
# return {
255-
# "filePath": self.file_path,
256-
# "moduleName": self.module_name,
257-
# "imports": self.imports,
258-
# "declarations": [
259-
# {
260-
# "declaration": decl.declaration.to_dict(),
261-
# "dependencies": [dep.model_dump(exclude_none=True) for dep in decl.dependencies],
262-
# "unresolvedNames": decl.unresolved_names,
263-
# **({"declId": decl.decl_id} if decl.decl_id else {})
264-
# }
265-
# for decl in self.declarations
266-
# ]
267-
# }
268-
269-
# @staticmethod
270-
# def from_dict(data: Dict) -> 'FileDependencyAnalysis':
271-
# """Parse from the JSON dict returned by dependency-parser executable."""
272-
# declarations = DeclWithDependencies.from_dependency_analysis(data)
273-
274-
# return FileDependencyAnalysis(
275-
# file_path=data['filePath'],
276-
# module_name=data['moduleName'],
277-
# imports=data.get('imports', []),
278-
# declarations=declarations
279-
# )
280-
281252
# Create an enum for parsing request type
282253
class RequestType(Enum):
283254
PARSE_TACTICS = "parse_tactics"

src/itp_interface/tools/tactic_parser/TacticParser/Base64.lean

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,49 +25,50 @@ def charToValue (c : Char) : Option UInt8 :=
2525
else
2626
none
2727

28-
/-- Decode a base64 string to bytes -/
29-
def decodeBytes (s : String) : Except String ByteArray :=
30-
let chars := s.trim.toList.filter (· ≠ '=')
31-
let rec loop (i : Nat) (result : ByteArray) : Except String ByteArray :=
32-
if i >= chars.length then
33-
.ok result
34-
else
35-
-- Get 4 characters (or remaining)
36-
let c1 := chars[i]!
37-
let c2 := if i + 1 < chars.length then chars[i + 1]! else '='
38-
let c3 := if i + 2 < chars.length then chars[i + 2]! else '='
39-
let c4 := if i + 3 < chars.length then chars[i + 3]! else '='
28+
partial def loop (i : Nat) (result : ByteArray) (chars : List Char) : Except String ByteArray :=
29+
if i >= chars.length then
30+
.ok result
31+
else
32+
-- Get 4 characters (or remaining)
33+
let c1 := chars[i]!
34+
let c2 := if i + 1 < chars.length then chars[i + 1]! else '='
35+
let c3 := if i + 2 < chars.length then chars[i + 2]! else '='
36+
let c4 := if i + 3 < chars.length then chars[i + 3]! else '='
4037

41-
match Option.toExcept (charToValue c1) "Invalid base64 character" with
38+
match Option.toExcept (charToValue c1) "Invalid base64 character" with
39+
| .error e => .error e
40+
| .ok v1 =>
41+
match Option.toExcept (charToValue c2) "Invalid base64 character" with
4242
| .error e => .error e
43-
| .ok v1 =>
44-
match Option.toExcept (charToValue c2) "Invalid base64 character" with
45-
| .error e => .error e
46-
| .ok v2 =>
47-
-- First byte is always present
48-
let b1 := (v1.toNat <<< 2 ||| (v2.toNat >>> 4)).toUInt8
49-
let result := result.push b1
43+
| .ok v2 =>
44+
-- First byte is always present
45+
let b1 := (v1.toNat <<< 2 ||| (v2.toNat >>> 4)).toUInt8
46+
let result := result.push b1
5047

51-
-- Second byte if c3 exists
52-
if c3 ≠ '=' then
53-
match Option.toExcept (charToValue c3) "Invalid base64 character" with
54-
| .error e => .error e
55-
| .ok v3 =>
56-
let b2 := ((v2.toNat &&& 0xF) <<< 4 ||| (v3.toNat >>> 2)).toUInt8
57-
let result := result.push b2
48+
-- Second byte if c3 exists
49+
if c3 ≠ '=' then
50+
match Option.toExcept (charToValue c3) "Invalid base64 character" with
51+
| .error e => .error e
52+
| .ok v3 =>
53+
let b2 := ((v2.toNat &&& 0xF) <<< 4 ||| (v3.toNat >>> 2)).toUInt8
54+
let result := result.push b2
5855

59-
-- Third byte if c4 exists
60-
if c4 ≠ '=' then
61-
match Option.toExcept (charToValue c4) "Invalid base64 character" with
62-
| .error e => .error e
63-
| .ok v4 =>
64-
let b3 := ((v3.toNat &&& 0x3) <<< 6 ||| v4.toNat).toUInt8
65-
loop (i + 4) (result.push b3)
66-
else
67-
loop (i + 4) result
68-
else
69-
loop (i + 4) result
70-
loop 0 ByteArray.empty
56+
-- Third byte if c4 exists
57+
if c4 ≠ '=' then
58+
match Option.toExcept (charToValue c4) "Invalid base64 character" with
59+
| .error e => .error e
60+
| .ok v4 =>
61+
let b3 := ((v3.toNat &&& 0x3) <<< 6 ||| v4.toNat).toUInt8
62+
loop (i + 4) (result.push b3) chars
63+
else
64+
loop (i + 4) result chars
65+
else
66+
loop (i + 4) result chars
67+
68+
/-- Decode a base64 string to bytes -/
69+
def decodeBytes (s : String) : Except String ByteArray :=
70+
let chars := s.trim.toList.filter (· ≠ '=')
71+
loop 0 ByteArray.empty chars
7172

7273
/-- Decode a base64 string to UTF-8 string -/
7374
def decode (s : String) : Except String String := do

src/itp_interface/tools/tactic_parser/TacticParser/DependencyParser.lean

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,31 @@ def extractDependenciesFromSyntax (env : Environment) (stx : Syntax) : Array Dec
150150
| none => (deps, unres.push name.toString)
151151
) (#[], #[])
152152

153+
/-- Parse the header recursively to find all import commands -/
154+
partial def findImports (stx : Syntax) (content: String) : IO (Array ImportInfo) → IO (Array ImportInfo)
155+
| accIO => do
156+
let acc ← accIO
157+
match stx with
158+
| Syntax.node _ kind args =>
159+
if kind == `Lean.Parser.Module.import then
160+
-- Found an import
161+
match stx.getRange? with
162+
| some range =>
163+
let moduleName := extractModuleName stx
164+
let text := content.extract range.start range.stop
165+
let info : ImportInfo := {
166+
moduleName := moduleName
167+
startPos := range.start.byteIdx
168+
endPos := range.stop.byteIdx
169+
text := text
170+
}
171+
return acc.push info
172+
| none => return acc
173+
else
174+
-- Recursively search children
175+
args.foldlM (fun acc child => findImports child content (pure acc)) acc
176+
| _ => return acc
177+
153178
/-- Parse imports and namespaces from a Lean 4 file -/
154179
def parseImports (filepath : System.FilePath) : IO DependencyInfo := do
155180
let content ← IO.FS.readFile filepath
@@ -164,32 +189,9 @@ def parseImports (filepath : System.FilePath) : IO DependencyInfo := do
164189
-- Extract the underlying syntax from TSyntax
165190
let headerSyn : Syntax := headerStx
166191

167-
-- Parse the header recursively to find all import commands
168-
let rec findImports (stx : Syntax) : IO (Array ImportInfo) → IO (Array ImportInfo)
169-
| accIO => do
170-
let acc ← accIO
171-
match stx with
172-
| Syntax.node _ kind args =>
173-
if kind == `Lean.Parser.Module.import then
174-
-- Found an import
175-
match stx.getRange? with
176-
| some range =>
177-
let moduleName := extractModuleName stx
178-
let text := content.extract range.start range.stop
179-
let info : ImportInfo := {
180-
moduleName := moduleName
181-
startPos := range.start.byteIdx
182-
endPos := range.stop.byteIdx
183-
text := text
184-
}
185-
return acc.push info
186-
| none => return acc
187-
else
188-
-- Recursively search children
189-
args.foldlM (fun acc child => findImports child (pure acc)) acc
190-
| _ => return acc
191-
192-
imports ← findImports headerSyn (pure imports)
192+
193+
194+
imports ← findImports headerSyn content (pure imports)
193195

194196
-- Now parse the rest of the file to find namespace declarations
195197
let env ← Lean.importModules #[] {} 0

src/itp_interface/tools/tactic_parser/TacticParser/LineParser.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ partial def identifySomeDeclType (stx : Syntax) : Option (DeclType × Nat) :=
1414
match stx with
1515
| Syntax.node _ _ args =>
1616
-- Look for the actual declaration type in the children
17-
let idx := args.findIdx (fun a => (identifySomeDeclType a).isSome);
17+
-- NOTE: using findIdx? for backward compatibility to Lean 4.15
18+
-- (as.findIdx? p).getD as.size
19+
let idx := (args.findIdx? (fun a => (identifySomeDeclType a).isSome)).getD args.size;
1820
if idx = args.size then
1921
some (.unknown, idx)
2022
else

src/itp_interface/tools/tactic_parser/TacticParser/SyntaxWalker.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def nodeEndPos (node : InfoTreeNode) : Option Position :=
181181
| InfoTreeNode.leanInfo _ _ _ _ _ endPos _ _ => some endPos
182182
| _ => none
183183

184-
def filterAllNodesWhichDontStartAndEndOnLine (node : InfoTreeNode) (line_num: Nat) : Array InfoTreeNode :=
184+
partial def filterAllNodesWhichDontStartAndEndOnLine (node : InfoTreeNode) (line_num: Nat) : Array InfoTreeNode :=
185185
match node with
186186
| .context child =>
187187
filterAllNodesWhichDontStartAndEndOnLine child line_num
@@ -213,7 +213,7 @@ let arg_max := all_possible_nodes.foldl (fun (acc_node, acc_len) n =>
213213
) (InfoTreeNode.hole, 0)
214214
arg_max
215215

216-
def getAllLinesInTree (node : InfoTreeNode) : Std.HashSet Nat :=
216+
partial def getAllLinesInTree (node : InfoTreeNode) : Std.HashSet Nat :=
217217
match node with
218218
| .context child =>
219219
getAllLinesInTree child

src/itp_interface/tools/tactic_parser/TacticParser/Types.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ partial def InfoNodeStruct.toJson (n: InfoNodeStruct) : Json :=
269269
instance : ToJson InfoNodeStruct where
270270
toJson := InfoNodeStruct.toJson
271271

272-
def getInfoNodeStruct (node : InfoTreeNode) : Option InfoNodeStruct :=
272+
partial def getInfoNodeStruct (node : InfoTreeNode) : Option InfoNodeStruct :=
273273
match node with
274274
| .leanInfo declType name docString text startPos endPos namespc children =>
275275
let childStructs := children.map getInfoNodeStruct

0 commit comments

Comments
 (0)