Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TCO to Lisp #420

Merged
merged 16 commits into from
Oct 24, 2022
8 changes: 8 additions & 0 deletions dsk/tmp/lisp/sum.lsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(load "/lib/lisp/core.lsp")

(def (sum n acc)
(if (= n 0) acc (sum (- n 1) (+ n acc))))

(println
(if (nil? args) "Usage: sum <num>"
(sum (string->number (car args)) 0)))
1 change: 1 addition & 0 deletions src/usr/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub fn copy_files(verbose: bool) {
copy_file("/tmp/lisp/factorial.lsp", include_bytes!("../../dsk/tmp/lisp/factorial.lsp"), verbose);
copy_file("/tmp/lisp/fibonacci.lsp", include_bytes!("../../dsk/tmp/lisp/fibonacci.lsp"), verbose);
copy_file("/tmp/lisp/pi.lsp", include_bytes!("../../dsk/tmp/lisp/pi.lsp"), verbose);
copy_file("/tmp/lisp/sum.lsp", include_bytes!("../../dsk/tmp/lisp/sum.lsp"), verbose);

create_dir("/tmp/life", verbose);
copy_file("/tmp/life/centinal.cells", include_bytes!("../../dsk/tmp/life/centinal.cells"), verbose);
Expand Down
4 changes: 2 additions & 2 deletions src/usr/lisp/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ pub fn env_set(key: &str, val: Exp, env: &Rc<RefCell<Env>>) -> Result<(), Err> {
}
}

pub fn lambda_env(params: Rc<Exp>, args: &[Exp], outer: &mut Rc<RefCell<Env>>) -> Result<Rc<RefCell<Env>>, Err> {
let ks = list_of_symbols(&params)?;
pub fn lambda_env(params: &Exp, args: &[Exp], outer: &mut Rc<RefCell<Env>>) -> Result<Rc<RefCell<Env>>, Err> {
let ks = list_of_symbols(params)?;
if ks.len() != args.len() {
let plural = if ks.len() == 1 { "" } else { "s" };
return Err(Err::Reason(format!("Expected {} argument{}, got {}", ks.len(), plural, args.len())));
Expand Down
163 changes: 79 additions & 84 deletions src/usr/lisp/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::string;
use crate::{ensure_length_eq, ensure_length_gt};
use crate::api::fs;

use alloc::boxed::Box;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::ToString;
Expand Down Expand Up @@ -60,12 +61,13 @@ fn eval_cons_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
match eval(&args[1], env)? {
Exp::List(mut list) => {
list.insert(0, eval(&args[0], env)?);
Ok(Exp::List(list.to_vec()))
Ok(Exp::List(list))
},
_ => Err(Err::Reason("Expected list form".to_string())),
}
}

// TODO: Remove this when macro is enabled
fn eval_cond_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
for arg in args {
Expand All @@ -83,17 +85,6 @@ fn eval_cond_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
Ok(Exp::List(vec![]))
}

fn eval_if_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_gt!(args, 1);
if eval(&args[0], env)? == Exp::Bool(true) {
eval(&args[1], env)
} else if args.len() > 2 {
eval(&args[2], env)
} else {
Ok(Exp::List(vec![]))
}
}

pub fn eval_label_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match &args[0] {
Expand Down Expand Up @@ -140,14 +131,7 @@ fn eval_while_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
Ok(res)
}

fn eval_lambda_args(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
Ok(Exp::Lambda(Lambda {
params: Rc::new(args[0].clone()),
body: Rc::new(args[1].clone()),
}))
}

// TODO: Remove this when macro is enabled
fn eval_defun_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
// (defun add (x y) (+ x y)) => (label add (lambda (x y) (+ x y)))
ensure_length_eq!(args, 3);
Expand Down Expand Up @@ -198,76 +182,87 @@ fn eval_load_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
Ok(Exp::Bool(true))
}

pub const BUILT_INS: [&str; 24] = [
"quote", "atom", "eq", "car", "cdr", "cons", "cond", "label", "lambda", "define", "def",
"function", "fun", "fn", "if", "while", "defun", "defn", "apply", "eval", "progn", "begin", "do",
"load"
];

fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Option<Result<Exp, Err>> {
match exp {
Exp::Sym(s) => {
match s.as_ref() {
// Seven Primitive Operators
"quote" => Some(eval_quote_args(args)),
"atom" => Some(eval_atom_args(args, env)),
"eq" => Some(eval_eq_args(args, env)),
"car" => Some(eval_car_args(args, env)),
"cdr" => Some(eval_cdr_args(args, env)),
"cons" => Some(eval_cons_args(args, env)),
"cond" => Some(eval_cond_args(args, env)),

// Two Special Forms
"label" | "define" | "def" => Some(eval_label_args(args, env)),
"lambda" | "function" | "fun" | "fn" => Some(eval_lambda_args(args)),

"if" => Some(eval_if_args(args, env)),
"set" => Some(eval_set_args(args, env)),
"while" => Some(eval_while_args(args, env)),
"defun" | "defn" => Some(eval_defun_args(args, env)),
"apply" => Some(eval_apply_args(args, env)),
"eval" => Some(eval_eval_args(args, env)),
"progn" | "begin" | "do" => Some(eval_progn_args(args, env)),
"load" => Some(eval_load_args(args, env)),
_ => None,
}
},
_ => None,
}
}

pub fn eval_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Vec<Exp>, Err> {
args.iter().map(|x| eval(x, env)).collect()
}

