宏和字符串插值(Julia)

10

假设我创建了这个简单的字符串宏

macro e_str(s)
    return string("I touched this: ",s)
end

如果我将它应用于带有插值的字符串,我会得到:
julia> e"foobar $(log(2))"
"I touched this: foobar \$(log(2))"

我希望获得:

julia> e"foobar $(log(2))"
"I touched this: foobar 0.6931471805599453"

我需要对宏声明做出哪些更改?


也许是 return string("我触碰了这个:",eval(parse("\""*s*"\"")))。如果 $ 表达式使用变量,则需要小心获取评估的上下文。 - Dan Getz
4
宏中不要使用eval!这肯定需要更容易实现。更好的注释版本是 return :(string("I touched this: ", $(parse("\""*s*"\"")))),但如果s包含任何"字面量,它将会出错。 - mbauman
感谢@MattB。绝对更好了。忘记连接警报铃声,每当使用eval时会响起。也许做 "\"\"\""*s*"\"\"\"" 可以减少 " 的风险(甚至可以更详细地将 " 替换为 \")。 - Dan Getz
为什么要使用宏?你不能只用标准的字符串插值来完成这个吗? - David P. Sanders
@DavidP.Sanders 我可以这样做,但我想用宏来实现,并且我觉得这个问题很有趣,值得在SO上问一下。我正在学习宏,像这样的东西让我感到困惑。 - RedPointyJackson
1个回答

11

最好在编译时解析字符串而不是委托给Julia。基本上,将字符串放入IOBuffer中,扫描字符串查找$符号,并在出现时使用parse函数。

macro e_str(s)
    components = []
    buf = IOBuffer(s)
    while !eof(buf)
        push!(components, rstrip(readuntil(buf, '$'), '$'))
        if !eof(buf)
            push!(components, parse(buf; greedy=false))
        end
    end
    quote
        string($(map(esc, components)...))
    end
end

这在转义的 $ 字符中不起作用,但可以通过一些微小的更改来处理 \。我在本贴底部包含了一个基本示例。

我以这种方式编写它是因为字符串宏通常不用于模拟 Julia 字符串 —— 使用普通字符串文字的普通宏更适合这个目的。因此,自己编写解析并不是很糟糕,特别是因为它允许定制扩展。如果您真的希望解析与 Julia 解析完全相同,则可以转义字符串,然后重新解析它,正如 @MattB 建议的那样:

macro e_str(s)
    esc(parse("\"$(escape_string(s))\""))
end

得到的表达式是一个:string表达式,您可以转储并检查,然后按照通常的方式进行分析。



字符串宏不带有内置的插值功能。但是可以手动实现此功能。请注意,无法嵌入具有与周围字符串宏相同分隔符的字符串字面量,而不进行转义;也就是说,虽然""" $("x") """是可能的,但" $("x") "则不行。相反,必须将其转义为" $(\"x\") "

手动实现插值的方法有两种:手动实现解析或让Julia进行解析。第一种方法更灵活,但第二种方法更容易。

手动解析

macro interp_str(s)
    components = []
    buf = IOBuffer(s)
    while !eof(buf)
        push!(components, rstrip(readuntil(buf, '$'), '$'))
        if !eof(buf)
            push!(components, parse(buf; greedy=false))
        end
    end
    quote
        string($(map(esc, components)...))
    end
end

Julia解析

macro e_str(s)
    esc(parse("\"$(escape_string(s))\""))
end

这种方法对字符串进行转义(但请注意,escape_string 不会转义 $ 符号),然后将其传回 Julia 的解析器进行解析。转义字符串是必要的,以确保 "\ 不会影响字符串的解析。生成的表达式是一个 :string 表达式,可以用于宏目的的检查和分解。


2
你的例子很清晰,但是仅仅出于一种学究气,我想指出 esc(parse("\"I touched this: $(escape_string(s))\""))。 :) - Tasos Papastylianou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接