Pegjs: 提供一种简洁的方式来指示序列中的单个返回值

创建于 2014-01-29  ·  21评论  ·  资料来源: pegjs/pegjs

这对我来说相当普遍。 我只想在 init 标签中返回序列的相关部分。 在这种情况下,只是AssignmentExpression。

pattern:Pattern init:(_ "=" _ a:AssignmentExpression {return a})?

我建议您添加 @ 表达式,它将仅从序列中返回以下表达式。 所以上面的例子看起来像这样:

pattern:Pattern init:(_ "=" _ @AssignmentExpression)?

相关问题:

  • #427 允许在没有操作的情况下返回规则中特定表达式的匹配结果
  • #545 简化语法的简单语法扩展
feature

最有用的评论

这已经发布到 npm 上的 dev 标签了吗?

https://www.npmjs.com/package/pegjs/v/0.11.0-dev.325

所有21条评论

我同意这是一个常见问题。 但我不确定是否值得解决。 换句话说,我不确定它是否经常发生,以及它是否会引起足够的痛苦,以保证通过实施解决方案来增加 PEG.js 的复杂性,不管那是什么。

在 1.0.0 之后,我将保持这个问题开放并标记它以供考虑。

同意。 我真的很想在 1.0 中看到这一点。 不过,我对 @ 语法并不那么热衷。 恕我直言,一个更好的主意是这样做:如果只有一个“顶级”标签,则隐式返回该标签。 所以而不是:

rule = space* a:(text space+ otherText)+ newLine* { return a; }

你得到:

rule = space* a:(text space+ otherText)+ newLine*

当标签不是特别有意义时,也允许这样做:

rule = space* :(text space+ otherText)+ newLine*

所以完全跳过标签名称。

@mulderr我认为 @ 运算符更好,因为做一些

+1 @ - 这在我的代码中反复出现。

+1 一些简洁的方法来做到这一点。

看起来这种痛苦类似于 JS 中冗长的function语法,ES6 使用箭头函数来解决它。 也许可以在这里使用类似的东西? 就像是:

rule = (space* a:(text space+ otherText) newLine*) => a

在我看来,这非常灵活,仍然很明确(a la @wildeyes的关注点),并且感觉不像增加复杂性,因为在语法和实现中它都

我一直在想象一些类似的东西:

additive = left:multiplicative "+" right:additive {= left + right; }

其中= (可以随意讨论字符的选择)作为块的第一个非空白字符被转换为return

这也适用于完整表达式,并且应该可以通过转换传递。

任何新闻? 为什么不是additive = left:multiplicative "+" right:additive { => left + right }

考虑到箭头函数现在的工作方式( (left, right) => left + right ),它肯定会感觉很直观。

实际上,在您的 parser.pegjs 文件中有很多地方示例可以通过此功能进行改进。

例如:

  = head:ActionExpression tail:(__ "/" __ ActionExpression)* {
      return tail.length > 0
        ? {
            type: "choice",
            alternatives: buildList(head, tail, 3),
            location: location()
          }
        : head;
    }

由于 buildList 调用中的幻数 3 不直观地链接到序列中 ActionExpression 的位置,因此它很脆弱。 buildList 函数本身通过组合两个不同的操作而变得复杂。 通过使用 @ 表达式和 es6 扩展语法,这变得更清晰:

  = head:ActionExpression tail:(__ "/" __ @ActionExpression)* {
      return tail.length > 0
        ? {
            type: "choice",
            alternatives: [head, ...tail],
            location: location()
          }
        : head;
    }

由于这几乎只是语法糖,我可以通过修改 parser.pegjs 中的 ActionExpression 将此功能添加到您的解析器

  = ExtractSequenceExpression
  / expression:SequenceExpression code:(__ CodeBlock)? {
      return code !== null
        ? {
            type: "action",
            expression: expression,
            code: code[1],
            location: location()
          }
        : expression;
    }

ExtractExpression
  = "@" __ expression:PrefixedExpression {
      return {
        type: "labeled",
        label: "value",
        expression: expression,
        location: location()
      };
    }

ExtractSequenceExpression
  = head:(__ PrefixedExpression)* _ extract:ExtractExpression tail:(__ PrefixedExpression)* {
      return {
        type: "action",
        expression: {
          type: "sequence",
          elements: extractList(head, 1).concat(extract, extractList(tail, 1)),
          location: location()
        },
        code: "return value;",
        location: location()
      }
    }

我检查了一个要点,显示了如何使用 @ 符号简化 parser.pegjs。

extractOptional、extractList 和 buildList 函数已被完全删除,因为 @ 符号使得从序列中提取所需值变得微不足道。

https://gist.github.com/krisnye/a6c2aac94ffc0e222754c52d69e44b83

