众所周知,在Rust中是没有goto表达式的。最近在 试着用Rust练习翻新一些古代陈旧代码, 结果这堆古代的pascal代码中就有很多goto语句。于是写了几个宏来模拟了一下。 在这里也写一篇文章介绍一下,希望给大家在思路上有所帮助。
如果不想听我讲,也可以直接看代码,相应的代码 在这里 。
原理非常简单,Rust的loop
可以加label。如果loop{}
内最后一行是break;
,其实也就相当于只执行一次的代码(不妨称为once模式,形如'label: {...;break;}
)。那么在once模式里使用break 'label
就可以实现向前跳,continue 'label
就可以实现向回跳。
说完了原理,我们试着用Rust的宏机制实现一下。我们需要选择一个MBE宏能够支持的,看起来过得去的语法。我在这里选择了这样:
region_forward_label! {
|'goto_label|
{
...;
goto_forward_label!('goto_label);
...;
}
'goto_label <-
}
region_backward_label! {
'goto_label <-
{
...;
goto_backward_label!('goto_label);
...;
}
|'goto_label|
}
理论上还应该有一个支持双向跳的版本,这里暂时没有用到,就先不写了。
接下来是实现:
macro_rules! region_forward_label {
(|$lbl_:lifetime| {$($s: stmt)*} $lbl:lifetime <- ) => {
#[allow(redundant_semicolons, unused_labels, unreachable_code)]
$lbl : loop {
$($s)*;
break;
}
};
}
macro_rules! region_backward_label {
($lbl:lifetime <- {$($s: stmt)*} |$lbl_:lifetime| ) => {
#[allow(redundant_semicolons, unused_labels, unreachable_code)]
$lbl : loop {
$($s)*;
break;
}
};
}
macro_rules! goto_forward_label {
($lbl:lifetime) => {
break $lbl;
};
}
macro_rules! goto_backward_label {
($lbl:lifetime) => {
continue $lbl;
};
}
怎么样,是不是很简单:)最后再来一段 实际使用这个的程序 ,改编自TeX the program 的第1332小节,是TeX的“主函数”:
/// Main entry to TeX
#[allow(unused_mut, unused_variables)]
pub fn entry() {
// @p begin @!{|start_here|}
/// start_here
let mut globals = TeXGlobals::default();
let globals = &mut globals;
region_forward_label! {|'final_end|{
region_forward_label! {|'end_of_TEX|{
region_forward_label! {|'start_of_TEX|{
// history:=fatal_error_stop; {in case we quit during initialization}
/// in case we quit during initialization
{
globals.history = fatal_error_stop;
}
// t_open_out; {open the terminal for output}
/// open the terminal for output
t_open_out(globals);
// if ready_already=314159 then goto start_of_TEX;
if globals.ready_already == 314159 {
goto_forward_label!('start_of_TEX);
}
// <中间若干行省略>
/// ready_already:=314159;
{
globals.ready_already = 314159;
}
}
// start_of_TEX: @<Initialize the output routines@>;
'start_of_TEX <-
};
Initialize_the_output_routines!(globals);
// @<Get the first line of input and prepare to start@>;
Get_the_first_line_of_input_and_prepare_to_start!(globals);
// history:=spotless; {ready to go!}
/// ready to go!
{
globals.history = spotless;
}
// main_control; {come to life}
/// come to life
main_control(globals);
// final_cleanup; {prepare for death}
/// prepare for death
final_cleanup(globals);
}
// end_of_TEX: close_files_and_terminate;
'end_of_TEX <-
};
close_files_and_terminate(globals);
}
// final_end: ready_already:=0;
'final_end <-
};
globals.ready_already = 0;
// end.
}