【问题标题】:lpeg parse first-order logic termlpeg 解析一阶逻辑项
【发布时间】:2013-07-26 23:23:59
【问题描述】:

正如标题所说,我正在尝试解析示例

term(A, b, c(d, "e", 7))

在 Lua 表中

{term, {A, b, {c, {d, "e", 7}}}}

这是我构建的语法:

local pattern = re.compile[=[
  term      <- variable / function
  argument  <- variable / lowercase /number / string
  function  <- {|lowercase {|(open argument (separator (argument / function))* close)?|}|}
  variable  <- uppercase
  lowercase <- {[a-z][A-Za-z0-9]*}
  uppercase <- {[A-Z][A-Za-z0-9]*}
  string    <- '"' {~ [^"]* ~} '"'
  number    <- {[0-9]+}
  close     <- blank ")"
  open      <- "(" blank
  separator <- blank "," blank
  blank     <- " "*
]=]

我遇到以下问题:

  • 它无法解析嵌套术语。对于上面的示例,它仅返回 {term, {} }(而 term(A, b, c) 可以)。
  • 为了去除字符串中的引号,我使用了{~ ~},但正因为如此,我不得不将所有捕获从argumentterm 移到下面的行中。有没有办法避免这种情况?
  • 我希望有一个与每个元素关联的键来指定其类型,例如,而不是 A 之类的 {value = "A", type = "variable"}。我找到了一种使用{:name: :} 执行此操作的方法,但是表中元素的顺序丢失了(因为它不会创建新表而只是添加一个键,在这种情况下variable="A" 和这个顺序元素不固定)。如何标记维持订单的商品?

【问题讨论】:

    标签: lua peg first-order-logic lpeg


    【解决方案1】:

    在你的语法中,你有:

    argument  <- variable / lowercase /number / string
    function  <- {|lowercase {|(open argument (separator (argument / function))* close)?|}|}
    

    请记住,lpeg 会按照您拥有的顺序尝试匹配规则中的模式/谓词。一旦找到匹配,lpeg 将不会考虑该语法规则中进一步可能的匹配,即使稍后可能有“更好”的匹配。

    这里它无法匹配嵌套函数调用,因为它看到c 可以匹配

    `argument  <- variable`
    

    由于您的variable 非终端列在function 之前,lpeg 不考虑后者,因此它停止解析后面的标记。

    作为一个实验,我稍微修改了您的语法,并为您感兴趣的大多数非终端添加了一些表格和命名捕获。

    local pattern = re.compile
    [=[
      term      <- {| {:type: '' -> "term" :} term_t |}
      term_t    <- func / var
      func      <- {| {:type: '' -> "func":} {:name: func_id:} "(" arg(separator arg)* ")" |}
      func_id   <- lower / upper
      arg       <- number / string / term_t
      var       <- {| {:type: '' -> "var" :} {:name: lower / upper:} |}
      string    <- '"' {~ [^"]* ~} '"'
      lower <- {%l%w*}
      upper <- {%u%w*}
      number    <- {%d+}
      separator <- blank "," blank
      blank     <- " "*
    ]=]
    

    通过快速模式测试:

    local test = [[fun(A, b, c(d(42), "e", f, 7))]]
    dump( pattern:match(test) )
    

    在我的机器上给出以下输出:

    {
      {
        {
          type = "var",
          name = "A"
        },
        {
          type = "var",
          name = "b"
        },
        {
          {
            "42",
            type = "func",
            name = "d"
          },
          "e",
          {
            type = "var",
            name = "f"
          },
          "7",
          type = "func",
          name = "c"
        },
        type = "func",
        name = "fun"
      },
      type = "term"
    }
    

    仔细查看上面的内容,您会注意到函数参数按照它们传入的顺序出现在表的索引部分中。OTOH typename 可以以任何顺序出现,因为它是在表格的关联部分。您可以将这些“属性”包装在另一个表中,并将该内部属性表放在外部表的索引部分中。

    编辑:这是一个修改后的语法,使解析更加统一。我删除了 term 捕获以帮助修剪一些不必要的分支。

    local pattern2 = re.compile
    [=[
      term      <- term_t
      term_t    <- func / var
      func      <- {| {:type: '' -> "func":} {:name: func_id:} "(" args? ")" |}
      func_id   <- lower / upper
      arg       <- number / string / term_t
      args      <- arg (separator args)?
      var       <- {| {:type: '' -> "var" :} {:name: lower / upper:} |}
      string    <- {| {:type: '' -> "string" :}'"' {:value: [^"]* :} '"' |}
      lower     <- {%l%w*}
      upper     <- {%u%w*}
      number    <- {| {:type: '' -> "number":} {:value: %d+:} |}
      separator <- blank "," blank
      blank     <- " "*
    ]=]
    

    产生以下结果:

    {
      {
        type = "var",
        name = "A"
      },
      {
        type = "var",
        name = "b"
      },
      {
        {
          {
            type = "number",
            value = "42"
          },
          type = "func",
          name = "d"
        },
        {
          type = "string",
          value = "e"
        },
        {
          type = "var",
          name = "f"
        },
        {
          type = "number",
          value = "7"
        },
        type = "func",
        name = "c"
      },
      type = "func",
      name = "fun"
    }
    

    【讨论】:

    • 非常感谢!钉子让我有点困惑。我会采用你的语法并从那里继续。有没有办法获得这样的东西 -> pastebin.com/m3udvahC
    • @キキジキ 你的意思是给term 分支命名?通过以某种方式调整语法,这可能是可能的。请注意,语法当前定义的方式 fun 被解析为函数,因此它是 term 的子代。 term 本身在这里实际上没有名字。 term 应该取什么名字?它应该窃取它的第一个孩子的名字吗?如果那个孩子没有名字怎么办?
    • @キキジキ 另一个想法是完全删除 term 捕获。在这种情况下,您在 AST 中只剩下函数、变量和其他原始终端,如数字、字符串等。这可能没问题,因为看起来 term 并没有在示例中添加任何其他信息。
    • 我想要更多统一的元素。在“c”中,“e”和“7”看起来像这样,但 d 和 f 是表格。我想要做的是每个元素都表示相同。例如,因为“42”是“d”的参数,所以应该有一个 d 的表及其名称和类型,然后是一个嵌套表及其参数列表,如 {d, {42}}。使用类型和名称标签,它应该是 {name="d", type="fun", {{name="42", type="num"}}}。如果 type 和 name 在指定的索引处也可以,例如 {fun, d{{num, 42}}}。关于术语,我碰巧称它为“术语”,但它可以是别的东西。
    • 太棒了!感谢您花时间回答我的问题。
    【解决方案2】:

    抱歉,我没有使用 LPeg 的经验,但通常的 Lua 模式足以轻松解决您的任务:

    local str = 'term(A, b, c(d, "e", 7))'
    
    local function convert(expr)
        return (expr:gsub('(%w+)(%b())',
            function (name, par_expr)
                return '{'..name..', {'..convert(par_expr:sub(2, -2))..'}}'
            end
        ))
    end
    
    print(convert(str))  -- {term, {A, b, {c, {d, "e", 7}}}}
    

    现在只需 load() 转换字符串来创建表。

    【讨论】:

    • 嗯,你是对的,这是一个非常好的解决方案!如果我找到一种方法让 pegs 工作,我可以获得更多数据(如元素类型)并最终扩展它以添加运算符,但如果它太复杂,我可能会采用你的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-20
    • 2019-07-04
    • 2011-03-21
    • 2011-01-19
    • 1970-01-01
    相关资源
    最近更新 更多