@krisnye如果有更多的语法糖,它

https://github.com/polkovnikov-ph/newpeg/blob/master/parse.np

我正在考虑将::#用于此语法功能(请参阅我在 #545 中的解释/原因):

  • ::绑定运算符
  • #扩展运算符
// this is imported into grammar
class List extends Array {
  constructor() { this.isList = true; }
}

// grammar
number = _ ::value+ _
value = ::int #(_ "," _ ::int)* { return new List(); }
int = $[0-9]+
_ = [ \t]*
  • 在规则的根序列上, ::将返回表达式的结果作为规则结果
  • 在嵌套序列中, ::将返回嵌套表达式的结果作为序列结果
  • 如果使用了多个:: ,则标记表达式的结果将作为数组返回
  • 如果#用于包含::的嵌套序列,结果将被推入父数组
  • 如果规则有一个代码块以及:: / # ,则先执行它,然后使用结果push方法

遵循这些规则,将09 , 55, 7传递给上述示例生成的解析器将产生:

result = [
    isList: true
    0: "09"
    1: "55"
    2: "7"
]

result instanceof Array # true in ES2015+ enviroments
result instanceof List # true

在规则的根序列上,:: 将返回表达式的结果作为规则结果
在嵌套序列中,:: 将返回嵌套表达式的结果作为序列结果

为什么这些是分开的? 为什么不只是“序列上的::生成序列的参数结果的结果”?

如果使用多个 :: ,则标记表达式的结果将作为数组返回。

这是个坏主意。 在这种情况下,它宁愿保释。 将几种类型的值组合成一个数组是没有意义的。 (那将是一个元组,但它们在 JS 中几乎没用。)

如果 # 用于序列,结果将被 Array#concat'ed 到父数组中

现在这是一个可怕的想法。 这显然是为了摆脱无关的{ return xs.push(x), xs } 。 创建这种特殊情况没有任何意义,因为它可以通过参数化规则以通用方式解决。 用于运算符的单字符序列并不多,我们不应该浪费它们。

如果规则有代码块和::/#,则先执行,然后使用结果推送方法

那么f = ::"a" { return "b" }应该有["a", "b"]作为结果吗?

路过09。 55: 7 到上面示例生成的解析器将产生:

它不会,没有提到.: 。 我也看不出所描述的行为会如何产生结果。

数字 = _ ::值+ _

这也不是_应该使用的方式。 它位于标记的右侧或左侧,为语法选择一次。 如果它在右边,主规则也应该以_开头,反之亦然。

start = _ value
value = ::int #("," _ ::int)* { return new List(); }
number = ::$[0-9]+ _
_ = [ \t]*

JavaScript 示例中实现的类示例(并非真正的实现):

ClassMethod
  = head:FunctionHead __ params:FunctionParameters __ body:FunctionBody {
      return {
        type: "method",
        name: head[ 1 ],
        modifiers: head[ 0 ],
        params: params,
        body: body
      };
    }

// `::` inside the zero_or_more "( ... )*" builds an array as we want,
// so this rule returns `[FunctionModifier[], Identifier]` as expected
MethodHead = (::MethodModifier __)* ("function" __)? ::Identifier

// https://github.com/tc39/proposal-class-fields#private-fields
MethodModifier
  = "#"
  / "static"
  / "async"

FunctionParameters
  = "(" __ head:FunctionParam tail:(__ "," __ ::FunctionParam)* __ ")" {
      // due to `::`, tail is `FunctionParam[]` instead of `[__, "", __, FunctionParam][]`
      return [ head ].concat( tail );
    }
    / "(" __ ")" { return []; }

FunctionParam
  = name:Identifier value:(__ "=" __ ::Expression)? {
      return { name, value };
    }

FunctionBody = "{" __ ::SourceElements? __ "}"

为什么这些是分开的? 为什么不只是::在序列上生成序列的参数结果的结果”?

那不是一样的吗? 我只是把它说得更清楚,以便可以轻松理解MethodHeadFunctionParamFunctionBody等不同用例的结果。

将几种类型的值组合成一个数组是没有意义的。 (那将是一个元组,但它们在 JS 中几乎没用。)

在构建 AST 时(生成解析器最常见的结果),在我看来,它会简化像MethodHead这样的用例,而不是这样写:

MethodHead
  = modifiers:(::MethodModifier __)* ("function" __)? name:Identifier {
      return [ modifiers, name ];
    }

在仔细考虑之后,虽然它简化了用例,但它也增加了开发人员犯错误的可能性(无论是在他们实现语法的方式上,还是在操作处理结果的方式上),因此我认为把这个像multipleSingleReturns (默认值: false )这样的选项背后的用例将是这里最好的行动方案(如果这个功能得到了实现)。

