Skip to content

Commit 96268d0

Browse files
authored
Merge pull request #211 from JunoLab/avi/setupmodules
setup modules.jl
2 parents 480ca57 + 16ce7a6 commit 96268d0

9 files changed

Lines changed: 410 additions & 264 deletions

File tree

src/Atom.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
__precompile__()
22

3+
@doc read(joinpath(dirname(@__DIR__), "README.md"), String)
34
module Atom
45

56
using Juno, Lazy, JSON, MacroTools, Media, Base.StackTraces
@@ -44,6 +45,7 @@ include("utils.jl")
4445
include("display/display.jl")
4546
include("progress.jl")
4647
include("eval.jl")
48+
include("modules.jl")
4749
include("workspace.jl")
4850
include("repl.jl")
4951
include("docs.jl")

src/comm.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ function initialise(; welcome = false)
8383
welcome && @msg welcome()
8484
end
8585

86+
exit_on_sigint(on) = ccall(:jl_exit_on_sigint, Nothing, (Cint,), on)
87+
8688
"""
8789
serve(port; kws...)
8890

src/eval.jl

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,11 @@
1-
using CodeTools, LNR, Media
1+
using Media
22
import REPL
33

44
using Logging: with_logger
55
using .Progress: JunoProgressLogger
66

77
ends_with_semicolon(x) = REPL.ends_with_semicolon(split(x,'\n',keepempty = false)[end])
88

9-
LNR.cursor(data::AbstractDict) = cursor(data["row"], data["column"])
10-
11-
exit_on_sigint(on) = ccall(:jl_exit_on_sigint, Nothing, (Cint,), on)
12-
13-
function modulenames(data, pos)
14-
main = haskey(data, "module") ? data["module"] :
15-
haskey(data, "path") ? CodeTools.filemodule(data["path"]) :
16-
"Main"
17-
main == "" && (main = "Main")
18-
sub = CodeTools.codemodule(data["code"], pos)
19-
main, sub
20-
end
21-
22-
# keeps the latest file that has been used for Main module scope
23-
const MAIN_MODULE_LOCATION = Ref{Tuple{String, Int}}(moduledefinition(Main))
24-
25-
handle("module") do data
26-
main, sub = modulenames(data, cursor(data))
27-
28-
mod = CodeTools.getmodule(main)
29-
smod = CodeTools.getmodule(mod, sub)
30-
31-
if main == "Main" && sub == ""
32-
MAIN_MODULE_LOCATION[] = get!(data, "path", ""), data["row"]
33-
end
34-
35-
return d(:main => main,
36-
:sub => sub,
37-
:inactive => (mod==nothing),
38-
:subInactive => smod==nothing)
39-
end
40-
41-
handle("allmodules") do
42-
sort!([string(m) for m in CodeTools.allchildren(Main)])
43-
end
44-
45-
isselection(data) = data["start"] data["stop"]
46-
479
withpath(f, path) =
4810
CodeTools.withpath(f, path == nothing || isuntitled(path) ? nothing : path)
4911

src/goto.jl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,6 @@ function searchtoplevelitems(mod::Module, text::String, path::Nothing)
165165
return pathitemsmaps
166166
end
167167

168-
# TODO:
169-
# use the module detection logic below for general module auto-detection,
170-
# e.g.: `module` handler and such
171-
172168
# sub entry method
173169
function _searchtoplevelitems(mod::Module, pathitemsmaps::PathItemsMaps)
174170
entrypath, paths = modulefiles(mod) # Revise-like approach

src/modules.jl

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
#=
2+
finding all the included files for a module
3+
4+
1. Revise-like approach
5+
* mostly adapted from https://github.com/timholy/Revise.jl/tree/b0c5c864ea78b93caaa820cb9cfc45eca47f43ff
6+
* only works for precompiled modules
7+
2. CSTPraser-based approach
8+
* static parsing -- works for all the files but costly
9+
* TODO: excludes files in submodules when searched from the parent module
10+
* TODO: looks for non-toplevel `include` calls
11+
=#
12+
13+
# Revise-like approach
14+
# --------------------
15+
16+
using Base: PkgId, UUID
17+
18+
"""
19+
parentfile, included_files = modulefiles(mod::Module)
20+
21+
Return the `parentfile` in which `mod` was defined, as well as a list of any
22+
other files that were `include`d to define `mod`. If this operation is unsuccessful,
23+
`(nothing, nothing)` is returned.
24+
All files are returned as absolute paths.
25+
"""
26+
function modulefiles(mod::Module)
27+
# NOTE: src_file_key stuff was removed when adapted
28+
parentfile = String(first(methods(getfield(mod, :eval))).file)
29+
id = Base.PkgId(mod)
30+
if id.name == "Base" || Symbol(id.name) stdlib_names
31+
parentfile = normpath(Base.find_source_file(parentfile))
32+
filedata = Base._included_files
33+
else
34+
use_compiled_modules() || return nothing, nothing # FIXME: support non-precompiled packages
35+
_, filedata = pkg_fileinfo(id)
36+
end
37+
filedata === nothing && return nothing, nothing
38+
included_files = filter(mf -> mf[1] == mod, filedata)
39+
return fixpath(parentfile), [fixpath(mf[2]) for mf in included_files]
40+
end
41+
42+
# Fix paths to files that define Julia (base and stdlibs)
43+
function fixpath(
44+
filename::AbstractString;
45+
badpath = basebuilddir,
46+
goodpath = basepath("..")
47+
)
48+
startswith(filename, badpath) || return fullpath(normpath(filename)) # NOTE: `fullpath` added when adapted
49+
filec = filename
50+
relfilename = relpath(filename, badpath)
51+
relfilename0 = relfilename
52+
for strippath in (joinpath("usr", "share", "julia"),)
53+
if startswith(relfilename, strippath)
54+
relfilename = relpath(relfilename, strippath)
55+
if occursin("stdlib", relfilename0) && !occursin("stdlib", relfilename)
56+
relfilename = joinpath("stdlib", relfilename)
57+
end
58+
end
59+
end
60+
ffilename = normpath(joinpath(goodpath, relfilename))
61+
if (isfile(filename) & !isfile(ffilename))
62+
ffilename = normpath(filename)
63+
end
64+
fullpath(ffilename) # NOTE: `fullpath` added when adapted
65+
end
66+
67+
"""
68+
basebuilddir
69+
70+
Julia's top-level directory when Julia was built, as recorded by the entries in
71+
`Base._included_files`.
72+
"""
73+
const basebuilddir = let # NOTE: changed from `begin` to `let` when adapted
74+
sysimg = filter(x -> endswith(x[2], "sysimg.jl"), Base._included_files)[1][2]
75+
dirname(dirname(sysimg))
76+
end
77+
78+
use_compiled_modules() = Base.JLOptions().use_compiled_modules != 0
79+
80+
# For tracking Julia's own stdlibs
81+
const stdlib_names = Set([
82+
:Base64,
83+
:CRC32c,
84+
:Dates,
85+
:DelimitedFiles,
86+
:Distributed,
87+
:FileWatching,
88+
:Future,
89+
:InteractiveUtils,
90+
:Libdl,
91+
:LibGit2,
92+
:LinearAlgebra,
93+
:Logging,
94+
:Markdown,
95+
:Mmap,
96+
:OldPkg,
97+
:Pkg,
98+
:Printf,
99+
:Profile,
100+
:Random,
101+
:REPL,
102+
:Serialization,
103+
:SHA,
104+
:SharedArrays,
105+
:Sockets,
106+
:SparseArrays,
107+
:Statistics,
108+
:SuiteSparse,
109+
:Test,
110+
:Unicode,
111+
:UUIDs,
112+
])
113+
114+
function pkg_fileinfo(id::PkgId)
115+
uuid, name = id.uuid, id.name
116+
# Try to find the matching cache file
117+
paths = Base.find_all_in_cache_path(id)
118+
sourcepath = Base.locate_package(id)
119+
for path in paths
120+
Base.stale_cachefile(sourcepath, path) === true && continue
121+
provides, includes_requires = parse_cache_header(path)
122+
mods_files_mtimes, _ = includes_requires
123+
for (pkgid, buildid) in provides
124+
if pkgid.uuid === uuid && pkgid.name == name
125+
return path, mods_files_mtimes
126+
end
127+
end
128+
end
129+
return nothing, nothing
130+
end
131+
132+
# A near-copy of the same method in `base/loading.jl`. However, this retains the full module path to the file.
133+
function parse_cache_header(f::IO)
134+
modules = Vector{Pair{PkgId,UInt64}}()
135+
while true
136+
n = read(f, Int32)
137+
n == 0 && break
138+
sym = String(read(f, n)) # module name
139+
uuid = UUID((read(f, UInt64), read(f, UInt64))) # pkg UUID
140+
build_id = read(f, UInt64) # build UUID (mostly just a timestamp)
141+
push!(modules, PkgId(uuid, sym) => build_id)
142+
end
143+
totbytes = read(f, Int64) # total bytes for file dependencies
144+
# read the list of requirements
145+
# and split the list into include and requires statements
146+
includes = Tuple{Module,String,Float64}[]
147+
requires = Pair{Module,PkgId}[]
148+
while true
149+
n2 = read(f, Int32)
150+
n2 == 0 && break
151+
depname = String(read(f, n2))
152+
mtime = read(f, Float64)
153+
n1 = read(f, Int32)
154+
mod = (n1 == 0) ? Main : Base.root_module(modules[n1].first)
155+
if n1 != 0
156+
# determine the complete module path
157+
while true
158+
n1 = read(f, Int32)
159+
totbytes -= 4
160+
n1 == 0 && break
161+
submodname = String(read(f, n1))
162+
mod = getfield(mod, Symbol(submodname))
163+
totbytes -= n1
164+
end
165+
end
166+
if depname[1] != '\0'
167+
push!(includes, (mod, depname, mtime))
168+
end
169+
totbytes -= 4 + 4 + n2 + 8
170+
end
171+
@assert totbytes == 12 "header of cache file appears to be corrupt"
172+
return modules, (includes, requires)
173+
end
174+
175+
function parse_cache_header(cachefile::String)
176+
io = open(cachefile, "r")
177+
try
178+
!Base.isvalid_cache_header(io) && throw(ArgumentError("Invalid header in cache file $cachefile."))
179+
return parse_cache_header(io)
180+
finally
181+
close(io)
182+
end
183+
end
184+
185+
# CSTParser-based approach
186+
# ------------------------
187+
188+
"""
189+
included_files = modulefiles(entrypath::String)::Vector{String}
190+
191+
Returns all the files that can be reached via [`include`](@ref) calls from `entrypath`.
192+
Note this function currently only looks for static toplevel calls (i.e. miss the calls
193+
in not in toplevel scope), and can include files in the submodules as well.
194+
"""
195+
function modulefiles(entrypath::String, files = Vector{String}())
196+
isfile′(entrypath) || return files
197+
198+
push!(files, entrypath)
199+
200+
text = read(entrypath, String)
201+
parsed = CSTParser.parse(text, true)
202+
items = toplevelitems(parsed, text)
203+
204+
for item in items
205+
if item isa ToplevelCall
206+
expr = item.expr
207+
if isinclude(expr)
208+
nextfile = expr.args[3].val
209+
nextentrypath = joinpath(dirname(entrypath), nextfile)
210+
isfile(nextentrypath) || continue
211+
modulefiles(nextentrypath, files)
212+
end
213+
end
214+
end
215+
216+
return files
217+
end
218+
219+
220+
#=
221+
find entry file of a module
222+
=#
223+
224+
"""
225+
entrypath, line = moduledefinition(mod::Module)
226+
227+
Returns an entry file of `mod`, and its definition line.
228+
229+
!!! note
230+
This function works for non-precompiled packages.
231+
"""
232+
function moduledefinition(mod::Module) # NOTE: added when adapted
233+
evalmethod = first(methods(getfield(mod, :eval)))
234+
parentfile = String(evalmethod.file)
235+
line = evalmethod.line
236+
id = Base.PkgId(mod)
237+
if id.name == "Base" || id.name == "Core" || Symbol(id.name) stdlib_names # NOTE: "Core" is added when adapted
238+
parentfile = normpath(Base.find_source_file(parentfile))
239+
end
240+
fixpath(parentfile), line
241+
end
242+
243+
244+
#=
245+
auto-module detection for the current file
246+
247+
# TODO:
248+
# use the same logics as finding module files for this auto-module detection
249+
# ref: https://github.com/JunoLab/Juno.jl/issues/411
250+
=#
251+
252+
using CodeTools, LNR
253+
254+
LNR.cursor(data::AbstractDict) = cursor(data["row"], data["column"])
255+
256+
function modulenames(data, pos)
257+
main = haskey(data, "module") ? data["module"] :
258+
haskey(data, "path") ? CodeTools.filemodule(data["path"]) :
259+
"Main"
260+
main == "" && (main = "Main")
261+
sub = CodeTools.codemodule(data["code"], pos)
262+
main, sub
263+
end
264+
265+
# keeps the latest file that has been used for Main module scope
266+
const MAIN_MODULE_LOCATION = Ref{Tuple{String, Int}}(moduledefinition(Main))
267+
268+
handle("module") do data
269+
main, sub = modulenames(data, cursor(data))
270+
271+
mod = CodeTools.getmodule(main)
272+
smod = CodeTools.getmodule(mod, sub)
273+
274+
if main == "Main" && sub == ""
275+
MAIN_MODULE_LOCATION[] = get!(data, "path", ""), data["row"]
276+
end
277+
278+
return d(:main => main,
279+
:sub => sub,
280+
:inactive => (mod==nothing),
281+
:subInactive => smod==nothing)
282+
end
283+
284+
285+
#=
286+
find all modules
287+
=#
288+
289+
handle("allmodules") do
290+
sort!([string(m) for m in CodeTools.allchildren(Main)])
291+
end

0 commit comments

Comments
 (0)