How to embed Julia in interactive mode

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; 
}

Result:

false

Suggests welcome.

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.

You can about the initialization of the Julia CLI here:
https://docs.julialang.org/en/v1/devdocs/init/

Basically it goes through the following files:

The simplest approach is just to Base._start().

#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:

  1. Create “test.jl” with the contents “S=0;for i in 1:4 S+=i; end”
  2. 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

The source for softscope is below.

1 Like

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
1 Like