pub const BUILT_INS: [&str; 24] = [
"quote", "atom", "eq", "car", "cdr", "cons", "cond", "label", "lambda", "define", "def",
"function", "fun", "fn", "if", "while", "defun", "defn", "apply", "eval", "progn", "begin",
"do", "load"
];

pub fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
match exp {
Exp::Sym(key) => env_get(key, env),
Exp::Bool(_) => Ok(exp.clone()),
Exp::Num(_) => Ok(exp.clone()),
Exp::Str(_) => Ok(exp.clone()),
Exp::List(list) => {
ensure_length_gt!(list, 0);
let first_form = &list[0];
let args = &list[1..];
match eval_built_in_form(first_form, args, env) {
Some(res) => res,
None => {
let first_eval = eval(first_form, env)?;
match first_eval {
Exp::Primitive(f) => {
f(&eval_args(args, env)?)
},
Exp::Lambda(f) => {
let mut env = lambda_env(f.params, args, env)?;
eval(&f.body, &mut env)
},
_ => Err(Err::Reason("First form must be a function".to_string())),
let mut exp = exp;
let mut env = env;
let mut env_tmp;
let mut exp_tmp;
loop {
match exp {
Exp::Sym(key) => return env_get(key, env),
Exp::Bool(_) => return Ok(exp.clone()),
Exp::Num(_) => return Ok(exp.clone()),
Exp::Str(_) => return Ok(exp.clone()),
Exp::List(list) => {
ensure_length_gt!(list, 0);
let args = &list[1..];
match &list[0] {
Exp::Sym(s) if s == "quote" => return eval_quote_args(args),
Exp::Sym(s) if s == "atom" => return eval_atom_args(args, env),
Exp::Sym(s) if s == "eq" => return eval_eq_args(args, env),
Exp::Sym(s) if s == "car" => return eval_car_args(args, env),
Exp::Sym(s) if s == "cdr" => return eval_cdr_args(args, env),
Exp::Sym(s) if s == "cons" => return eval_cons_args(args, env),
Exp::Sym(s) if s == "cond" => return eval_cond_args(args, env),
Exp::Sym(s) if s == "set" => return eval_set_args(args, env),
Exp::Sym(s) if s == "while" => return eval_while_args(args, env),
Exp::Sym(s) if s == "defun" => return eval_defun_args(args, env),
Exp::Sym(s) if s == "defn" => return eval_defun_args(args, env),
Exp::Sym(s) if s == "apply" => return eval_apply_args(args, env),
Exp::Sym(s) if s == "eval" => return eval_eval_args(args, env),
Exp::Sym(s) if s == "progn" => return eval_progn_args(args, env),
Exp::Sym(s) if s == "begin" => return eval_progn_args(args, env),
Exp::Sym(s) if s == "do" => return eval_progn_args(args, env),
Exp::Sym(s) if s == "load" => return eval_load_args(args, env),
Exp::Sym(s) if s == "label" => return eval_label_args(args, env),
Exp::Sym(s) if s == "define" => return eval_label_args(args, env),
Exp::Sym(s) if s == "def" => return eval_label_args(args, env),
Exp::Sym(s) if s == "if" => {
ensure_length_gt!(args, 1);
if eval(&args[0], env)? == Exp::Bool(true) { // consequent
exp_tmp = args[1].clone();
} else if args.len() > 2 { // alternate
exp_tmp = args[2].clone();
} else { // '()
exp_tmp = Exp::List(vec![Exp::Sym("quote".to_string()), Exp::List(vec![])]);
}
exp = &exp_tmp;
}
Exp::Sym(s) if s == "lambda" || s == "function" || s == "fun" || s == "fn" => {
ensure_length_eq!(args, 2);
return Ok(Exp::Lambda(Box::new(Lambda {
params: args[0].clone(),
body: args[1].clone(),
})))
}
_ => {
match eval(&list[0], env)? {
Exp::Lambda(f) => {
env_tmp = lambda_env(&f.params, args, env)?;
exp_tmp = f.body;
env = &mut env_tmp;
exp = &exp_tmp;
},
Exp::Primitive(f) => {
return f(&eval_args(args, env)?)
},
_ => return Err(Err::Reason("First form must be a function".to_string())),
}
}
}
}
},
Exp::Primitive(_) => Err(Err::Reason("Unexpected form".to_string())),
Exp::Lambda(_) => Err(Err::Reason("Unexpected form".to_string())),
},
Exp::Primitive(_) => return Err(Err::Reason("Unexpected form".to_string())),
Exp::Lambda(_) => return Err(Err::Reason("Unexpected form".to_string())),
}
}
}
7 changes: 4 additions & 3 deletions src/usr/lisp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::api::console::Style;
use crate::api::process::ExitCode;
use crate::api::prompt::Prompt;

use alloc::boxed::Box;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::String;
Expand Down Expand Up @@ -45,7 +46,7 @@ use spin::Mutex;
#[derive(Clone)]
pub enum Exp {
Primitive(fn(&[Exp]) -> Result<Exp, Err>),
Lambda(Lambda),
Lambda(Box<Lambda>),
List(Vec<Exp>),
Bool(bool),
Num(Number),
Expand Down Expand Up @@ -87,8 +88,8 @@ impl fmt::Display for Exp {

#[derive(Clone, PartialEq)]
pub struct Lambda {
params: Rc<Exp>,
body: Rc<Exp>,
params: Exp,
body: Exp,
}

#[derive(Debug)]
Expand Down