loopsvariablesrustinterpreter

The until function never exits


I am writing a lisp interpreter with rust. I already have a lot of functions, but the loop does not seem to work. The loop is until, which is the opposite of while. It loops until the value is t.

use uni_vars::*;
fn main() {
   let expr = vec![/* SEE BELOW TokenExpressions */];
   for i in expr {
       println!("{:#?}", eval(i));
}
}

#[derive(Debug, Clone, PartialEq)]
enum Token {
    String(String),
    QSymbol(String),
    Symbol(String),
    Bool(bool),
    // Integer, decimal places
    Function(Vec<Token>),
}

#[allow(warnings)]
pub fn eval(mexpr: Token) -> Token {
    match mexpr {
    Token::Symbol(s) => {
        get!(s.as_str(),Token).unwrap_or(Token::Bool(false))
    }
        Token::Function(Args) => {
            let name = Args[0].clone();
            let ARGS = &Args[1..];

            let mut args: Vec<Token> = vec![];
            for i in ARGS {
        match i {
            Token::Function(_) => {
            let evaluated = eval(i.clone());
            args.push(evaluated);
            }
            Token::Symbol(s) => {
            let evaluated = get!(&s,Token).unwrap_or(Token::Bool(false));
            args.push(evaluated);
            }
            _ => {
            args.push(i.clone());
            }
        }
        }


            if name == Token::Symbol("print".to_string()) {
                for i in &args {
                    print!("{:#?}", i.clone());
                }
                return args[args.len() - 1].clone();
        } else if name == Token::Symbol("defvar".to_string()) {
        if args.len() != 2 {
            panic!("Expected 2 argument(s), found {}",args.len())
        }
        let mut N = "".to_string();
        match &args[0] {
            Token::QSymbol(s) => {
            N = s.clone()
            }
            _ => panic!("Invalid argument(s) for defvar")
        }
        global!(&N,args[1].clone(),Token);
        args[0].clone()
        } else if name == Token::Symbol("until".to_string()) {
        if args[0] != Token::Bool(true) && args[0] != Token::Bool(false) {
            panic!("Invalid arguments for until")
        }
        let mut last = Token::Bool(false);
        loop {
            if eval(args[0].clone()) != Token::Bool(true) {
            for i in &args[1..] {
                last = eval(i.clone())
            }
            } else {
            return last;
            }
        }
        } else {
                panic!("Unrecognized function call.")
        }
        }
    _ => mexpr,
    }
}



Say I have this expression in expr:

vec![
    Token::Function(
        vec![
            Token::Symbol(
                "defvar",
            ),
            Token::QSymbol(
                "*test*",
            ),
            Token::Bool(
                false,
            ),
        ],
    ),
    Token::Function(
        vec![
            Token::Symbol(
                "until",
            ),
            Token::Bool(
                true,
            ),
            Token::Function(
                [
                    Token::Symbol(
                        "print",
                    ),
                    Token::String(
                        "Hello from until",
                    ),
                ],
            ),
            Token::Function(
                vec![
                    Token::Symbol(
                        "defvar",
                    ),
                    Token::QSymbol(
                        "*test*",
                    ),
                    Token::Bool(
                        true,
                    ),
                ],
            ),
        ],
    ),
]

This prints "Hello from until". It should not, as t is always t, and only nil gets evaluated. If I have:

vec![
    Token::Function(
        vec![
            Token::Symbol(
                "defvar",
            ),
            Token::QSymbol(
                "*test*",
            ),
            Token::Bool(
                false,
            ),
        ],
    ),
    Token::Function(
        vec![
            Token::Symbol(
                "until",
            ),
            Token::Symbol(
                "*test*",
            ),
            Token::Function(
                [
                    Token::Symbol(
                        "print",
                    ),
                    Token::String(
                        "Hello from until",
                    ),
                ],
            ),
            Token::Function(
                vec![
                    Token::Symbol(
                        "defvar",
                    ),
                    Token::QSymbol(
                        "*test*",
                    ),
                    Token::Bool(
                        true,
                    ),
                ],
            ),
        ],
    ),
]

That never exits, even though *test* is already t. In case you are wondering, uni_vars is a global var package. here I have also tried a while loop, which produced the same result.


Solution

  • Minimised version for the first case, got by trimming all the code not relevant to the problem:

    fn main() {
        use Token::*;
        let expr = Function(vec![
            Symbol("until".into()),
            Bool(true),
            Function(vec![
                Symbol("print".into()),
                String("Hello from until".into()),
            ]),
        ]);
        eval(expr);
    }
    
    #[derive(Debug, Clone, PartialEq)]
    enum Token {
        String(String),
        Symbol(String),
        Bool(bool),
        // Integer, decimal places
        Function(Vec<Token>),
    }
    
    #[allow(warnings)]
    pub fn eval(mexpr: Token) {
        match mexpr {
            Token::Function(Args) => {
                let name = Args[0].clone();
                let ARGS = &Args[1..];
    
                let mut args: Vec<Token> = vec![];
                for i in ARGS {
                    match i {
                        Token::Function(_) => {
                            let evaluated = eval(i.clone());
                        }
                        _ => {
                            args.push(i.clone());
                        }
                    }
                }
    
                if name == Token::Symbol("print".to_string()) {
                    for i in &args {
                        print!("{:#?}", i.clone());
                    }
                }
            }
            _ => {}
        }
    }
    

    Playground

    Here, the problem is much more visible - you're evaluating all of the arguments to your functions eagerly, including calling all the functions passed as arguments; therefore, print executes even before until ever takes control.

    The way to fix this will depend on the semantics you want to achieve. You could special-case until to have its argument evaluated lazily - that is, to push it into args as Token::Function, without calling eval. You could treat any function argument as lazily-evaluated and add another token type, which would force eager evaluation when it's necessary.