在Rust中实现goto逻辑

众所周知,在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.
}

发布于 2020-10-08 12:02