-- https://github.com/hishamhm/f-strings

local F = {}

local load = load

if _VERSION == "Lua 5.1" then
    load = function(code, name, _, env)
        local fn, err = loadstring(code, name)
        if fn then
            setfenv(fn, env)
            return fn
        end
        return nil, err
    end
end

local function scan_using(scanner, arg, searched)
    local i = 1
    repeat
        local name, value = scanner(arg, i)
        if name == searched then
            return true, value
        end
        i = i + 1
    until name == nil
    return false
end

local function snd(_, b)
    return b
end

local function format(_, str)
    local outer_env = _ENV and
                        (snd(scan_using(debug.getlocal, 3, "_ENV")) or
                        snd(scan_using(debug.getupvalue, debug.getinfo(2, "f").func, "_ENV")) or _ENV) or
                        getfenv(2)
    return (str:gsub("%b{}", function(block)
        local code, fmt = block:match("{(.*):(%%.*)}")
        code = code or block:match("{(.*)}")
        local exp_env = {}
        setmetatable(exp_env, {
            __index = function(_, k)
                local level = 6
                while true do
                    local funcInfo = debug.getinfo(level, "f")
                    if not funcInfo then
                        break
                    end
                    local ok, value = scan_using(debug.getupvalue, funcInfo.func, k)
                    if ok then
                        return value
                    end
                    ok, value = scan_using(debug.getlocal, level + 1, k)
                    if ok then
                        return value
                    end
                    level = level + 1
                end
                return rawget(outer_env, k)
            end,
        })
        local fn, err = load("return " .. code, "expression `" .. code .. "`", "t", exp_env)
        if fn then
            return fmt and string.format(fmt, fn()) or tostring(fn())
        else
            error(err, 0)
        end
    end))
end

setmetatable(F, {
    __call = format,
})

return F
