Macros 在Rust宏中有没有一种模式匹配中缀操作优先的方法?
一个非常简单的例子是在Rust宏中实现基本的加法和乘法Macros 在Rust宏中有没有一种模式匹配中缀操作优先的方法?,macros,rust,Macros,Rust,一个非常简单的例子是在Rust宏中实现基本的加法和乘法 compute!(1 + 2 * 3) // should evaluate to 7 由于Rust macro有限的语法,我不能完全确定这是否可能 这里的要点不是在编译时计算某些东西,而是能够以某种方式解析标记(具有优先级): 宏的使用有严重的限制。例如,您不能有解析歧义。因此,表达式后面不能有一个+。这意味着我们需要用逗号等分隔解析标记。然后我们需要指定基本的二进制操作。最后是从中缀到带括号的中缀或到前缀的中缀的映射。使用中缀到中缀加
compute!(1 + 2 * 3) // should evaluate to 7
由于Rust macro有限的语法,我不能完全确定这是否可能
这里的要点不是在编译时计算某些东西,而是能够以某种方式解析标记(具有优先级):
宏的使用有严重的限制。例如,您不能有解析歧义。因此,表达式后面不能有一个
+
。这意味着我们需要用逗号等分隔解析标记。然后我们需要指定基本的二进制操作。最后是从中缀到带括号的中缀或到前缀的中缀的映射。使用中缀到中缀加括号方法的示例如下:
macro_rules! compute {
($a:expr, +, $b:expr) => {{ add($a, $b) }};
($a:expr, *, $b:expr) => {{ mul($a, $b) }};
($a:expr, +, $($rest:tt)*) => {{
compute!($a, +, compute!($($rest)*))
}};
($a:expr, *, $b:expr, $($rest:tt)*) => {{
compute!(compute!($a, *, $b), $($rest)*)
}};
}
您现在可以像您的问题中那样调用此宏:
compute!(1,+,2,*,3)
理论上,你可以做到。实际上,这不是一个好主意。我还是做了。我在reddit上发布了这篇文章,并被要求在这里转载
这样的宏必然是“tt-muncher”,一个递归到自身的宏,一次解析其输入的一个标记。这是必需的,因为正如上面评论中所指出的,这是分离像a+b
这样的表达式的唯一方法。这些所谓的计划是有充分理由的,而tt-munchers规避了这些计划。递归还意味着扩展宏的时间至少在表达式长度上是线性的。默认情况下,在递归64次后,rustc将放弃扩展宏(但您可以更改对stable的限制)
考虑到这些警告,让我们看看宏!我选择的策略是将中缀表达式转换为后缀,然后计算后缀表达式,这相当简单。我模模糊糊地记得该怎么做,但因为这里的目标是宏观疯狂,而不是算法技巧,我只是遵循了本文底部的规则
无需进一步ado,代码()如下:
我在这里使用的大多数宏技巧都可以在中找到。您可以看到宏分为三个部分:中缀到后缀的转换(所有以@cvt
开头的规则)、后缀解释器(所有以@pfx
开头的规则)和单个入口点(最后一个规则,没有前缀)
转换器使用一个操作符堆栈,在处理输入时建立后缀输出字符串。括号被转换为LP
和RP
,以保持输入为线性令牌流(通常macro_规则
要求括号保持平衡,并将括号组作为单个令牌树进行匹配)。所有运算符都被认为是右关联的,PEMDAS适用(*
和/
优先于+
和-
)
解释器使用一个操作数堆栈,并以一种非常简单的方式计算表达式:将操作数推到堆栈上,当遇到运算符时,弹出两个操作数并应用运算符。后缀解释器的结果是一个与原始中缀表达式非常相似的表达式,但所有内容都用括号括起来以模拟运算符优先级。然后我们依靠rustc进行实际的运算:)
代码末尾包含了一些示例。如果你发现任何错误,请告诉我!一个限制是每个操作数必须是单个令牌树,因此像
5.0f32.sqrt()
这样的输入将导致分析错误,像-2
这样的多令牌文本可能导致错误答案。你可以用大括号来解决这个问题,例如,infix!({-2.0}-{5.0f32.sqrt()})
(这也可以通过使宏复杂化来解决)。这不是关于在宏中进行数学运算,而是关于在宏中具有优先级。我已经更新了问题。这里的想法是能够解析操作。如果你只是简单地重新实现默认的Rust算法,这并不重要。所以基本的限制是,你不允许在expr
之后有+
,因为这会有歧义,对吗?这意味着模棱两可也适用于模式,而不仅仅是生锈规则。我不确定你所说的“生锈规则”是什么意思。但是很明显,如果有一个表达式后跟一个+
,那么后面需要另一个表达式。Rust在第一个+
之前不可能知道您想要解析表达式,这通常会非常奇怪。也许有一种方法可以使用tt
而不是expr
,但我还没能让它工作。我还没读过《铁锈宏》这本小书,但也许这会是一个很好的补充。我有几个像这样疯狂的宏。我正在考虑开始我自己的宏博客系列。
macro_rules! compute {
($a:expr, +, $b:expr) => {{ add($a, $b) }};
($a:expr, *, $b:expr) => {{ mul($a, $b) }};
($a:expr, +, $($rest:tt)*) => {{
compute!($a, +, compute!($($rest)*))
}};
($a:expr, *, $b:expr, $($rest:tt)*) => {{
compute!(compute!($a, *, $b), $($rest)*)
}};
}
macro_rules! infix {
// done converting
(@cvt () $postfix:tt) => { infix!(@pfx () $postfix) };
// | | ^ postfix expression
// | ^ operand stack
// ^ postfix interpreter
// infix to postfix conversion using the rules at the bottom of this page: http://csis.pace.edu/~wolf/CS122/infix-postfix.htm
// at end of input, flush the operators to postfix
(@cvt ($ophead:tt $($optail:tt)*) ($($postfix:tt)*)) => { infix!(@cvt ($($optail)*) ($($postfix)* $ophead)) };
// 2. push an operator onto the stack if it's empty or has a left-paren on top
(@cvt ( ) $postfix:tt + $($tail:tt)*) => { infix!(@cvt (+ ) $postfix $($tail)*) };
(@cvt ( ) $postfix:tt - $($tail:tt)*) => { infix!(@cvt (- ) $postfix $($tail)*) };
(@cvt ( ) $postfix:tt * $($tail:tt)*) => { infix!(@cvt (* ) $postfix $($tail)*) };
(@cvt ( ) $postfix:tt / $($tail:tt)*) => { infix!(@cvt (/ ) $postfix $($tail)*) };
(@cvt (LP $($optail:tt)*) $postfix:tt + $($tail:tt)*) => { infix!(@cvt (+ LP $($optail)*) $postfix $($tail)*) };
(@cvt (LP $($optail:tt)*) $postfix:tt - $($tail:tt)*) => { infix!(@cvt (- LP $($optail)*) $postfix $($tail)*) };
(@cvt (LP $($optail:tt)*) $postfix:tt * $($tail:tt)*) => { infix!(@cvt (* LP $($optail)*) $postfix $($tail)*) };
(@cvt (LP $($optail:tt)*) $postfix:tt / $($tail:tt)*) => { infix!(@cvt (/ LP $($optail)*) $postfix $($tail)*) };
// 3. push a left-paren onto the stack
(@cvt ($($operator:tt)*) $postfix:tt ($($inner:tt)*) $($tail:tt)*) => { infix!(@cvt (LP $($operator)*) $postfix $($inner)* RP $($tail)*) };
// 4. see right-paren, pop operators to postfix until left-paren
(@cvt (LP $($optail:tt)*) $postfix:tt RP $($tail:tt)*) => { infix!(@cvt ($($optail)*) $postfix $($tail)* ) };
(@cvt ($ophead:tt $($optail:tt)*) ($($postfix:tt)*) RP $($tail:tt)*) => { infix!(@cvt ($($optail)*) ($($postfix)* $ophead) RP $($tail)*) };
// 5. if an operator w/ lower precedence is on top, just push
(@cvt (+ $($optail:tt)*) $postfix:tt * $($tail:tt)*) => { infix!(@cvt (* + $($optail)*) $postfix $($tail)*) };
(@cvt (- $($optail:tt)*) $postfix:tt * $($tail:tt)*) => { infix!(@cvt (* - $($optail)*) $postfix $($tail)*) };
(@cvt (+ $($optail:tt)*) $postfix:tt / $($tail:tt)*) => { infix!(@cvt (/ + $($optail)*) $postfix $($tail)*) };
(@cvt (- $($optail:tt)*) $postfix:tt / $($tail:tt)*) => { infix!(@cvt (/ - $($optail)*) $postfix $($tail)*) };
// 6. if an operator w/ equal precedence is on top, pop and push
(@cvt (+ $($optail:tt)*) ($($postfix:tt)*) + $($tail:tt)*) => { infix!(@cvt (+ $($optail)*) ($($postfix)* +) $($tail)*) };
(@cvt (- $($optail:tt)*) ($($postfix:tt)*) - $($tail:tt)*) => { infix!(@cvt (- $($optail)*) ($($postfix)* -) $($tail)*) };
(@cvt (+ $($optail:tt)*) ($($postfix:tt)*) - $($tail:tt)*) => { infix!(@cvt (- $($optail)*) ($($postfix)* +) $($tail)*) };
(@cvt (- $($optail:tt)*) ($($postfix:tt)*) + $($tail:tt)*) => { infix!(@cvt (+ $($optail)*) ($($postfix)* -) $($tail)*) };
(@cvt (* $($optail:tt)*) ($($postfix:tt)*) * $($tail:tt)*) => { infix!(@cvt (* $($optail)*) ($($postfix)* *) $($tail)*) };
(@cvt (/ $($optail:tt)*) ($($postfix:tt)*) / $($tail:tt)*) => { infix!(@cvt (/ $($optail)*) ($($postfix)* /) $($tail)*) };
(@cvt (* $($optail:tt)*) ($($postfix:tt)*) / $($tail:tt)*) => { infix!(@cvt (/ $($optail)*) ($($postfix)* *) $($tail)*) };
(@cvt (/ $($optail:tt)*) ($($postfix:tt)*) * $($tail:tt)*) => { infix!(@cvt (* $($optail)*) ($($postfix)* /) $($tail)*) };
// 7. if an operator w/ higher precedence is on top, pop it to postfix
(@cvt (* $($optail:tt)*) ($($postfix:tt)*) + $($tail:tt)*) => { infix!(@cvt ($($optail)*) ($($postfix)* *) + $($tail)*) };
(@cvt (* $($optail:tt)*) ($($postfix:tt)*) - $($tail:tt)*) => { infix!(@cvt ($($optail)*) ($($postfix)* *) - $($tail)*) };
(@cvt (/ $($optail:tt)*) ($($postfix:tt)*) + $($tail:tt)*) => { infix!(@cvt ($($optail)*) ($($postfix)* /) + $($tail)*) };
(@cvt (/ $($optail:tt)*) ($($postfix:tt)*) - $($tail:tt)*) => { infix!(@cvt ($($optail)*) ($($postfix)* /) - $($tail)*) };
// 1. operands go to the postfix output
(@cvt $operators:tt ($($postfix:tt)*) $head:tt $($tail:tt)*) => { infix!(@cvt $operators ($($postfix)* ($head)) $($tail)*) };
// postfix interpreter
(@pfx ($result:expr ) ( )) => { $result };
(@pfx (($a:expr) ($b:expr) $($stack:tt)*) (+ $($tail:tt)*)) => { infix!(@pfx ((($b + $a)) $($stack)*) ($($tail)*)) };
(@pfx (($a:expr) ($b:expr) $($stack:tt)*) (- $($tail:tt)*)) => { infix!(@pfx ((($b - $a)) $($stack)*) ($($tail)*)) };
(@pfx (($a:expr) ($b:expr) $($stack:tt)*) (* $($tail:tt)*)) => { infix!(@pfx ((($b * $a)) $($stack)*) ($($tail)*)) };
(@pfx (($a:expr) ($b:expr) $($stack:tt)*) (/ $($tail:tt)*)) => { infix!(@pfx ((($b / $a)) $($stack)*) ($($tail)*)) };
(@pfx ($($stack:tt)* ) ($head:tt $($tail:tt)*)) => { infix!(@pfx ($head $($stack)*) ($($tail)*)) };
($($t:tt)*) => { infix!(@cvt () () $($t)*) }
// | | | ^ infix expression
// | | ^ postfix expression
// | ^ operator stack
// ^ convert infix to postfix
}
fn main() {
println!("{}", infix!(1 + 2 * 3));
println!("{}", infix!(1 * 2 + 3));
println!("{}", infix!(((1 + 2) * 3) * 3));
println!("{}", infix!(( 1 + 2 * 3) * 3));
println!("{}", infix!(1 - 2 - 1));
}