如果 # 用于序列,结果将被 Array#concat'ed 到父数组中

现在这是一个可怕的想法。 这显然是为了摆脱无关紧要的 { return xs.push(x), xs }

它还有助于更常见的用例,例如FunctionParameters ,在这种情况下编写会更好:

// should always return `FunctionParam[]`
FunctionParameters
  = "(" __ ::FunctionParam #(__ "," __ ::FunctionParam)* __ ")"
  / "(" __ ")" { return []; }

它可以通过参数化规则以通用方式解决

我想我应该在参数化规则之前实现单个返回值,因为我仍然不确定如何处理后者(我喜欢使用模板,但使用rule < .., .. > = ..似乎给PEG.js 语法,所以我试图考虑稍微修改语法以使其更适合),但这是一个单独的问题。

用于运算符的单字符序列并不多,我们不应该浪费它们。

这是真的,但如果我们在这样的场合不使用它们,那也一样糟糕。

我想到在这个用例中使用# ,因为我记得它在一些实现预处理指令的语言中用作扩展运算符,这基本上是这个用例在这里涵盖的内容。

所以f = ::"a" { return "b" }应该有["a", "b"]作为结果?

不,因为解析器期望代码块返回一个类似数组的对象,该对象包含一个push方法(因此f = ::"a" { return [ "b" ] }返回[ "b", "a" ]作为结果),因此不仅允许推送到数组中,还允许推送具有相同方法以类似方式工作的自定义节点。 看到这是你读完之后的第一个思路,最好理解这是否是在像pushSingleReturns这样的选项后面? 如果此选项为false (默认),则在包含单个返回值的序列之后放置代码块将引发错误。

路过09。 55: 7 到上面示例生成的解析器将产生:

它不会,没有提到.:

抱歉,这是我重写给定示例时留下的错误。

这也不是_应该使用的方式。 它位于标记的右侧或左侧,为语法选择一次。 如果它在右边,主要规则也应该以_开头,反之亦然。

我认为这真的只是一个偏好问题:smile:,虽然我认为这个例子会更容易理解:

number = _ ::value+ EOS
...
EOS = !.

我也看不出所描述的行为会如何产生结果。

这个更新的评论是否有助于理解我想说的,如果没有,你不明白什么。

我同意@polkovnikov-ph 的观点,即按地点提供的更改非常不明显,只会导致额外错误。

如果 # 用于序列,结果将被 Array#concat'ed 到父数组中

语法start = #('a')应该返回什么? 据我了解,他认为扁平数组的语法糖如何? 我不认为这个运营商是必要的。 对于其最明显的用途——带分隔符的成员列表表达式——最好使用特殊语法(参见 #30 和我的分支,https://github.com/Mingun/pegjs/commit/db4b2b102982a53dbed1f579477c85c06f8b92e6)。

如果规则有代码块和::/#,则先执行,然后使用结果推送方法

极其不明显的行为。 语法源中的动作位于表达式之后,通常在表达式解析之后调用。 突然不知何故,他们开始在解析之前被调用。 标签应该如何表现?

剩下的3点你设法描述出来,让他们的清晰感觉开始逃跑。 注意到@polkovnikov-ph 为什么在描述中将第一种和第二种情况分开的正确率如何? 只需要执行两个简单的规则:

  1. :: (老实说,我不喜欢选择这个字符,太吵了)之前的表达式导致从sequence节点中标出这个字符返回的元素
  2. 如果序列中只有一个这样的元素,则返回其结果,否则返回结果数组

例子:

start =   'a' 'b'   'c'; // => ['a', 'b', 'c']
start = ::'a' 'b'   'c'; // => 'a'
start = ::'a' 'b' ::'c'; // => ['a', 'c']

这个大例子准确地描述了我期望从::看到的内容,并且没有描述可疑的用例( # ,几个:: )。

那不是一样的吗?

这就是我一直在问的。 通用方式的描述通常更有用,因为它使读者确信它确实是同一回事。 谢谢你的澄清。 :)

在我看来,它会简化像 MethodHead 这样的用例

但是为什么不创建一个对象呢? 有modifiers:name:符号,将它们保留在结果 JS 对象中,这会很酷。

它还增加了开发人员犯错误的可能性(无论是在他们实现语法的方式上,还是在操作处理结果的方式上)

最初我打算写这个,但后来决定我没有足够的硬论据。 我宁愿根本不允许在同一级别的序列上有多个:: (甚至没有标志)。

它还有助于更常见的用例,例如 FunctionParameters ,在这种情况下编写会更好:

但那是一样的。 非常好的语法是inter(FunctionParam, "," __)

