这对我来说相当普遍。 我只想在 init 标签中返回序列的相关部分。 在这种情况下,只是AssignmentExpression。
pattern:Pattern init:(_ "=" _ a:AssignmentExpression {return a})?
我建议您添加 @ 表达式,它将仅从序列中返回以下表达式。 所以上面的例子看起来像这样:
pattern:Pattern init:(_ "=" _ @AssignmentExpression)?
相关问题:
我同意这是一个常见问题。 但我不确定是否值得解决。 换句话说,我不确定它是否经常发生,以及它是否会引起足够的痛苦,以保证通过实施解决方案来增加 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? __ "}"
为什么这些是分开的? 为什么不只是
::
在序列上生成序列的参数结果的结果”?
那不是一样的吗? 我只是把它说得更清楚,以便可以轻松理解MethodHead
、 FunctionParam
和FunctionBody
等不同用例的结果。
将几种类型的值组合成一个数组是没有意义的。 (那将是一个元组,但它们在 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 为什么在描述中将第一种和第二种情况分开的正确率如何? 只需要执行两个简单的规则:
::
(老实说,我不喜欢选择这个字符,太吵了)之前的表达式导致从sequence
节点中标出这个字符返回的元素例子:
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探索了设计空间。 这是一件好事,特别是如果它是公开的并且我们有机会不同意:)
要做的主要事情不是问“为什么”,而是说“不!不是那样!”
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 标签了吗?
嗨,这是如果我们有 es6 就会消失的另一个问题,我们需要这些字符来处理从那时起添加到 es6 的其他内容。 为你已经可以做的事情添加运算符会适得其反。
这张票合并了
pattern:Pattern init:(_ "=" _ <strong i="7">@a</strong>:AssignmentExpression)?
es6 中同样的东西,每个人都会固有地理解,并且在解析器的其他部分完成时免费提供,是
pattern:Pattern init:(_ "=" _ a:AssignmentExpression)? => a
有问题的是,在测试时,这个 pluck 实现似乎有问题,当然,这被标记为关闭,因为这是在永远不会发布的分支中修复的
请重新打开这个问题, @futagoza ,直到这个问题在发布版本中得到修复
最有用的评论
https://www.npmjs.com/package/pegjs/v/0.11.0-dev.325