How to read a string from a file with interpolation

The file “test.txt” contains a single line:

I am $me

The following is intended to simply read the string and store the interpolated result in str:

me = "Sam"
str = read("test.txt", String);
str == "I am Sam"
false

I do not know, but I have some premptive follow up questions for whoever does know.

At what point is string interpolation processed? Is it on string creation, string literal parsing, or at some other point?

My intuition would be when the actual string literal is parsed. Is there a way to force string interpolation on a string created from a non-literal?

parsing any arbitrary code is probably not a good idea

what about using a Dict and replace:

vars = Dict("me" => "Sam", "you" => "Max")
str = "I am \$me"
for (var, val) in vars
    str = replace(str, "\$"*var => val)
end

although this would cause problems if a variable name contains another variable name (eg if you have $me and $metoo)

2 Likes

If all your variables are in global scope, and you don’t care that much about performance, you can actually implement interpolation yourself relatively easily and then use eval:

if isdefined(Meta, :parseatom)
    const parseatom = Meta.parseatom
else
    parseatom(s, i; filename=nothing) = Meta.parse(s, i; greedy=false)
end

function parse_interpolation(s, filename)
    i = firstindex(s)
    buf = IOBuffer(maxsize=ncodeunits(s))
    ex = Expr(:string)
    while i <= ncodeunits(s)
        c = @inbounds s[i]
        i = nextind(s, i)
        if c === '$'
            position(buf) > 0 && push!(ex.args, String(take!(buf)))
            atom, i = parseatom(s, i; filename)
            Meta.isexpr(atom, :incomplete) && error(atom.args[1])
            atom !== nothing && push!(ex.args, atom)
        else
            print(buf, c)
        end
    end
    position(buf) > 0 && push!(ex.args, String(take!(buf)))
    return ex
end
julia> open("test.txt", "w") do io
           print(io, raw"I am $me")
       end

julia> me = "Sam"
"Sam"

julia> str = eval(parse_interpolation(read("test.txt", String), "test.txt"))
"I am Sam"

Of course, you are executing arbitrary code from a text file, so you definitely want to be careful with this.

3 Likes

Your intuition is right, regular string interpolation is processed inside Julia’s parser, so "a $b c" parses as Expr(:string, "a ", :b, " c"). This then gets transformed to Base.string("a ", b, " c") in lowering.
If you want to do string interpolation programmatically, you need to implement the parsing yourself, like in my answer above, but that only gives you the expression tree, not the final string. Usually, this is implemented inside macros, so a macro could simply return this expression tree (escaped with esc), and interpolation would happen inside the macro caller’s scope. You can of course also use eval, like I did above, but that will only work in global scope and will be quite slow, because everything is executed at runtime.

3 Likes

Unless you need the full power of $ (for all valid Julia expressions), you may be interested in

https://github.com/jverzani/Mustache.jl

4 Likes

It’s unfortunate that only one solution can be marked within Discourse

Thanks, this worked perfectly!