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.
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());
}
}
}
_ => {}
}
}
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.