inter a b = x:a xs:(b ::a)* { return xs.unshift(x), xs; }

rule < .., .. > = .. 似乎给 PEG.js 语法添加了很多噪音

人们期望<...>用于类型,而在这种情况下,参数不是类型。 最好的方法是完全没有任何额外字符的 Haskell 方法(参见上面的inter )。 我不确定这在 PEG.js 语法中如何与省略的;交互。 可能存在f a b = ...被(部分)包含在前一行中的情况。

在某些实现预处理指令的语言中用作扩展运算符

是的,但我宁愿聪明地使用它。 我不会在数组上建议push操作,而是将其用作对象上的Object.assign操作,甚至是与空字符串( eps )匹配的内容,但返回它的论点。 因此,例如,

f = type:#"ident" name:$([a-z]i [a-z0-9_]i+)

将为输入"abc"返回{type: "ident", name: "abc"} "abc"

不,因为解析器期望代码块返回一个包含 push 方法的类数组对象(因此它将是 f = ::"a" { return [ "b" ] } 返回 [ "b", " a" ] 结果),

O_O

我认为这真的只是一个偏好问题

不仅如此,还有性能。 如果每个标记的两边都有_ ,空白字符序列只匹配尾随的,而前面的_则不匹配。 额外调用parse$_需要额外的一点时间。 代码也更长,因为它有两倍多的_ s。

@Mingun我认为@futagoza探索了设计空间。 这是一件好事,特别是如果它是公开的并且我们有机会不同意:)

要做的主要事情不是问“为什么”,而是说“不!不是那样!”

image

f = type:#"ident" name:$([a-z]i [a-z0-9_]i+)将为输入"abc"返回{type: "ident", name: "abc"} "abc"

请不要,哈哈。 这种语法_wayyy_ 太神奇了。

我认为最初提出的@运算符是完美的。 很多很多次我遇到了序列问题:

sequence
    = first:element rest:(whitespace next:element {return next;})*
    {
        return [first].concat(rest);
    }
    ;

_这样_一遍又一遍地打字很痛苦,尤其是当它们比这更复杂时。

但是,使用@运算符,上面的内容就变得简单了:

sequence = first:element rest:(whitespace @element)* { return [first].concat(rest); };

并使用https://github.com/pegjs/pegjs/issues/235#issuecomment -66915879 或https://github.com/pegjs/pegjs/issues/235#issuecomment -67544080 进一步减少到:

sequence = first:element rest:(whitespace @element)* => [first].concat(rest);
/* or */
sequence = first:element rest:(whitespace @element)* {=[first].concat(rest)};

...我非常喜欢第一个。

这似乎是一个向后兼容的更改,很容易实现(似乎有人已经做到了)。

事实上,如果我没记错的话,这可能只是一个小小的碰撞。 可能需要考虑 0.11.0 @futagoza。

刚刚将这个添加到master。 计划使用::进行多次拔毛,使用@进行单次拔毛,但是使用带有标签的::看起来非常丑陋和混乱,所以挂了这个想法🙄

不久前我开始自己实现它,但直到现在才放弃它(当我应该做 #579 时😆)并基于 Mingun 的实现(https://github.com/Mingun/pegjs/commit/1c1c852bae91868eaa90d9bd9f7e4f722aa6433)和基于字节码生成器)

你可以在这里试一试: https ://pegjs.org/development/try(在线编辑器,但使用 PEG 0.11.0-dev)

神圣的周转时间,蝙蝠侠。 很棒的工作@futagoza ,这工作完美 - 非常感谢。 这已经发布到 npm 上的dev标签了吗? 我很想开始测试它。

对于任何想尝试基本语法的人来说,把这只小狗放在那里并给它一个像"abcd"这样的输入。

foo
    = '"' @$bar '"'
    ;

bar
    = [abcd]*
    ;

这已经发布到 npm 上的 dev 标签了吗?

https://www.npmjs.com/package/pegjs/v/0.11.0-dev.325

嗨,这是如果我们有 es6 就会消失的另一个问题,我们需要这些字符来处理从那时起添加到 es6 的其他内容。 为你已经可以做的事情添加运算符会适得其反。

这张票合并了

pattern:Pattern init:(_ "=" _ <strong i="7">@a</strong>:AssignmentExpression)?

es6 中同样的东西,每个人都会固有地理解,并且在解析器的其他部分完成时免费提供,是

pattern:Pattern init:(_ "=" _ a:AssignmentExpression)? => a

有问题的是,在测试时,这个 pluck 实现似乎有问题,当然,这被标记为关闭,因为这是在永远不会发布的分支中修复的

请重新打开这个问题, @futagoza ,直到这个问题在发布版本中得到修复

此页面是否有帮助?
0 / 5 - 0 等级