Add Lisp interpreter (#207)

* Add lisp interpreter

* Refactor lisp code

* Add copyright notice

* Add multiplication

* Shorten type names

* Remove print import

* Add tests

* Add tests for def and fn

* Add print builtin

* Add file eval

* Add fib.ls

* Use define and lambda instead of def and fn

* Rewrite the primitive operators

* Update fibonacci example

* Add syntactic sugar for quote

* Rename console::abort to console::end_of_text

* Add console::end_of_transmission

* Remove auto parenthesis

* Fix parsing quotes

* Refactor quote primitive

* Add empty list comparison to eq primitive

* Add defun primitive

* Rename example

* Fix install path

* Add doc

* Add eq? and atom? aliases
This commit is contained in:
Vincent Ollivier 2021-07-21 09:40:40 +02:00 committed by GitHub
parent 6826a9701d
commit 8af428beab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 725 additions and 9 deletions

63
doc/lisp.md Normal file
View File

@ -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
```

7
dsk/tmp/fibonacci.lisp Normal file
View File

@ -0,0 +1,7 @@
(label fib
(lambda (n)
(cond
((< n 2) n)
(true (+ (fib (- n 1)) (fib (- n 2)))))))
(print (fib 6))

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
}

View File

@ -44,7 +44,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode {
let mut send_queue: VecDeque<Vec<u8>> = 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;
}

View File

@ -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"));

637
src/usr/lisp.rs Normal file
View File

@ -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<Exp>),
Func(fn(&[Exp]) -> Result<Exp, Err>),
Lambda(Lambda),
}
#[derive(Clone)]
struct Lambda {
params_exp: Rc<Exp>,
body_exp: Rc<Exp>,
}
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<String> = 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<String, Exp>,
outer: Option<&'a Env<'a>>,
}
// Parse
fn tokenize(expr: &str) -> Vec<String> {
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<Exp> = 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<f64, ParseFloatError> = 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<Exp, Err> {
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<String, Exp> = BTreeMap::new();
data.insert(
"*".to_string(),
Exp::Func(
|args: &[Exp]| -> Result<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Vec<f64>, Err> {
args.iter().map(|x| parse_single_float(x)).collect()
}
fn parse_single_float(exp: &Exp) -> Result<f64, Err> {
match exp {
Exp::Number(num) => Ok(*num),
_ => Err(Err::Reason("expected a number".to_string())),
}
}
// Eval
fn eval_quote_args(arg_forms: &[Exp]) -> Result<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Exp, Err> {
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<Result<Exp, Err>> {
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<Exp> {
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<Exp>) -> Result<Vec<String>, 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<Exp>, arg_forms: &[Exp], outer_env: &'a mut Env) -> Result<Env<'a>, 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<String, Exp> = 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<Vec<Exp>, Err> {
arg_forms.iter().map(|x| eval(x, env)).collect()
}
fn eval(exp: &Exp, env: &mut Env) -> Result<Exp, Err> {
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<Exp, Err> {
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");
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,
}
}

View File

@ -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;
}