Correctly generate function from user input


I’m trying to put together a module for rapid fitting of data. I work in an experimental physics lab and want to make a tool that my collegues and I can use to bang out fits of large data sets efficiently.

Ideally I want something of the form:

fit(xdata, ydata, "sin(a * x) + b", "a = 1, b = 1")

Importantly, I want to just be able to pass a string (or expression) that defines the function, as well as the parameters of interest. These two inputs would eventually be turned into a function of the form:

 func(x, p) =  sin.(a * p[1]) .+ p[2])

to be handled by LsqFit for the actual fitting.

My initial attempt at this was the following set of functions:

function parseFunc(fstring::String, params::Vector{Symbol})
    ex = Meta.parse(fstring)
    repExp!(ex, params)
    ex = Meta.parse("@. (x,p) -> $ex")
    return eval(ex)

function repExp!(ex::Expr, params::Vector{Symbol})
    for (i, arg) in enumerate(ex.args)
        if arg ∈ params
            pindex = findfirst(isequal(arg), params)
            ex.args[i] = Meta.parse("p[$pindex]")
        elseif arg isa Expr
            repExp!(arg, params)

However, this lead me to the world age issue that has been discussed multiple times. I understand that I can use Base.invokelatest() but this makes the actual running of the function much slower, and since it has to be evaluated several times over a large data set, this is unnacceptable.

I’ve made several attemps at working around this, but haven’t come up with anything productive. Does anyone have an idea of how I can get this to work in a similar manner of input?

Generally, I’m not worried about safety of the input functions, since the only people using this code will be physicists, but as not all my colleagues use Julia, I very much want the interface of this module to require as close to plain text input of the function as possible.

I’ve already read through the following threads, and while giving solid advice, I feel that they don’t entirely apply to my situation, and have yet to tease out a working solution from the information they provide…