Another in my continuing series of questions about how to embed Julia… @mkitti and @GunnarFarneback have been very helpful in the past.
I am embedding Julia in another program and am trying to approximately simulate the REPL. To this end I would like to launch Julia in interactive mode. My understanding is that this would affect, for example, the behavior of soft scoping.
However, when I launch it after passing the -i option, isinteractive() still returns false. Here’s my code, in Windows:
#include <julia.h>
int main(int argc, char* argv[])
{
int ac = 2;
char** av = (char**)malloc(sizeof(char*) * 2);
av[0] = 0;
av[1] = (char*)"-i";
jl_parse_opts(&ac, &av);
jl_init();
jl_eval_string("print(isinteractive())");
jl_atexit_hook(0);
return 0;
}
I get the sense that wanting to embed Julia and run the REPL is not a fully anticipated scenario. There is no a straightforward API to do just that. However we can trace the initialization of Julia through the normal path.
#include <julia.h>
int main(int argc, char* argv[])
{
int ac = 2;
char** av = (char**)malloc(sizeof(char*) * 2);
av[0] = 0;
av[1] = (char*)"-i";
jl_parse_opts(&ac, &av);
jl_init();
jl_eval_string("Base._start()");
jl_atexit_hook(0);
return 0;
}
I compile the above with julia usr/share/julia/julia-config.jl --allflags | xargs gcc interactive_embed.c -o interactive_embed
The more complicated answer is that you may want to emulate true_main:
The other thing is that the leading underscore suggests we should not call Base._start() directly. Perhaps we should call Base.exec_options(Base.JLOptions()) directly instead?
As you can see on Line 234 that’s where the Base.is_interactive global is set which is what Base.isinteractive() returns.
Thank you for that helpful effort. Passing Base.exec_options(Base.JLOptions()) after jl_init() worked well enough for me to learn that this will not do what I want, alas.
I think the MWE for my new problem is:
Create “test.jl” with the contents “S=0;for i in 1:4 S+=i; end”
Do “julia -i test.jl” in a shell.
In Windows I get
┌ Warning: Assignment to `S` in soft scope is ambiguous because a global variable by the same name exists: `S` will be treated as a new local. Disambiguate by using `local S` to suppress this warning or `global S` to assign to the existing global variable.
└ @ D:\OneDrive\Documents\Macros\julia.ado\test.jl:1
ERROR: LoadError: UndefVarError: `S` not defined
Stacktrace:
[1] top-level scope
@ D:\OneDrive\Documents\Macros\julia.ado\test.jl:1
in expression starting at D:\OneDrive\Documents\Macros\julia.ado\test.jl:1
_
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.10.2 (2024-03-01)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
So it runs test.jl non-interactively, gives me an error, and then fires up a REPL. Obviously not what I’m after. And I don’t see a good way to fix this.
I think with effort, in order to create a REPL experience with embedded Julia, I could figure out how to hook more directly into the Julia REPL code. But maybe not now…
Ah, now I understand your question. You are not interested in the REPL, just the softscope. The softscope is an abstract syntax tree transform (AST) that is in the REPL backend.
julia> Base.active_repl_backend.ast_transforms[1] softscope (generic function with 1 method)
julia> const softscope = Base.active_repl_backend.ast_transforms[1]
softscope (generic function with 1 method)
julia> @which softscope(:())
softscope(ex)
@ REPL ~/julia-1.10.0/share/julia/stdlib/v1.10/REPL/src/REPL.jl:105
julia> using REPL
julia> REPL.softscope
softscope (generic function with 1 method)
julia> REPL.softscope == softscope
true
So we can pop it off the AST transform stack.
julia> Base.active_repl_backend.ast_transforms |> pop!
softscope (generic function with 1 method)
julia> begin
b = 0
for i in 1:10
b += i
end
end
┌ Warning: Assignment to `b` in soft scope is ambiguous because a global variable by the same name exists: `b` will be treated as a new local. Disambiguate by using `local b` to suppress this warning or `global b` to assign to the existing global variable.
└ @ REPL[50]:4
ERROR: UndefVarError: `b` not defined
Stacktrace:
[1] top-level scope
@ ./REPL[50]:4
If we push it back, then we get softscope again.
julia> push!(Base.active_repl_backend.ast_transforms, softscope)
1-element Vector{Any}:
softscope (generic function with 1 method)
julia> begin
a = 0
for i in 1:10
a += i
end
end
julia> a
55
You can apply the function manually.
julia> ex = :(begin
a = 0
for i in 1:10
a += i
end
end);
julia> REPL.softscope(ex)
quote
$(Expr(:softscope, true))
begin
#= REPL[60]:2 =#
a = 0
#= REPL[60]:3 =#
for i = 1:10
#= REPL[60]:4 =#
a += i
#= REPL[60]:5 =#
end
end
end
julia> eval(softscope(ex))
julia> a
55
julia> eval(ex)
┌ Warning: Assignment to `a` in soft scope is ambiguous because a global variable by the same name exists: `a` will be treated as a new local. Disambiguate by using `local a` to suppress this warning or `global a` to assign to the existing global variable.
└ @ REPL[60]:4
ERROR: UndefVarError: `a` not defined
Stacktrace:
[1] top-level scope
@ ./REPL[60]:4
[2] eval
@ Core ./boot.jl:385 [inlined]
[3] eval(x::Expr)
@ Base.MainInclude ./client.jl:491
[4] top-level scope
@ REPL[62]:1
Amazing! It seems that when embedding, Base.active_repl_backend is not defined.
BUT I can use softscope() to do what I need. Behind the scenes I now wrap strings passed to jl_eval_string() with eval(Meta.parse(REPL.softscope(...))).Here’s an actual session in Stata using the revised Julia plugin.
. jl
------------------------------------------------ Julia (type exit() to exit) ----------------------------------------------------------------------------------------------------------------
jl> . S = 0
0
jl> . for i in 1:4
... S += i
... end
jl> . S
10