如何在Julia中解析多行字符串?

9

我可以如何解析更多的代码行?

以下是有效的代码:

julia> eval(parse("""print("O");print("K")"""))
OK

这不起作用:

julia> eval(parse("""print("N");
print("O")"""))
ERROR: ParseError("extra token after end of expression")
Stacktrace:
 [1] #parse#235(::Bool, ::Function, ::String) at ./parse.jl:237
 [2] parse(::String) at ./parse.jl:232

顺便说一下,如果我逐行尝试,会遇到其他问题。例如:

julia> parse("""for i in 1:3""")
:($(Expr(:incomplete, "incomplete: premature end of input")))

尽管:

julia> eval(parse("""for i in 1:2
println(i)
end"""))
1
2
2个回答

7

有点愚蠢,但这样就可以了!:

function parseall(str)
    return Meta.parse("begin $str end").args
end

解释:

正如 @Alexander Morley 指出的以上, parse 的设计是只解析单个表达式。因此,如果您使单个表达式能够包含原始字符串中的所有表达式,则它将可以成功解析!:)

最简单的方法是将字符串包装在块中,这使其成为有效的julia表达式。您可以看到这样的表达式可以成功解析:

julia> Meta.parse("""
       begin
       function f3(x)
         x + 2
       end
       print("N")
       print(f3(5))
       end
       """)
>> quote
    #= none:2 =#
    function f3(x)
        #= none:3 =#
        x + 2
    end
    #= none:5 =#
    print("N")
    #= none:6 =#
    print(f3(5))
end

实际上,@Alexander Morley的代码构建的就是这种结构。(这也是我想以这种方式做的原因!谢谢你,Alexander!)

但是,请注意,如果您正在尝试解析一个文件,您实际上不想返回像那样返回单个block表达式,因为您无法将其作为单个块评估:Julia不允许您将一些“顶级语句”(模块)嵌套在任何东西中。相反,您希望返回一个顶级表达式的数组。这就是为什么我们将args从Block表达式中返回。

有了以上内容,您可以eval每个顶级表达式,并且它将正常工作! :)

julia> for expr in parseall("module M  f() = 1  end");  Core.eval(Main, expr);  end

julia> M.f()
1

这部分是与 @misakawa (Github上的@thautwarm)一起解决的。

4

parse 的设计目的是解析单个表达式(至少文档是这么说的:鉴于此,我有点惊讶你的第一个示例可以正常工作,而不会抛出错误...)。

如果您想解析多个表达式,则可以利用以下事实:

  1. parse 可以接受第二个参数 start,告诉它从哪里开始解析。
  2. 如果您提供了此启动参数,则它将返回一个元组,其中包含表达式和表达式结束的位置。

要自己定义一个 parseall 函数。在基础库中曾经有过,但我不确定现在是否还有。 编辑: 在下面的测试中仍然存在。

# modified from the julia source ./test/parse.jl
function parseall(str)
    pos = start(str)
    exs = []
    while !done(str, pos)
        ex, pos = parse(str, pos) # returns next starting point as well as expr
        ex.head == :toplevel ? append!(exs, ex.args) : push!(exs, ex) #see comments for info
    end
    if length(exs) == 0
        throw(ParseError("end of input"))
    elseif length(exs) == 1
        return exs[1]
    else
        return Expr(:block, exs...) # convert the array of expressions
                                    # back to a single expression
    end
end

1
谢谢!这很鼓舞人心! :) 看起来是在一行中出现了多个表达式的问题。我编辑了你的回复以在代码中显示它,但没想到需要同行审查... 你可以检查 eval(parseall("print(1); print(2) \n\nfor i in 3:4\n print(i)\nend\n"))eval(parseall("print(1)\nprint(2) \n\nfor i in 3:4\n print(i)\nend\n")) - Liso
1
@Liso,你的例子确实出错了,这可能与解析器在发现分号时生成:toplevel表达式有关。将push!(exs,ex)替换为ex.head ==:toplevel?append!(exs,ex.args):push!(exs,ex)可以解决我的问题。 - Dan Getz
Alexander_Morley和@Dan_Getz,我愿意接受修正版本。我们该怎么做? - Liso
1
我已经编辑了我的内容,包括@DanGetz的想法。没有电脑来检查它,但应该可以工作! - Alexander Morley
似乎 parseall 已经被移动到 Meta.parseall - BallpointBen

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