# This file is a part of Julia. License is MIT: https://julialang.org/license ## client.jl - frontend handling command line options, environment setup, ## and REPL const text_colors = AnyDict( :black => "\033[30m", :red => "\033[31m", :green => "\033[32m", :yellow => "\033[33m", :blue => "\033[34m", :magenta => "\033[35m", :cyan => "\033[36m", :white => "\033[37m", :light_black => "\033[90m", # gray :light_red => "\033[91m", :light_green => "\033[92m", :light_yellow => "\033[93m", :light_blue => "\033[94m", :light_magenta => "\033[95m", :light_cyan => "\033[96m", :normal => "\033[0m", :default => "\033[39m", :bold => "\033[1m", :underline => "\033[4m", :nothing => "", ) for i in 0:255 text_colors[i] = "\033[38;5;$(i)m" end const disable_text_style = AnyDict( :bold => "\033[22m", :underline => "\033[24m", :normal => "", :default => "", ) # Create a docstring with an automatically generated list # of colors. available_text_colors = collect(Iterators.filter(x -> !isa(x, Integer), keys(text_colors))) const possible_formatting_symbols = [:normal, :bold, :default] available_text_colors = cat(1, sort!(intersect(available_text_colors, possible_formatting_symbols), rev=true), sort!(setdiff( available_text_colors, possible_formatting_symbols))) const available_text_colors_docstring = string(join([string("`:", key,"`") for key in available_text_colors], ",\n", ", or \n")) """Dictionary of color codes for the terminal. Available colors are: $available_text_colors_docstring as well as the integers 0 to 255 inclusive. The color `:default` will print text in the default color while the color `:normal` will print text with all text properties (like boldness) reset. Printing with the color `:nothing` will print the string without modifications. """ text_colors have_color = false default_color_warn = :yellow default_color_error = :light_red default_color_info = :cyan default_color_input = :normal default_color_answer = :normal color_normal = text_colors[:normal] function repl_color(key, default) env_str = get(ENV, key, "") c = tryparse(Int, env_str) c_conv = isnull(c) ? Symbol(env_str) : get(c) haskey(text_colors, c_conv) ? c_conv : default end error_color() = repl_color("JULIA_ERROR_COLOR", default_color_error) warn_color() = repl_color("JULIA_WARN_COLOR" , default_color_warn) info_color() = repl_color("JULIA_INFO_COLOR" , default_color_info) input_color() = text_colors[repl_color("JULIA_INPUT_COLOR", default_color_input)] answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answer)] stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bold) stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold) function repl_cmd(cmd, out) shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh"))) shell_name = Base.basename(shell[1]) if isempty(cmd.exec) throw(ArgumentError("no cmd to execute")) elseif cmd.exec[1] == "cd" new_oldpwd = pwd() if length(cmd.exec) > 2 throw(ArgumentError("cd method only takes one argument")) elseif length(cmd.exec) == 2 dir = cmd.exec[2] if dir == "-" if !haskey(ENV, "OLDPWD") error("cd: OLDPWD not set") end cd(ENV["OLDPWD"]) else @static if !Sys.iswindows() # TODO: this is a rather expensive way to copy a string, remove? # If it's intended to simulate `cd`, it should instead be doing # more nearly `cd $dir && printf %s \$PWD` (with appropriate quoting), # since shell `cd` does more than just `echo` the result. dir = read(`$shell -c "printf %s $(shell_escape_posixly(dir))"`, String) end cd(dir) end else cd() end ENV["OLDPWD"] = new_oldpwd println(out, pwd()) else @static if !Sys.iswindows() if shell_name == "fish" shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end" else shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true" end cmd = `$shell` isa(STDIN, TTY) && (cmd = `$cmd -i`) cmd = `$cmd -c $shell_escape_cmd` end run(ignorestatus(cmd)) end nothing end function display_error(io::IO, er, bt) if !isempty(bt) st = stacktrace(bt) if !isempty(st) io = redirect(io, log_error_to, st[1]) end end print_with_color(Base.error_color(), io, "ERROR: "; bold = true) # remove REPL-related frames from interactive printing eval_ind = findlast(addr->Base.REPL.ip_matches_func(addr, :eval), bt) if eval_ind != 0 bt = bt[1:eval_ind-1] end showerror(IOContext(io, :limit => true), er, bt) println(io) end display_error(er, bt) = display_error(STDERR, er, bt) display_error(er) = display_error(er, []) function eval_user_input(@nospecialize(ast), show_value) errcount, lasterr, bt = 0, (), nothing while true try if have_color print(color_normal) end if errcount > 0 display_error(lasterr,bt) errcount, lasterr = 0, () else ast = Meta.lower(Main, ast) value = eval(Main, ast) eval(Main, Expr(:body, Expr(:(=), :ans, QuoteNode(value)), Expr(:return, nothing))) if !(value === nothing) && show_value if have_color print(answer_color()) end try eval(Main, Expr(:body, Expr(:return, Expr(:call, display, QuoteNode(value))))) catch err println(STDERR, "Evaluation succeeded, but an error occurred while showing value of type ", typeof(value), ":") rethrow(err) end println() end end break catch err if errcount > 0 println(STDERR, "SYSTEM: show(lasterr) caused an error") end errcount, lasterr = errcount+1, err if errcount > 2 println(STDERR, "WARNING: it is likely that something important is broken, and Julia will not be able to continue normally") break end bt = catch_backtrace() end end isa(STDIN,TTY) && println() end syntax_deprecation_warnings(warn::Bool) = ccall(:jl_parse_depwarn, Cint, (Cint,), warn) == 1 function syntax_deprecation_warnings(f::Function, warn::Bool) prev = syntax_deprecation_warnings(warn) try f() finally syntax_deprecation_warnings(prev) end end function parse_input_line(s::String; filename::String="none") # (expr, pos) = Meta.parse(s, 1) # (ex, pos) = ccall(:jl_parse_string, Any, # (Ptr{UInt8},Csize_t,Int32,Int32), # s, sizeof(s), pos-1, 1) # if ex!==() # throw(Meta.ParseError("extra input after end of expression")) # end # expr ex = ccall(:jl_parse_input_line, Any, (Ptr{UInt8}, Csize_t, Ptr{UInt8}, Csize_t), s, sizeof(s), filename, sizeof(filename)) if ex isa Symbol && all(equalto('_'), string(ex)) # remove with 0.7 deprecation Meta.lower(Main, ex) # to get possible warning about using _ as an rvalue end return ex end parse_input_line(s::AbstractString) = parse_input_line(String(s)) function parse_input_line(io::IO) s = "" while !eof(io) s *= readline(io, chomp=false) e = parse_input_line(s) if !(isa(e,Expr) && e.head === :incomplete) return e end end end # detect the reason which caused an :incomplete expression # from the error message # NOTE: the error messages are defined in src/julia-parser.scm incomplete_tag(ex) = :none function incomplete_tag(ex::Expr) Meta.isexpr(ex, :incomplete) || return :none msg = ex.args[1] contains(msg, "string") && return :string contains(msg, "comment") && return :comment contains(msg, "requires end") && return :block contains(msg, "\"`\"") && return :cmd contains(msg, "character") && return :char return :other end # try to include() a file, ignoring if not found try_include(mod::Module, path::AbstractString) = isfile(path) && include(mod, path) function process_options(opts::JLOptions) if !isempty(ARGS) idxs = find(x -> x == "--", ARGS) length(idxs) > 0 && deleteat!(ARGS, idxs[1]) end quiet = (opts.quiet != 0) startup = (opts.startupfile != 2) history_file = (opts.historyfile != 0) color_set = (opts.color != 0) global have_color = (opts.color == 1) global is_interactive = (opts.isinteractive != 0) # pre-process command line argument list arg_is_program = !isempty(ARGS) repl = !arg_is_program cmds = unsafe_load_commands(opts.commands) for (cmd, arg) in cmds if cmd == 'e' arg_is_program = false repl = false elseif cmd == 'E' arg_is_program = false repl = false elseif cmd == 'L' # nothing else warn("unexpected command -$cmd'$arg'") end end # remove filename from ARGS global PROGRAM_FILE = arg_is_program ? shift!(ARGS) : "" # startup worker. # opts.startupfile, opts.load, etc should should not be processed for workers. if opts.worker == 1 # does not return if opts.cookie != C_NULL start_worker(unsafe_string(opts.cookie)) else start_worker() end end # add processors if opts.nprocs > 0 addprocs(opts.nprocs) end # load processes from machine file if opts.machinefile != C_NULL addprocs(load_machine_file(unsafe_string(opts.machinefile))) end # load ~/.juliarc file startup && load_juliarc() # process cmds list for (cmd, arg) in cmds if cmd == 'e' eval(Main, parse_input_line(arg)) elseif cmd == 'E' invokelatest(show, eval(Main, parse_input_line(arg))) println() elseif cmd == 'L' # load file immediately on all processors @sync for p in procs() @async remotecall_wait(include, p, Main, arg) end end end # load file if arg_is_program # program if !is_interactive ccall(:jl_exit_on_sigint, Void, (Cint,), 1) end include(Main, PROGRAM_FILE) end repl |= is_interactive return (quiet, repl, startup, color_set, history_file) end function load_juliarc() # If the user built us with a specific Base.SYSCONFDIR, check that location first for a juliarc.jl file # If it is not found, then continue on to the relative path based on JULIA_HOME if !isempty(Base.SYSCONFDIR) && isfile(joinpath(JULIA_HOME, Base.SYSCONFDIR, "julia", "juliarc.jl")) include(Main, abspath(JULIA_HOME, Base.SYSCONFDIR, "julia", "juliarc.jl")) else try_include(Main, abspath(JULIA_HOME, "..", "etc", "julia", "juliarc.jl")) end try_include(Main, abspath(homedir(), ".juliarc.jl")) nothing end function load_machine_file(path::AbstractString) machines = [] for line in split(read(path, String),'\n'; keep=false) s = split(line, '*'; keep = false) map!(strip, s, s) if length(s) > 1 cnt = isnumber(s[1]) ? parse(Int,s[1]) : Symbol(s[1]) push!(machines,(s[2], cnt)) else push!(machines,line) end end return machines end import .Terminals import .REPL const repl_hooks = [] """ atreplinit(f) Register a one-argument function to be called before the REPL interface is initialized in interactive sessions; this is useful to customize the interface. The argument of `f` is the REPL object. This function should be called from within the `.juliarc.jl` initialization file. """ atreplinit(f::Function) = (unshift!(repl_hooks, f); nothing) function __atreplinit(repl) for f in repl_hooks try f(repl) catch err showerror(STDERR, err) println(STDERR) end end end _atreplinit(repl) = invokelatest(__atreplinit, repl) function _start() empty!(ARGS) append!(ARGS, Core.ARGS) opts = JLOptions() @eval Main using Base.MainInclude try (quiet,repl,startup,color_set,history_file) = process_options(opts) banner = opts.banner == 1 local term global active_repl global active_repl_backend if repl if !isa(STDIN,TTY) global is_interactive |= !isa(STDIN, Union{File, IOStream}) banner |= opts.banner != 0 && is_interactive color_set || (global have_color = false) else term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb") term = Terminals.TTYTerminal(term_env, STDIN, STDOUT, STDERR) global is_interactive = true banner |= opts.banner != 0 color_set || (global have_color = Terminals.hascolor(term)) banner && REPL.banner(term,term) if term.term_type == "dumb" active_repl = REPL.BasicREPL(term) quiet || warn("Terminal not fully functional") else active_repl = REPL.LineEditREPL(term, true) active_repl.history_file = history_file active_repl.hascolor = have_color end # Make sure any displays pushed in .juliarc.jl ends up above the # REPLDisplay pushdisplay(REPL.REPLDisplay(active_repl)) end else banner |= opts.banner != 0 && is_interactive end if repl if !isa(STDIN,TTY) # note: currently IOStream is used for file STDIN if isa(STDIN,File) || isa(STDIN,IOStream) # reading from a file, behave like include eval(Main,parse_input_line(read(STDIN, String))) else # otherwise behave repl-like while !eof(STDIN) eval_user_input(parse_input_line(STDIN), true) end end else _atreplinit(active_repl) REPL.run_repl(active_repl, backend->(global active_repl_backend = backend)) end end catch err eval(Main, Expr(:body, Expr(:return, Expr(:call, Base.display_error, QuoteNode(err), catch_backtrace())))) exit(1) end if is_interactive && have_color print(color_normal) end end