Line | Exclusive | Inclusive | Code |
---|---|---|---|
1 | # This file is a part of Julia. License is MIT: https://julialang.org/license | ||
2 | |||
3 | ## client.jl - frontend handling command line options, environment setup, | ||
4 | ## and REPL | ||
5 | |||
6 | have_color = false | ||
7 | default_color_warn = :yellow | ||
8 | default_color_error = :light_red | ||
9 | default_color_info = :cyan | ||
10 | default_color_debug = :blue | ||
11 | default_color_input = :normal | ||
12 | default_color_answer = :normal | ||
13 | color_normal = text_colors[:normal] | ||
14 | |||
15 | function repl_color(key, default) | ||
16 | env_str = get(ENV, key, "") | ||
17 | c = tryparse(Int, env_str) | ||
18 | c_conv = something(c, Symbol(env_str)) | ||
19 | haskey(text_colors, c_conv) ? c_conv : default | ||
20 | end | ||
21 | |||
22 | error_color() = repl_color("JULIA_ERROR_COLOR", default_color_error) | ||
23 | warn_color() = repl_color("JULIA_WARN_COLOR" , default_color_warn) | ||
24 | info_color() = repl_color("JULIA_INFO_COLOR" , default_color_info) | ||
25 | debug_color() = repl_color("JULIA_DEBUG_COLOR" , default_color_debug) | ||
26 | |||
27 | input_color() = text_colors[repl_color("JULIA_INPUT_COLOR", default_color_input)] | ||
28 | answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answer)] | ||
29 | |||
30 | stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bold) | ||
31 | stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold) | ||
32 | |||
33 | function repl_cmd(cmd, out) | ||
34 | shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh"))) | ||
35 | shell_name = Base.basename(shell[1]) | ||
36 | |||
37 | # Immediately expand all arguments, so that typing e.g. ~/bin/foo works. | ||
38 | cmd.exec .= expanduser.(cmd.exec) | ||
39 | |||
40 | if isempty(cmd.exec) | ||
41 | throw(ArgumentError("no cmd to execute")) | ||
42 | elseif cmd.exec[1] == "cd" | ||
43 | new_oldpwd = pwd() | ||
44 | if length(cmd.exec) > 2 | ||
45 | throw(ArgumentError("cd method only takes one argument")) | ||
46 | elseif length(cmd.exec) == 2 | ||
47 | dir = cmd.exec[2] | ||
48 | if dir == "-" | ||
49 | if !haskey(ENV, "OLDPWD") | ||
50 | error("cd: OLDPWD not set") | ||
51 | end | ||
52 | cd(ENV["OLDPWD"]) | ||
53 | else | ||
54 | @static if !Sys.iswindows() | ||
55 | # TODO: this is a rather expensive way to copy a string, remove? | ||
56 | # If it's intended to simulate `cd`, it should instead be doing | ||
57 | # more nearly `cd $dir && printf %s \$PWD` (with appropriate quoting), | ||
58 | # since shell `cd` does more than just `echo` the result. | ||
59 | dir = read(`$shell -c "printf '%s' $(shell_escape_posixly(dir))"`, String) | ||
60 | end | ||
61 | cd(dir) | ||
62 | end | ||
63 | else | ||
64 | cd() | ||
65 | end | ||
66 | ENV["OLDPWD"] = new_oldpwd | ||
67 | println(out, pwd()) | ||
68 | else | ||
69 | @static if !Sys.iswindows() | ||
70 | if shell_name == "fish" | ||
71 | shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" | ||
72 | else | ||
73 | shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true" | ||
74 | end | ||
75 | cmd = `$shell -c $shell_escape_cmd` | ||
76 | end | ||
77 | run(ignorestatus(cmd)) | ||
78 | end | ||
79 | nothing | ||
80 | end | ||
81 | |||
82 | function ip_matches_func(ip, func::Symbol) | ||
83 | for fr in StackTraces.lookup(ip) | ||
84 | if fr === StackTraces.UNKNOWN || fr.from_c | ||
85 | return false | ||
86 | end | ||
87 | fr.func === func && return true | ||
88 | end | ||
89 | return false | ||
90 | end | ||
91 | |||
92 | function display_error(io::IO, er, bt) | ||
93 | printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) | ||
94 | # remove REPL-related frames from interactive printing | ||
95 | eval_ind = findlast(addr->ip_matches_func(addr, :eval), bt) | ||
96 | if eval_ind !== nothing | ||
97 | bt = bt[1:eval_ind-1] | ||
98 | end | ||
99 | showerror(IOContext(io, :limit => true), er, bt) | ||
100 | println(io) | ||
101 | end | ||
102 | display_error(er, bt) = display_error(stderr, er, bt) | ||
103 | display_error(er) = display_error(er, []) | ||
104 | |||
105 | function eval_user_input(@nospecialize(ast), show_value::Bool) | ||
106 | errcount, lasterr, bt = 0, (), nothing | ||
107 | while true | ||
108 | try | ||
109 | if have_color | ||
110 | print(color_normal) | ||
111 | end | ||
112 | if errcount > 0 | ||
113 | invokelatest(display_error, lasterr, bt) | ||
114 | errcount, lasterr = 0, () | ||
115 | else | ||
116 | ast = Meta.lower(Main, ast) | ||
117 | value = Core.eval(Main, ast) | ||
118 | ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, value) | ||
119 | if !(value === nothing) && show_value | ||
120 | if have_color | ||
121 | print(answer_color()) | ||
122 | end | ||
123 | try | ||
124 | invokelatest(display, value) | ||
125 | catch err | ||
126 | println(stderr, "Evaluation succeeded, but an error occurred while showing value of type ", typeof(value), ":") | ||
127 | rethrow(err) | ||
128 | end | ||
129 | println() | ||
130 | end | ||
131 | end | ||
132 | break | ||
133 | catch err | ||
134 | if errcount > 0 | ||
135 | println(stderr, "SYSTEM: show(lasterr) caused an error") | ||
136 | end | ||
137 | errcount, lasterr = errcount+1, err | ||
138 | if errcount > 2 | ||
139 | println(stderr, "WARNING: it is likely that something important is broken, and Julia will not be able to continue normally") | ||
140 | break | ||
141 | end | ||
142 | bt = catch_backtrace() | ||
143 | end | ||
144 | end | ||
145 | isa(stdin, TTY) && println() | ||
146 | nothing | ||
147 | end | ||
148 | |||
149 | function parse_input_line(s::String; filename::String="none", depwarn=true) | ||
150 | # For now, assume all parser warnings are depwarns | ||
151 | ex = if depwarn | ||
152 | ccall(:jl_parse_input_line, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t), | ||
153 | s, sizeof(s), filename, sizeof(filename)) | ||
154 | else | ||
155 | with_logger(NullLogger()) do | ||
156 | ccall(:jl_parse_input_line, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t), | ||
157 | s, sizeof(s), filename, sizeof(filename)) | ||
158 | end | ||
159 | end | ||
160 | return ex | ||
161 | end | ||
162 | parse_input_line(s::AbstractString) = parse_input_line(String(s)) | ||
163 | |||
164 | function parse_input_line(io::IO) | ||
165 | s = "" | ||
166 | while !eof(io) | ||
167 | s *= readline(io, keep=true) | ||
168 | e = parse_input_line(s) | ||
169 | if !(isa(e,Expr) && e.head === :incomplete) | ||
170 | return e | ||
171 | end | ||
172 | end | ||
173 | end | ||
174 | |||
175 | # detect the reason which caused an :incomplete expression | ||
176 | # from the error message | ||
177 | # NOTE: the error messages are defined in src/julia-parser.scm | ||
178 | incomplete_tag(ex) = :none | ||
179 | function incomplete_tag(ex::Expr) | ||
180 | Meta.isexpr(ex, :incomplete) || return :none | ||
181 | msg = ex.args[1] | ||
182 | occursin("string", msg) && return :string | ||
183 | occursin("comment", msg) && return :comment | ||
184 | occursin("requires end", msg) && return :block | ||
185 | occursin("\"`\"", msg) && return :cmd | ||
186 | occursin("character", msg) && return :char | ||
187 | return :other | ||
188 | end | ||
189 | |||
190 | # call include() on a file, ignoring if not found | ||
191 | include_ifexists(mod::Module, path::AbstractString) = isfile(path) && include(mod, path) | ||
192 | |||
193 |
40 (95.24%) samples spent in exec_options
function exec_options(opts)
0 (ex.), 40 (100.00%) (incl.) when called from _start line 425 |
||
194 | if !isempty(ARGS) | ||
195 | idxs = findall(x -> x == "--", ARGS) | ||
196 | length(idxs) > 0 && deleteat!(ARGS, idxs[1]) | ||
197 | end | ||
198 | quiet = (opts.quiet != 0) | ||
199 | startup = (opts.startupfile != 2) | ||
200 | history_file = (opts.historyfile != 0) | ||
201 | color_set = (opts.color != 0) # --color!=auto | ||
202 | global have_color = (opts.color == 1) # --color=on | ||
203 | global is_interactive = (opts.isinteractive != 0) | ||
204 | |||
205 | # pre-process command line argument list | ||
206 | arg_is_program = !isempty(ARGS) | ||
207 | repl = !arg_is_program | ||
208 | cmds = unsafe_load_commands(opts.commands) | ||
209 | for (cmd, arg) in cmds | ||
210 | if cmd == 'e' | ||
211 | arg_is_program = false | ||
212 | repl = false | ||
213 | elseif cmd == 'E' | ||
214 | arg_is_program = false | ||
215 | repl = false | ||
216 | elseif cmd == 'L' | ||
217 | # nothing | ||
218 | else | ||
219 | @warn "Unexpected command -$cmd'$arg'" | ||
220 | end | ||
221 | end | ||
222 | |||
223 | # remove filename from ARGS | ||
224 | global PROGRAM_FILE = arg_is_program ? popfirst!(ARGS) : "" | ||
225 | |||
226 | # Load Distributed module only if any of the Distributed options have been specified. | ||
227 | distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL) | ||
228 | if distributed_mode | ||
229 | let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed")) | ||
230 | Core.eval(Main, :(const Distributed = $Distributed)) | ||
231 | Core.eval(Main, :(using .Distributed)) | ||
232 | end | ||
233 | |||
234 | invokelatest(Main.Distributed.process_opts, opts) | ||
235 | end | ||
236 | |||
237 | # load ~/.julia/config/startup.jl file | ||
238 | startup && load_julia_startup() | ||
239 | |||
240 | # process cmds list | ||
241 | for (cmd, arg) in cmds | ||
242 | if cmd == 'e' | ||
243 | Core.eval(Main, parse_input_line(arg)) | ||
244 | elseif cmd == 'E' | ||
245 | invokelatest(show, Core.eval(Main, parse_input_line(arg))) | ||
246 | println() | ||
247 | elseif cmd == 'L' | ||
248 | # load file immediately on all processors | ||
249 | if !distributed_mode | ||
250 | include(Main, arg) | ||
251 | else | ||
252 | # TODO: Move this logic to Distributed and use a callback | ||
253 | @sync for p in invokelatest(Main.procs) | ||
254 | @async invokelatest(Main.remotecall_wait, include, p, Main, arg) | ||
255 | end | ||
256 | end | ||
257 | end | ||
258 | end | ||
259 | |||
260 | # load file | ||
261 | if arg_is_program | ||
262 | # program | ||
263 | if !is_interactive | ||
264 | ccall(:jl_exit_on_sigint, Cvoid, (Cint,), 1) | ||
265 | end | ||
266 | 40 (95.24%) |
40 (100.00%)
samples spent calling
include
include(Main, PROGRAM_FILE)
|
|
267 | end | ||
268 | repl |= is_interactive | ||
269 | if repl | ||
270 | interactiveinput = isa(stdin, TTY) | ||
271 | if interactiveinput | ||
272 | global is_interactive = true | ||
273 | banner = (opts.banner != 0) # --banner!=no | ||
274 | else | ||
275 | banner = (opts.banner == 1) # --banner=yes | ||
276 | end | ||
277 | run_main_repl(interactiveinput, quiet, banner, history_file, color_set) | ||
278 | end | ||
279 | nothing | ||
280 | end | ||
281 | |||
282 | function load_julia_startup() | ||
283 | # If the user built us with a specific Base.SYSCONFDIR, check that location first for a startup.jl file | ||
284 | # If it is not found, then continue on to the relative path based on Sys.BINDIR | ||
285 | BINDIR = Sys.BINDIR::String | ||
286 | SYSCONFDIR = Base.SYSCONFDIR::String | ||
287 | if !isempty(SYSCONFDIR) && isfile(joinpath(BINDIR, SYSCONFDIR, "julia", "startup.jl")) | ||
288 | include(Main, abspath(BINDIR, SYSCONFDIR, "julia", "startup.jl")) | ||
289 | else | ||
290 | include_ifexists(Main, abspath(BINDIR, "..", "etc", "julia", "startup.jl")) | ||
291 | end | ||
292 | include_ifexists(Main, abspath(homedir(), ".julia", "config", "startup.jl")) | ||
293 | return nothing | ||
294 | end | ||
295 | |||
296 | const repl_hooks = [] | ||
297 | |||
298 | """ | ||
299 | atreplinit(f) | ||
300 | |||
301 | Register a one-argument function to be called before the REPL interface is initialized in | ||
302 | interactive sessions; this is useful to customize the interface. The argument of `f` is the | ||
303 | REPL object. This function should be called from within the `.julia/config/startup.jl` | ||
304 | initialization file. | ||
305 | """ | ||
306 | atreplinit(f::Function) = (pushfirst!(repl_hooks, f); nothing) | ||
307 | |||
308 | function __atreplinit(repl) | ||
309 | for f in repl_hooks | ||
310 | try | ||
311 | f(repl) | ||
312 | catch err | ||
313 | showerror(stderr, err) | ||
314 | println(stderr) | ||
315 | end | ||
316 | end | ||
317 | end | ||
318 | _atreplinit(repl) = invokelatest(__atreplinit, repl) | ||
319 | |||
320 | # The REPL stdlib hooks into Base using this Ref | ||
321 | const REPL_MODULE_REF = Ref{Module}() | ||
322 | |||
323 | # run the requested sort of evaluation loop on stdio | ||
324 | function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool) | ||
325 | global active_repl | ||
326 | # load interactive-only libraries | ||
327 | if !isdefined(Main, :InteractiveUtils) | ||
328 | try | ||
329 | let InteractiveUtils = require(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils")) | ||
330 | Core.eval(Main, :(const InteractiveUtils = $InteractiveUtils)) | ||
331 | Core.eval(Main, :(using .InteractiveUtils)) | ||
332 | end | ||
333 | catch ex | ||
334 | @warn "Failed to import InteractiveUtils into module Main" exception=(ex, catch_backtrace()) | ||
335 | end | ||
336 | end | ||
337 | |||
338 | if interactive && isassigned(REPL_MODULE_REF) | ||
339 | invokelatest(REPL_MODULE_REF[]) do REPL | ||
340 | term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") | ||
341 | term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr) | ||
342 | color_set || (global have_color = REPL.Terminals.hascolor(term)) | ||
343 | banner && Base.banner(term) | ||
344 | if term.term_type == "dumb" | ||
345 | active_repl = REPL.BasicREPL(term) | ||
346 | quiet || @warn "Terminal not fully functional" | ||
347 | else | ||
348 | active_repl = REPL.LineEditREPL(term, have_color, true) | ||
349 | active_repl.history_file = history_file | ||
350 | end | ||
351 | # Make sure any displays pushed in .julia/config/startup.jl ends up above the | ||
352 | # REPLDisplay | ||
353 | pushdisplay(REPL.REPLDisplay(active_repl)) | ||
354 | _atreplinit(active_repl) | ||
355 | REPL.run_repl(active_repl, backend->(global active_repl_backend = backend)) | ||
356 | end | ||
357 | else | ||
358 | # otherwise provide a simple fallback | ||
359 | if interactive && !quiet | ||
360 | @warn "REPL provider not available: using basic fallback" | ||
361 | end | ||
362 | banner && Base.banner() | ||
363 | let input = stdin | ||
364 | if isa(input, File) || isa(input, IOStream) | ||
365 | # for files, we can slurp in the whole thing at once | ||
366 | ex = parse_input_line(read(input, String)) | ||
367 | if Meta.isexpr(ex, :toplevel) | ||
368 | # if we get back a list of statements, eval them sequentially | ||
369 | # as if we had parsed them sequentially | ||
370 | for stmt in ex.args | ||
371 | eval_user_input(stmt, true) | ||
372 | end | ||
373 | body = ex.args | ||
374 | else | ||
375 | eval_user_input(ex, true) | ||
376 | end | ||
377 | else | ||
378 | while isopen(input) || !eof(input) | ||
379 | if interactive | ||
380 | print("julia> ") | ||
381 | flush(stdout) | ||
382 | end | ||
383 | eval_user_input(parse_input_line(input), true) | ||
384 | end | ||
385 | end | ||
386 | end | ||
387 | end | ||
388 | nothing | ||
389 | end | ||
390 | |||
391 | baremodule MainInclude | ||
392 | include(fname::AbstractString) = Main.Base.include(Main, fname) | ||
393 | eval(x) = Core.eval(Main, x) | ||
394 | end | ||
395 | |||
396 | """ | ||
397 | eval(expr) | ||
398 | |||
399 | Evaluate an expression in the global scope of the containing module. | ||
400 | Every `Module` (except those defined with `baremodule`) has its own 1-argument | ||
401 | definition of `eval`, which evaluates expressions in that module. | ||
402 | """ | ||
403 | MainInclude.eval | ||
404 | |||
405 | """ | ||
406 | include(path::AbstractString) | ||
407 | |||
408 | Evaluate the contents of the input source file in the global scope of the containing module. | ||
409 | Every module (except those defined with `baremodule`) has its own 1-argument | ||
410 | definition of `include`, which evaluates the file in that module. | ||
411 | Returns the result of the last evaluated expression of the input file. During including, | ||
412 | a task-local include path is set to the directory containing the file. Nested calls to | ||
413 | `include` will search relative to that path. This function is typically used to load source | ||
414 | interactively, or to combine files in packages that are broken into multiple source files. | ||
415 | |||
416 | Use [`Base.include`](@ref) to evaluate a file into another module. | ||
417 | """ | ||
418 | MainInclude.include | ||
419 | |||
420 | function _start() | ||
421 | empty!(ARGS) | ||
422 | append!(ARGS, Core.ARGS) | ||
423 | @eval Main import Base.MainInclude: eval, include | ||
424 | try | ||
425 | 40 (95.24%) |
40 (100.00%)
samples spent calling
exec_options
exec_options(JLOptions())
|
|
426 | catch err | ||
427 | invokelatest(display_error, err, catch_backtrace()) | ||
428 | exit(1) | ||
429 | end | ||
430 | if is_interactive && have_color | ||
431 | print(color_normal) | ||
432 | end | ||
433 | end |