Skip to content

Commit 44a2b08

Browse files
Khakim-em
andauthored
feat: scripts/Modulize.lean (#10460)
This PR introduces a simple script that adjusts module headers in a package for use of the module system, without further minimizing import or annotation use. --------- Co-authored-by: Kim Morrison <[email protected]>
1 parent 7f18c73 commit 44a2b08

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed

lean.code-workspace

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
},
99
{
1010
"path": "tests"
11+
},
12+
{
13+
"path": "script"
1114
}
1215
],
1316
"settings": {

script/Modulize.lean

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import Lake.CLI.Main
2+
3+
/-!
4+
A simple script that inserts `module` and `@[expose] public section` into un-modulized files and
5+
bumps their imports to `public`.
6+
-/
7+
8+
open Lean Parser.Module
9+
10+
def main (args : List String) : IO Unit := do
11+
initSearchPath (← findSysroot)
12+
let mods := args.toArray.map (·.toName)
13+
14+
-- Determine default module(s) to run modulize on
15+
let defaultTargetModules : Array Name ← try
16+
let (elanInstall?, leanInstall?, lakeInstall?) ← Lake.findInstall?
17+
let config ← Lake.MonadError.runEIO <| Lake.mkLoadConfig { elanInstall?, leanInstall?, lakeInstall? }
18+
let some workspace ← Lake.loadWorkspace config |>.toBaseIO
19+
| throw <| IO.userError "failed to load Lake workspace"
20+
let defaultTargetModules := workspace.root.defaultTargets.flatMap fun target =>
21+
if let some lib := workspace.root.findLeanLib? target then
22+
lib.roots
23+
else if let some exe := workspace.root.findLeanExe? target then
24+
#[exe.config.root]
25+
else
26+
#[]
27+
pure defaultTargetModules
28+
catch _ =>
29+
pure #[]
30+
31+
-- the list of root modules
32+
let mods := if mods.isEmpty then defaultTargetModules else mods
33+
-- Only submodules of `pkg` will be edited or have info reported on them
34+
let pkg := mods[0]!.components.head!
35+
36+
-- Load all the modules
37+
let imps := mods.map ({ module := · })
38+
let env ← importModules imps {}
39+
40+
let srcSearchPath ← getSrcSearchPath
41+
42+
for mod in env.header.moduleNames do
43+
if !pkg.isPrefixOf mod then
44+
continue
45+
46+
-- Parse the input file
47+
let some path ← srcSearchPath.findModuleWithExt "lean" mod
48+
| throw <| .userError "error: failed to find source file for {mod}"
49+
let mut text ← IO.FS.readFile path
50+
let inputCtx := Parser.mkInputContext text path.toString
51+
let (header, parserState, msgs) ← Parser.parseHeader inputCtx
52+
if !msgs.toList.isEmpty then -- skip this file if there are parse errors
53+
msgs.forM fun msg => msg.toString >>= IO.println
54+
throw <| .userError "parse errors in file"
55+
let `(header| $[module%$moduleTk?]? $imps:import*) := header
56+
| throw <| .userError s!"unexpected header syntax of {path}"
57+
if moduleTk?.isSome then
58+
continue
59+
60+
let looksMeta := mod.components.any (· ∈ [`Tactic, `Linter])
61+
62+
-- initial whitespace if empty header
63+
let startPos := header.raw.getPos? |>.getD parserState.pos
64+
65+
-- insert section if any trailing text
66+
if header.raw.getTrailingTailPos?.all (· < text.endPos) then
67+
let insertPos := header.raw.getTailPos? |>.getD startPos -- empty header
68+
let mut sec := if looksMeta then
69+
"public meta section"
70+
else
71+
"@[expose] public section"
72+
if !imps.isEmpty then
73+
sec := "\n\n" ++ sec
74+
if header.raw.getTailPos?.isNone then
75+
sec := sec ++ "\n\n"
76+
text := text.extract 0 insertPos ++ sec ++ text.extract insertPos text.endPos
77+
78+
-- prepend each import with `public `
79+
for imp in imps.reverse do
80+
let insertPos := imp.raw.getPos?.get!
81+
let prfx := if looksMeta then "public meta " else "public "
82+
text := text.extract 0 insertPos ++ prfx ++ text.extract insertPos text.endPos
83+
84+
-- insert `module` header
85+
let mut initText := text.extract 0 startPos
86+
if !initText.trim.isEmpty then
87+
-- If there is a header comment, preserve it and put `module` in the line after
88+
initText := initText.trimRight ++ "\n"
89+
text := initText ++ "module\n\n" ++ text.extract startPos text.endPos
90+
91+
IO.FS.writeFile path text

script/lakefile.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
name = "scripts"
2+
3+
[[lean_exe]]
4+
name = "modulize"
5+
root = "Modulize"

script/lean-toolchain

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lean4

0 commit comments

Comments
 (0)