使用 LPEG(Lua 解析器表达式语法),例如 boost::spirit

发布于 2024-12-13 06:32:13 字数 1248 浏览 0 评论 0原文

所以我正在用 lpeg 来代替 boostspirit 语法,我必须说 boost::spirit 比 lpeg 更加优雅和自然。然而,由于当前 C++ 编译器技术的限制以及 C++ 中 TMP 的问题,它很难使用。在这种情况下,类型机制是你的敌人而不是你的朋友。另一方面,Lpeg 虽然丑陋且基本,但会带来更高的生产力。

无论如何,我离题了,我的 lpeg 语法的一部分如下所示:

function get_namespace_parser()
  local P, R, S, C, V =
    lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.V

namespace_parser = 
lpeg.P{
    "NAMESPACE";
    NAMESPACE   = V("WS") * P("namespace") * V("SPACE_WS") * V("NAMESPACE_IDENTIFIER") 
                  * V("WS") * V("NAMESPACE_BODY") * V("WS"),

    NAMESPACE_IDENTIFIER = V("IDENTIFIER") / print_string ,
    NAMESPACE_BODY =  "{" * V("WS") *   
                      V("ENTRIES")^0 * V("WS") * "}",


    WS = S(" \t\n")^0,
    SPACE_WS = P(" ") * V("WS") 
}
  return namespace_parser
end 

此语法(尽管不完整)与以下 namespace foo {} 匹配。我想实现以下语义(这是使用 boostspirit 时的常见用例)。

  1. 为命名空间规则创建局部变量。
  2. namespace IDENTIFIER { 匹配时,将命名空间数据结构添加到此局部变量。
  3. 将新创建的命名空间数据结构传递给 NAMESPACE_BODY 以进一步构建 AST...等等。

我确信这个用例是可以实现的。没有例子表明这一点。我对语言或图书馆的了解不够,无法弄清楚如何做到这一点。有人可以展示它的语法吗?

编辑:在尝试与 lpeg 共舞几天并踏上脚步之后,我决定回到精神上:D 很明显 lpeg 应该与 lua 函数一起编织并且这种编织的形式非常自由(而精神具有清晰且记录良好的语义)。我只是还没有正确的 lua 思维模型。

So I am playing with lpeg to replace a boost spirit grammar, I must say boost::spirit is far more elegant and natural than lpeg. However it is a bitch to work with due to the constraints of current C++ compiler technology and the issues of TMP in C++. The type mechanism is in this case your enemy rather than your friend. Lpeg on the other hand while ugly and basic results in more productivity.

Anyway, I am digressing, part of my lpeg grammar looks like as follows:

function get_namespace_parser()
  local P, R, S, C, V =
    lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.V

namespace_parser = 
lpeg.P{
    "NAMESPACE";
    NAMESPACE   = V("WS") * P("namespace") * V("SPACE_WS") * V("NAMESPACE_IDENTIFIER") 
                  * V("WS") * V("NAMESPACE_BODY") * V("WS"),

    NAMESPACE_IDENTIFIER = V("IDENTIFIER") / print_string ,
    NAMESPACE_BODY =  "{" * V("WS") *   
                      V("ENTRIES")^0 * V("WS") * "}",


    WS = S(" \t\n")^0,
    SPACE_WS = P(" ") * V("WS") 
}
  return namespace_parser
end 

This grammar (although incomplete) matches the following namespace foo {}. I'd like to achieve the following semantics (which are common use-cases when using boost spirit).

  1. Create a local variable for the namespace rule.
  2. Add a namespace data structure to this local variable when namespace IDENTIFIER { has been matched.
  3. Pass the newly created namespace data structure to the NAMESPACE_BODY for further construction of the AST... so on and so forth.

I am sure this use-case is achievable. No examples show it. I don't know the language or the library enough to figure out how to do it. Can someone show the syntax for it.

edit : After a few days of trying to dance with lpeg, and getting my feet troden on, I have decided to go back to spirit :D it is clear that lpeg is meant to be weaved with lua functions and that such weaving is very free-form (whereas spirit has clear very well documented semantics). I simply do not have the right mental model of lua yet.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

听风念你 2024-12-20 06:32:13

虽然“为命名空间规则创建局部变量”听起来像“上下文相关语法”,这听起来令人不安,但这并不真正适用于 LPEG,但我假设您想要构建一个抽象语法树。

在 Lua 中,AST 可以表示为嵌套表(带有命名和索引字段)或闭包,执行树应执行的任何任务。

两者都可以通过嵌套 LPEG捕获的组合来生成。

我将把这个答案限制为 AST 作为 Lua 表。

在这种情况下,最有用的 LPEG 捕获将是:

  • lpeg.C(pattern) - 简单捕获,
  • lpeg.Ct(pattern) - 表捕获,
  • >lpeg.Cg(pattern, name) -- 命名组捕获。

以下基于您的代码的示例将生成一个简单的语法树作为 Lua 表:

local lpeg = require'lpeg'
local P, V = lpeg.P, lpeg.V
local C, Ct, Cg = lpeg.C, lpeg.Ct, lpeg.Cg
local locale = lpeg.locale()
local blank = locale.space ^ 0
local space = P' ' * blank
local id = P'_' ^ 0 * locale.alpha * (locale.alnum + '_') ^ 0

local NS = P{ 'ns',
                  -- The upper level table with two fields: 'id' and 'entries':
    ns          = Ct( blank * 'namespace' * space * Cg( V'ns_id', 'id' )
                    * blank * Cg( V'ns_body', 'entries' ) * blank ),
    ns_id       = id,
    ns_body     = P'{' * blank
                         -- The field 'entries' is, in turn, an indexed table:
                       * Ct( (C( V'ns_entry' )
                       * (blank * P',' * blank * C( V'ns_entry') ) ^ 0) ^ -1 )
                       * blank * P'}',
    ns_entry    = id
}
  • lpeg.match( NS, 'namespace foo {}' ) 将给出:
    <前><代码>表#1 {
    [“条目”] = 表#2 {
    },
    [“id”] =“foo”,
    }

  • lpeg.match( NS, 'namespace foo {AA}' ) 将给出:
    <前><代码>表#1 {
    [“条目”] = 表#2 {
    “AA”
    },
    [“id”] =“foo”,
    }

  • lpeg.match( NS, 'namespace foo {AA, _BB}' ) 将给出:
    <前><代码>表#1 {
    [“条目”] = 表#2 {
    “AA”,
    “_BB”
    },
    [“id”] =“foo”,
    }

  • lpeg.match( NS, 'namespace foo {AA, _BB, CC1}' ) 将给出:
    <前><代码>表#1 {
    [“条目”] = 表#2 {
    “AA”,
    “_BB”,
    “CC1”
    },
    [“id”] =“foo”,
    }

