diff --git a/doc/lisp.md b/doc/lisp.md new file mode 100644 index 0000000..6a4af34 --- /dev/null +++ b/doc/lisp.md @@ -0,0 +1,63 @@ +# MOROS Lisp + +A minimalist Lisp interpreter is available in MOROS to extend the capabilities +of the Shell. + +It started from [Risp][https://github.com/stopachka/risp] and was extended to +include the seven primitive operators and the two special forms of John +McCarthy's paper "Recursive Functions of Symbolic Expressions and Their +Computation by Machine" (1960) and "The Roots of Lisp" (2002) by Paul Graham. + +MOROS Lisp dialect is also inspired by Scheme and Clojure. + +## Seven Primitive Operators +- `quote` (with the `'` syntax) +- `atom` (aliased to `atom?`) +- `eq` (aliased to `eq?`) +- `car` (aliased to `first`) +- `cdr` (aliased to `rest`) +- `cons` +- `cond` + +## Two Special Forms +- `label` (aliased to `def`) +- `lambda` (aliased to `fn`) + +## Additional primitives +- `defun` (aliased to `defn`) +- `print` + +## Usage + +The interpreter can be invoked from the shell: + +``` +> lisp +MOROS Lisp v0.1.0 + +> (+ 1 2) +3 + +> (exit) +``` + +And it can execute a file. + +For example a file located in `/tmp/fib.ls` with the following content: + +```lisp +(label fib + (lambda (n) + (cond + ((< n 2) n) + (true (+ (fib (- n 1)) (fib (- n 2))))))) + +(print (fib 6)) +``` + +Would produce the following output: + +``` +> lisp /tmp/fib.ls +8 +``` diff --git a/dsk/tmp/fibonacci.lisp b/dsk/tmp/fibonacci.lisp new file mode 100644 index 0000000..ce8a613 --- /dev/null +++ b/dsk/tmp/fibonacci.lisp @@ -0,0 +1,7 @@ +(label fib + (lambda (n) + (cond + ((< n 2) n) + (true (+ (fib (- n 1)) (fib (- n 2))))))) + +(print (fib 6)) diff --git a/src/sys/console.rs b/src/sys/console.rs index 02fe084..5a25c65 100644 --- a/src/sys/console.rs +++ b/src/sys/console.rs @@ -120,12 +120,18 @@ pub fn key_handle(key: char) { } } -pub fn abort() -> bool { +pub fn end_of_text() -> bool { interrupts::without_interrupts(|| { STDIN.lock().contains('\x03') }) } +pub fn end_of_transmission() -> bool { + interrupts::without_interrupts(|| { + STDIN.lock().contains('\x04') + }) +} + pub fn drain() { interrupts::without_interrupts(|| { STDIN.lock().clear(); diff --git a/src/usr/dhcp.rs b/src/usr/dhcp.rs index 5512cdc..aa88dcf 100644 --- a/src/usr/dhcp.rs +++ b/src/usr/dhcp.rs @@ -32,7 +32,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { print!("Timeout reached\n"); return usr::shell::ExitCode::CommandError; } - if sys::console::abort() { + if sys::console::end_of_text() { print!("\n"); return usr::shell::ExitCode::CommandError; } diff --git a/src/usr/http.rs b/src/usr/http.rs index 452adf3..e50403f 100644 --- a/src/usr/http.rs +++ b/src/usr/http.rs @@ -115,7 +115,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { print!("Timeout reached\n"); return usr::shell::ExitCode::CommandError; } - if sys::console::abort() { + if sys::console::end_of_text() { print!("\n"); return usr::shell::ExitCode::CommandError; } diff --git a/src/usr/httpd.rs b/src/usr/httpd.rs index d02fb21..3958c35 100644 --- a/src/usr/httpd.rs +++ b/src/usr/httpd.rs @@ -44,7 +44,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { let mut send_queue: VecDeque> = VecDeque::new(); let mut tcp_active = false; loop { - if sys::console::abort() { + if sys::console::end_of_text() { print!("\n"); return usr::shell::ExitCode::CommandSuccessful; } diff --git a/src/usr/install.rs b/src/usr/install.rs index a1db187..93c4a85 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -43,6 +43,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { copy_file("/ini/version.txt", include_bytes!("../../dsk/ini/version.txt")); copy_file("/ini/palette.csv", include_bytes!("../../dsk/ini/palette.csv")); copy_file("/tmp/alice.txt", include_bytes!("../../dsk/tmp/alice.txt")); + copy_file("/tmp/fibonacci.lisp", include_bytes!("../../dsk/tmp/fibonacci.lisp")); create_dir("/ini/fonts"); copy_file("/ini/fonts/lat15-terminus-8x16.psf", include_bytes!("../../dsk/ini/fonts/lat15-terminus-8x16.psf")); diff --git a/src/usr/lisp.rs b/src/usr/lisp.rs new file mode 100644 index 0000000..82e156c --- /dev/null +++ b/src/usr/lisp.rs @@ -0,0 +1,637 @@ +use crate::{sys, usr}; +use crate::api::console::Style; +use alloc::string::ToString; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::format; +use alloc::vec; +use alloc::collections::BTreeMap; +use alloc::rc::Rc; +use core::fmt; +use core::num::ParseFloatError; + +// Adapted from Risp +// Copyright 2019 Stepan Parunashvili +// https://github.com/stopachka/risp +// +// See "Recursive Functions of Symbolic Expressions and Their Computation by Machine" by John McCarthy (1960) +// And "The Roots of Lisp" by Paul Graham (2002) +// +// MOROS Lisp is also inspired by Racket and Clojure + +// Types + +#[derive(Clone)] +enum Exp { + Bool(bool), + Symbol(String), + Number(f64), + List(Vec), + Func(fn(&[Exp]) -> Result), + Lambda(Lambda), +} + +#[derive(Clone)] +struct Lambda { + params_exp: Rc, + body_exp: Rc, +} + +impl fmt::Display for Exp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let str = match self { + Exp::Bool(a) => a.to_string(), + Exp::Symbol(s) => s.clone(), + Exp::Number(n) => n.to_string(), + Exp::List(list) => { + let xs: Vec = list.iter().map(|x| x.to_string()).collect(); + format!("({})", xs.join(" ")) + }, + Exp::Func(_) => "Function {}".to_string(), + Exp::Lambda(_) => "Lambda {}".to_string(), + }; + + write!(f, "{}", str) + } +} + +#[derive(Debug)] +enum Err { + Reason(String), +} + +#[derive(Clone)] +struct Env<'a> { + data: BTreeMap, + outer: Option<&'a Env<'a>>, +} + +// Parse + +fn tokenize(expr: &str) -> Vec { + expr.replace("(", " ( ") + .replace(")", " ) ") + .replace("'", " ' ") + .split_whitespace().map(|x| x.to_string()).collect() +} + +fn parse<'a>(tokens: &'a [String]) -> Result<(Exp, &'a [String]), Err> { + let (token, rest) = tokens.split_first().ok_or(Err::Reason("could not get token".to_string()))?; + match &token[..] { + "'" => parse_quoted(rest), + "(" => read_seq(rest), + ")" => Err(Err::Reason("unexpected `)`".to_string())), + _ => Ok((parse_atom(token), rest)), + } +} + +fn read_seq<'a>(tokens: &'a [String]) -> Result<(Exp, &'a [String]), Err> { + let mut res: Vec = vec![]; + let mut xs = tokens; + loop { + let (next_token, rest) = xs.split_first().ok_or(Err::Reason("could not find closing `)`".to_string()))?; + if next_token == ")" { + return Ok((Exp::List(res), rest)) // skip `)`, head to the token after + } + let (exp, new_xs) = parse(&xs)?; + res.push(exp); + xs = new_xs; + } +} + +fn parse_quoted<'a>(tokens: &'a [String]) -> Result<(Exp, &'a [String]), Err> { + let xs = tokens; + let (next_token, _) = xs.split_first().ok_or(Err::Reason("could not parse quote".to_string()))?; + let (exp, rest) = if next_token == "(" { + read_seq(&tokens[1..])? // Skip "(" + } else { + parse(&tokens)? + }; + let list = vec![Exp::Symbol("quote".to_string()), exp]; + Ok((Exp::List(list), rest)) +} + +fn parse_atom(token: &str) -> Exp { + match token.as_ref() { + "true" => Exp::Bool(true), + "false" => Exp::Bool(false), + _ => { + let potential_float: Result = token.parse(); + match potential_float { + Ok(v) => Exp::Number(v), + Err(_) => Exp::Symbol(token.to_string().clone()) + } + } + } +} + +// Env + +macro_rules! ensure_tonicity { + ($check_fn:expr) => { + |args: &[Exp]| -> Result { + let floats = parse_list_of_floats(args)?; + let first = floats.first().ok_or(Err::Reason("expected at least one number".to_string()))?; + let rest = &floats[1..]; + fn f (prev: &f64, xs: &[f64]) -> bool { + match xs.first() { + Some(x) => $check_fn(prev, x) && f(x, &xs[1..]), + None => true, + } + } + Ok(Exp::Bool(f(first, rest))) + } + }; +} + +fn default_env<'a>() -> Env<'a> { + let mut data: BTreeMap = BTreeMap::new(); + data.insert( + "*".to_string(), + Exp::Func( + |args: &[Exp]| -> Result { + let res = parse_list_of_floats(args)?.iter().fold(1.0, |res, a| res * a); + Ok(Exp::Number(res)) + } + ) + ); + data.insert( + "+".to_string(), + Exp::Func( + |args: &[Exp]| -> Result { + let res = parse_list_of_floats(args)?.iter().fold(0.0, |res, a| res + a); + Ok(Exp::Number(res)) + } + ) + ); + data.insert( + "-".to_string(), + Exp::Func( + |args: &[Exp]| -> Result { + let floats = parse_list_of_floats(args)?; + let first = *floats.first().ok_or(Err::Reason("expected at least one number".to_string()))?; + let sum_of_rest = floats[1..].iter().fold(0.0, |sum, a| sum + a); + Ok(Exp::Number(first - sum_of_rest)) + } + ) + ); + data.insert( + "=".to_string(), + Exp::Func(ensure_tonicity!(|a, b| a == b)) + ); + data.insert( + ">".to_string(), + Exp::Func(ensure_tonicity!(|a, b| a > b)) + ); + data.insert( + ">=".to_string(), + Exp::Func(ensure_tonicity!(|a, b| a >= b)) + ); + data.insert( + "<".to_string(), + Exp::Func(ensure_tonicity!(|a, b| a < b)) + ); + data.insert( + "<=".to_string(), + Exp::Func(ensure_tonicity!(|a, b| a <= b)) + ); + + Env {data, outer: None} +} + +fn parse_list_of_floats(args: &[Exp]) -> Result, Err> { + args.iter().map(|x| parse_single_float(x)).collect() +} + +fn parse_single_float(exp: &Exp) -> Result { + match exp { + Exp::Number(num) => Ok(*num), + _ => Err(Err::Reason("expected a number".to_string())), + } +} + +// Eval + +fn eval_quote_args(arg_forms: &[Exp]) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + Ok(first_form.clone()) +} + +fn eval_atom_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_eval = eval(first_form, env)?; + match first_eval { + Exp::Symbol(_) => Ok(Exp::Bool(true)), + _ => Ok(Exp::Bool(false)), + } +} + +fn eval_eq_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_eval = eval(first_form, env)?; + let second_form = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + let second_eval = eval(second_form, env)?; + match first_eval { + Exp::Symbol(a) => { + match second_eval { + Exp::Symbol(b) => { + Ok(Exp::Bool(a == b)) + }, + _ => Ok(Exp::Bool(false)) + } + }, + Exp::List(a) => { + match second_eval { + Exp::List(b) => { + Ok(Exp::Bool(a.len() == 0 && b.len() == 0)) + }, + _ => Ok(Exp::Bool(false)) + } + }, + _ => Ok(Exp::Bool(false)) + } +} + +fn eval_car_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_eval = eval(first_form, env)?; + match first_eval { + Exp::List(list) => { + let exp = list.first().ok_or(Err::Reason("list cannot be empty".to_string()))?; // TODO: return nil? + Ok(exp.clone()) + }, + _ => Err(Err::Reason("expected list form".to_string())), + } +} + +fn eval_cdr_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_eval = eval(first_form, env)?; + match first_eval { + Exp::List(list) => { + if list.len() < 1 { + return Err(Err::Reason("list cannot be empty".to_string())) // TODO: return nil? + } + Ok(Exp::List(list[1..].iter().map(|exp| exp.clone()).collect())) + }, + _ => Err(Err::Reason("expected list form".to_string())), + } +} + +fn eval_cons_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_eval = eval(first_form, env)?; + let second_form = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + let second_eval = eval(second_form, env)?; + match second_eval { + Exp::List(mut list) => { + list.insert(0, first_eval); + Ok(Exp::List(list.iter().map(|exp| exp.clone()).collect())) + }, + _ => Err(Err::Reason("expected list form".to_string())), + } +} + +fn eval_cond_args(arg_forms: &[Exp], env: &mut Env) -> Result { + if arg_forms.len() == 0 { + return Err(Err::Reason("expected at least one form".to_string())) + } + for arg_form in arg_forms { + match arg_form { + Exp::List(list) => { + if list.len() != 2 { + return Err(Err::Reason("expected lists of predicate and expression".to_string())) + } + let pred = eval(&list[0], env)?; + let exp = eval(&list[1], env)?; + match pred { + Exp::Bool(b) => { + if b { + return Ok(exp.clone()); + } + }, + _ => continue, + } + }, + _ => return Err(Err::Reason("expected lists of predicate and expression".to_string())), + } + } + Ok(Exp::List(Vec::new())) +} + +fn eval_label_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_str = match first_form { + Exp::Symbol(s) => Ok(s.clone()), + _ => Err(Err::Reason("expected first form to be a symbol".to_string())) + }?; + let second_form = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + if arg_forms.len() > 2 { + return Err(Err::Reason("label can only have two forms".to_string())) + } + let second_eval = eval(second_form, env)?; + env.data.insert(first_str, second_eval); + Ok(first_form.clone()) +} + +fn eval_lambda_args(arg_forms: &[Exp]) -> Result { + let params_exp = arg_forms.first().ok_or(Err::Reason("expected args form".to_string()))?; + let body_exp = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + if arg_forms.len() > 2 { + return Err(Err::Reason("lambda definition can only have two forms".to_string())) + } + Ok(Exp::Lambda(Lambda { + body_exp: Rc::new(body_exp.clone()), + params_exp: Rc::new(params_exp.clone()), + })) +} + +fn eval_defun_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let name = arg_forms.get(0).ok_or(Err::Reason("expected first form".to_string()))?.clone(); + let params = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?.clone(); + let exp = arg_forms.get(2).ok_or(Err::Reason("expected third form".to_string()))?.clone(); + let lambda_args = vec![Exp::Symbol("lambda".to_string()), params, exp]; + let label_args = vec![name, Exp::List(lambda_args)]; + eval_label_args(&label_args, env) +} + +fn eval_print_args(arg_forms: &[Exp], env: &mut Env) -> Result { + let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + if arg_forms.len() > 1 { + return Err(Err::Reason("print can only have one form".to_string())) + } + match eval(first_form, env) { + Ok(res) => { + println!("{}", res); + Ok(res) + }, + Err(res) => { + Err(res) + }, + } +} + +fn eval_built_in_form(exp: &Exp, arg_forms: &[Exp], env: &mut Env) -> Option> { + match exp { + Exp::Symbol(s) => { + match s.as_ref() { + // Seven Primitive Operators + "quote" => Some(eval_quote_args(arg_forms)), + "atom" | "atom?" => Some(eval_atom_args(arg_forms, env)), + "eq" | "eq?" => Some(eval_eq_args(arg_forms, env)), + "car" | "first" => Some(eval_car_args(arg_forms, env)), + "cdr" | "rest" => Some(eval_cdr_args(arg_forms, env)), + "cons" => Some(eval_cons_args(arg_forms, env)), + "cond" => Some(eval_cond_args(arg_forms, env)), + + // Two Special Forms + "label" | "def" => Some(eval_label_args(arg_forms, env)), + "lambda" | "fn" => Some(eval_lambda_args(arg_forms)), + + "defun" | "defn" => Some(eval_defun_args(arg_forms, env)), + "print" => Some(eval_print_args(arg_forms, env)), + _ => None, + } + }, + _ => None, + } +} + +fn env_get(k: &str, env: &Env) -> Option { + match env.data.get(k) { + Some(exp) => Some(exp.clone()), + None => { + match &env.outer { + Some(outer_env) => env_get(k, &outer_env), + None => None + } + } + } +} + +fn parse_list_of_symbol_strings(form: Rc) -> Result, Err> { + let list = match form.as_ref() { + Exp::List(s) => Ok(s.clone()), + _ => Err(Err::Reason("expected args form to be a list".to_string())) + }?; + list.iter().map(|x| { + match x { + Exp::Symbol(s) => Ok(s.clone()), + _ => Err(Err::Reason("expected symbols in the argument list".to_string())) + } + }).collect() +} + +fn env_for_lambda<'a>(params: Rc, arg_forms: &[Exp], outer_env: &'a mut Env) -> Result, Err> { + let ks = parse_list_of_symbol_strings(params)?; + if ks.len() != arg_forms.len() { + return Err(Err::Reason(format!("expected {} arguments, got {}", ks.len(), arg_forms.len()))); + } + let vs = eval_forms(arg_forms, outer_env)?; + let mut data: BTreeMap = BTreeMap::new(); + for (k, v) in ks.iter().zip(vs.iter()) { + data.insert(k.clone(), v.clone()); + } + Ok(Env { + data, + outer: Some(outer_env), + }) +} + +fn eval_forms(arg_forms: &[Exp], env: &mut Env) -> Result, Err> { + arg_forms.iter().map(|x| eval(x, env)).collect() +} + +fn eval(exp: &Exp, env: &mut Env) -> Result { + match exp { + Exp::Symbol(k) => env_get(k, env).ok_or(Err::Reason(format!("unexpected symbol k='{}'", k))), + Exp::Bool(_a) => Ok(exp.clone()), + Exp::Number(_a) => Ok(exp.clone()), + Exp::List(list) => { + let first_form = list.first().ok_or(Err::Reason("expected a non-empty list".to_string()))?; + let arg_forms = &list[1..]; + match eval_built_in_form(first_form, arg_forms, env) { + Some(res) => res, + None => { + let first_eval = eval(first_form, env)?; + match first_eval { + Exp::Func(f) => { + f(&eval_forms(arg_forms, env)?) + }, + Exp::Lambda(lambda) => { + let new_env = &mut env_for_lambda(lambda.params_exp, arg_forms, env)?; + eval(&lambda.body_exp, new_env) + }, + _ => Err(Err::Reason("first form must be a function".to_string())), + } + } + } + }, + Exp::Func(_) => Err(Err::Reason("unexpected form".to_string())), + Exp::Lambda(_) => Err(Err::Reason("unexpected form".to_string())), + } +} + +// REPL + +fn parse_eval(expr: &str, env: &mut Env) -> Result { + let (parsed_exp, _) = parse(&tokenize(expr))?; + let evaled_exp = eval(&parsed_exp, env)?; + Ok(evaled_exp) +} + +fn strip_comments(s: &str) -> String { + s.split("#").next().unwrap().into() +} + +fn slurp_expr() -> String { + strip_comments(sys::console::get_line().trim_end()) +} + +fn repl(env: &mut Env) -> usr::shell::ExitCode { + print!("MOROS Lisp v0.1.0\n\n"); + let csi_color = Style::color("Cyan"); + let csi_error = Style::color("Red"); + let csi_reset = Style::reset(); + loop { + print!("{}>{} ", csi_color, csi_reset); + let expr = slurp_expr(); + if expr == "(exit)" || sys::console::end_of_text() || sys::console::end_of_transmission() { + return usr::shell::ExitCode::CommandSuccessful; + } + match parse_eval(&expr, env) { + Ok(res) => print!("{}\n\n", res), + Err(e) => match e { + Err::Reason(msg) => print!("{}Error: {}{}\n\n", csi_error, msg, csi_reset), + }, + } + } +} + +pub fn main(args: &[&str]) -> usr::shell::ExitCode { + let env = &mut default_env(); + match args.len() { + 1 => { + return repl(env); + }, + 2 => { + let pathname = args[1]; + if let Some(mut file) = sys::fs::File::open(pathname) { + let mut block = String::new(); + let mut opened = 0; + let mut closed = 0; + for line in file.read_to_string().split("\n") { + let line = strip_comments(line); + if line.len() > 0 { + opened += line.matches("(").count(); + closed += line.matches(")").count(); + block.push_str(&line); + if closed >= opened { + //println!("eval: '{}'", block); + if let Err(e) = parse_eval(&block, env) { + match e { + Err::Reason(msg) => { + println!("{}", msg); + return usr::shell::ExitCode::CommandError; + } + } + } + block.clear(); + opened = 0; + closed = 0; + } + } + } + usr::shell::ExitCode::CommandSuccessful + } else { + print!("File not found '{}'\n", pathname); + usr::shell::ExitCode::CommandError + } + }, + _ => { + usr::shell::ExitCode::CommandError + }, + } +} + +#[test_case] +pub fn test_lisp() { + let env = &mut default_env(); + + macro_rules! eval { + ($e:expr) => { + format!("{}", parse_eval($e, env).unwrap()) + }; + } + + // quote + assert_eq!(eval!("(quote (1 2 3))"), "(1 2 3)"); + assert_eq!(eval!("'(1 2 3)"), "(1 2 3)"); + assert_eq!(eval!("(quote 1)"), "1"); + assert_eq!(eval!("'1"), "1"); + assert_eq!(eval!("(quote a)"), "a"); + assert_eq!(eval!("'a"), "a"); + assert_eq!(eval!("(quote '(a b c))"), "(quote (a b c))"); + + // atom + assert_eq!(eval!("(atom (quote a))"), "true"); + assert_eq!(eval!("(atom (quote (1 2 3)))"), "false"); + assert_eq!(eval!("(atom 1)"), "false"); + + // eq + assert_eq!(eval!("(eq (quote a) (quote a))"), "true"); + assert_eq!(eval!("(eq (quote a) (quote b))"), "false"); + assert_eq!(eval!("(eq (quote a) (quote ()))"), "false"); + assert_eq!(eval!("(eq (quote ()) (quote ()))"), "true"); + + // car + assert_eq!(eval!("(car (quote (1)))"), "1"); + assert_eq!(eval!("(car (quote (1 2 3)))"), "1"); + + // cdr + assert_eq!(eval!("(cdr (quote (1)))"), "()"); + assert_eq!(eval!("(cdr (quote (1 2 3)))"), "(2 3)"); + + // cons + assert_eq!(eval!("(cons (quote 1) (quote (2 3)))"), "(1 2 3)"); + assert_eq!(eval!("(cons (quote 1) (cons (quote 2) (cons (quote 3) (quote ()))))"), "(1 2 3)"); + + // cond + assert_eq!(eval!("(cond ((< 2 4) 1) (true 2))"), "1"); + assert_eq!(eval!("(cond ((> 2 4) 1) (true 2))"), "2"); + + // label + eval!("(label a 2)"); + assert_eq!(eval!("(+ a 1)"), "3"); + //eval!("(label fn lambda)"); + //assert_eq!(eval!("((fn (a) (+ 1 a)) 2)"), "3"); + eval!("(label add-one (lambda (b) (+ b 1)))"); + assert_eq!(eval!("(add-one 2)"), "3"); + eval!("(label fib (lambda (n) (cond ((< n 2) n) (true (+ (fib (- n 1)) (fib (- n 2)))))))"); + assert_eq!(eval!("(fib 6)"), "8"); + + // lambda + assert_eq!(eval!("((lambda (a) (+ 1 a)) 2)"), "3"); + assert_eq!(eval!("((lambda (a) (* a a)) 2)"), "4"); + assert_eq!(eval!("((lambda (x) (cons x '(b c))) 'a)"), "(a b c)"); + + // defun + eval!("(defun add (a b) (+ a b))"); + assert_eq!(eval!("(add 1 2)"), "3"); + + // addition + assert_eq!(eval!("(+ 2 2)"), "4"); + assert_eq!(eval!("(+ 2 3 4)"), "9"); + assert_eq!(eval!("(+ 2 (+ 3 4))"), "9"); + + // multiplication + assert_eq!(eval!("(* 2 2)"), "4"); + assert_eq!(eval!("(* 2 3 4)"), "24"); + assert_eq!(eval!("(* 2 (* 3 4))"), "24"); + + // comparisons + assert_eq!(eval!("(< 6 4)"), "false"); + assert_eq!(eval!("(> 6 4 3 1)"), "true"); + assert_eq!(eval!("(= 6 4)"), "false"); + assert_eq!(eval!("(= 6 6)"), "true"); +} diff --git a/src/usr/mod.rs b/src/usr/mod.rs index 57d248c..64a5258 100644 --- a/src/usr/mod.rs +++ b/src/usr/mod.rs @@ -18,6 +18,7 @@ pub mod httpd; pub mod install; pub mod ip; pub mod list; +pub mod lisp; pub mod mem; pub mod net; pub mod print; diff --git a/src/usr/net.rs b/src/usr/net.rs index 64b77ee..8686941 100644 --- a/src/usr/net.rs +++ b/src/usr/net.rs @@ -49,7 +49,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { let _server_handle = sockets.add(server_socket); loop { - if sys::console::abort() { + if sys::console::end_of_text() { print!("\n"); return usr::shell::ExitCode::CommandSuccessful; } diff --git a/src/usr/read.rs b/src/usr/read.rs index 4a77d50..c2ea722 100644 --- a/src/usr/read.rs +++ b/src/usr/read.rs @@ -36,7 +36,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { if let Some(c) = core::char::from_u32(i) { print!("{}", c); } - if sys::console::abort() { + if sys::console::end_of_text() { print!("\n"); return usr::shell::ExitCode::CommandSuccessful; } diff --git a/src/usr/shell.rs b/src/usr/shell.rs index e3d1240..6436b59 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -66,13 +66,13 @@ impl Shell { '\0' => { continue; } - '\x04' => { // Ctrl D + '\x04' => { // Ctrl D => End of Transmission if self.cmd.is_empty() { sys::vga::clear_screen(); return ExitCode::CommandSuccessful; } }, - '\x03' => { // Ctrl C + '\x03' => { // Ctrl C => End of Text self.cmd.clear(); self.errored = false; print!("\n\n"); @@ -430,6 +430,7 @@ impl Shell { "disk" => usr::disk::main(&args), "user" => usr::user::main(&args), "mem" | "memory" => usr::mem::main(&args), + "lisp" => usr::lisp::main(&args), _ => ExitCode::CommandUnknown, } } diff --git a/src/usr/tcp.rs b/src/usr/tcp.rs index d795f84..9bed886 100644 --- a/src/usr/tcp.rs +++ b/src/usr/tcp.rs @@ -76,7 +76,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { print!("Timeout reached\n"); return usr::shell::ExitCode::CommandError; } - if sys::console::abort() { + if sys::console::end_of_text() { print!("\n"); return usr::shell::ExitCode::CommandError; }