Though "Create a local variable for the namespace rule" sounds disturbingly like "context-sensitive grammar", which is not really for LPEG, I will assume that you want to build an abstract syntax tree.

In Lua, an AST can be represented as a nested table (with named and indexed fields) or a closure, doing whatever task that tree is meant to do.

Both can be produced by a combination of nested LPEG captures.

I will limit this answer to AST as a Lua table.

Most useful, in this case, LPEG captures will be:

  • lpeg.C( pattern ) -- simple capture,
  • lpeg.Ct( pattern ) -- table capture,
  • lpeg.Cg( pattern, name ) -- named group capture.

The following example based on your code will produce a simple syntax tree as a Lua table:

local lpeg = require'lpeg'
local P, V = lpeg.P, lpeg.V
local C, Ct, Cg = lpeg.C, lpeg.Ct, lpeg.Cg
local locale = lpeg.locale()
local blank = locale.space ^ 0
local space = P' ' * blank
local id = P'_' ^ 0 * locale.alpha * (locale.alnum + '_') ^ 0

local NS = P{ 'ns',
                  -- The upper level table with two fields: 'id' and 'entries':
    ns          = Ct( blank * 'namespace' * space * Cg( V'ns_id', 'id' )
                    * blank * Cg( V'ns_body', 'entries' ) * blank ),
    ns_id       = id,
    ns_body     = P'{' * blank
                         -- The field 'entries' is, in turn, an indexed table:
                       * Ct( (C( V'ns_entry' )
                       * (blank * P',' * blank * C( V'ns_entry') ) ^ 0) ^ -1 )
                       * blank * P'}',
    ns_entry    = id
}
  • lpeg.match( NS, 'namespace foo {}' ) will give:
    table#1 {
        ["entries"] = table#2 {
        },
       ["id"] = "foo",
    }
    
  • lpeg.match( NS, 'namespace foo {AA}' ) will give:
    table#1 {
        ["entries"] = table#2 {
            "AA"
        },
       ["id"] = "foo",
    }
    
  • lpeg.match( NS, 'namespace foo {AA, _BB}' ) will give:
    table#1 {
        ["entries"] = table#2 {
            "AA",
            "_BB"
        },
       ["id"] = "foo",
    }
    
  • lpeg.match( NS, 'namespace foo {AA, _BB, CC1}' ) will give:
    table#1 {
        ["entries"] = table#2 {
            "AA",
            "_BB",
            "CC1"
        },
       ["id"] = "foo",
    }
    
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文