Improve Lisp (#344)

* Add println

* Rewrite fib

* Refactor parse

* Update lisp completer

* Store args in env

* Add 'or' and 'and' builtins

* Add factorial.lsp

* Update docs

* Add cat operation

* Add join operation

* Add system command

* Add time command (#346)

* Add time command

* Fix merge artefact

* Fix call to realtime

* Replace clock syscalls with device files (#345)

* Replace clock syscalls with device files

* Add missing newline to read

* Update time command

* Use Rc<RefCell<Env>>

* Add first TCO

* Remove Box

* Change result of env_for_lambda

* Run clippy

* Remove env clone

* Remove TCO

* Change return type of env_for_lambda
This commit is contained in:
Vincent Ollivier 2022-06-06 16:02:34 +02:00 committed by GitHub
parent 13ca997b7b
commit b2da75189f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 217 additions and 111 deletions

View File

@ -3,13 +3,13 @@
A minimalist Lisp interpreter is available in MOROS to extend the capabilities
of the Shell.
MOROS Lisp is a Lisp-1 dialect inspired by Scheme and Clojure.
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.
In version 0.2.0 the whole implementation was refactored and the parser was
rewritten to use [Nom](https://github.com/Geal/nom). This allowed the addition
of strings to the language and reading from the filesystem.
@ -53,18 +53,17 @@ And it can execute a file. For example a file located in `/tmp/fibonacci.lsp`
with the following content:
```lisp
(label fib
(lambda (n)
(cond
((< n 2) n)
(true (+ (fib (- n 1)) (fib (- n 2)))))))
(defn fib (n)
(cond
((< n 2) n)
(true (+ (fib (- n 1)) (fib (- n 2))))))
(print (fib 6))
(println (fib 10))
```
Would produce the following output:
```
> lisp /tmp/fibonacci.lsp
8
55
```

View File

@ -41,3 +41,6 @@
(defn third (lst)
(second (rest lst)))
(defn println (exp)
(do (print exp) (print "\n")))

View File

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

View File

@ -0,0 +1,15 @@
(load "/ini/lisp/core.lsp")
(defn fact-acc (n acc)
(cond
((< n 2) acc)
(true (fact-acc (- n 1) (* acc n)))))
(defn fact (n)
(fact-acc n 1))
(println
(fact
(cond
((null? args) 10)
(true (parse (car args))))))

View File

@ -0,0 +1,12 @@
(load "/ini/lisp/core.lsp")
(defn fib (n)
(cond
((< n 2) n)
(true (+ (fib (- n 1)) (fib (- n 2))))))
(println
(fib
(cond
((null? args) 10)
(true (parse (car args))))))

View File

@ -42,7 +42,10 @@ pub fn copy_files(verbose: bool) {
copy_file("/ini/fonts/zap-vga-8x16.psf", include_bytes!("../../dsk/ini/fonts/zap-vga-8x16.psf"), verbose);
copy_file("/tmp/alice.txt", include_bytes!("../../dsk/tmp/alice.txt"), verbose);
copy_file("/tmp/fibonacci.lsp", include_bytes!("../../dsk/tmp/fibonacci.lsp"), verbose);
create_dir("/tmp/lisp", verbose);
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);
create_dir("/tmp/beep", verbose);
copy_file("/tmp/beep/tetris.sh", include_bytes!("../../dsk/tmp/beep/tetris.sh"), verbose);

View File

@ -2,13 +2,16 @@ use crate::{api, usr};
use crate::api::fs;
use crate::api::console::Style;
use crate::api::prompt::Prompt;
use alloc::string::ToString;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::format;
use alloc::vec;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use alloc::vec;
use core::borrow::Borrow;
use core::cell::RefCell;
use core::fmt;
use float_cmp::approx_eq;
@ -96,9 +99,47 @@ enum Err {
}
#[derive(Clone)]
struct Env<'a> {
struct Env {
data: BTreeMap<String, Exp>,
outer: Option<&'a Env<'a>>,
outer: Option<Rc<RefCell<Env>>>,
}
const COMPLETER_FORMS: [&str; 21] = [
"atom",
"bytes",
"car",
"cdr",
"cond",
"cons",
"defun",
"eq",
"label",
"lambda",
"lines",
"load",
"mapcar",
"parse",
"print",
"progn",
"quote",
"read",
"read-bytes",
"str",
"type",
];
fn lisp_completer(line: &str) -> Vec<String> {
let mut entries = Vec::new();
if let Some(last_word) = line.split_whitespace().next_back() {
if let Some(f) = last_word.strip_prefix('(') {
for form in COMPLETER_FORMS {
if let Some(entry) = form.strip_prefix(f) {
entries.push(entry.into());
}
}
}
}
entries
}
// Parser
@ -149,10 +190,9 @@ fn parse_exp(input: &str) -> IResult<&str, Exp> {
}
fn parse(input: &str)-> Result<(String, Exp), Err> {
if let Ok((input, exp)) = parse_exp(input) {
Ok((input.to_string(), exp))
} else {
Err(Err::Reason("Could not parse input".to_string()))
match parse_exp(input) {
Ok((input, exp)) => Ok((input.to_string(), exp)),
Err(_) => Err(Err::Reason("Could not parse input".to_string())),
}
}
@ -194,7 +234,7 @@ macro_rules! ensure_length_gt {
};
}
fn default_env<'a>() -> Env<'a> {
fn default_env() -> Rc<RefCell<Env>> {
let mut data: BTreeMap<String, Exp> = BTreeMap::new();
data.insert("=".to_string(), Exp::Func(ensure_tonicity!(|a, b| approx_eq!(f64, a, b))));
data.insert(">".to_string(), Exp::Func(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a > b)));
@ -237,15 +277,35 @@ fn default_env<'a>() -> Env<'a> {
let res = args[1..].iter().fold(car, |acc, a| libm::pow(acc, *a));
Ok(Exp::Num(res))
}));
data.insert("or".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (args[0].clone(), args[1].clone()) {
(Exp::Bool(a), Exp::Bool(b)) => Ok(Exp::Bool(a || b)),
_ => Err(Err::Reason("Expected booleans".to_string())),
}
}));
data.insert("and".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (args[0].clone(), args[1].clone()) {
(Exp::Bool(a), Exp::Bool(b)) => Ok(Exp::Bool(a && b)),
_ => Err(Err::Reason("Expected booleans".to_string())),
}
}));
data.insert("system".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let cmd = string(&args[0])?;
let res = usr::shell::exec(&cmd);
Ok(Exp::Num(res as u8 as f64))
}));
data.insert("print".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match args[0].clone() {
Exp::Str(s) => {
println!("{}", s);
print!("{}", s);
Ok(Exp::Str(s))
}
exp => {
println!("{}", exp);
print!("{}", exp);
Ok(exp)
}
}
@ -282,6 +342,16 @@ fn default_env<'a>() -> Env<'a> {
_ => Err(Err::Reason("Expected arg to be a list".to_string()))
}
}));
data.insert("cat".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::Str(list_of_strings(args)?.join("")))
}));
data.insert("join".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::List(list), Exp::Str(s)) => Ok(Exp::Str(list_of_strings(list)?.join(s))),
_ => Err(Err::Reason("Expected args to be a list and a string".to_string()))
}
}));
data.insert("lines".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
@ -307,7 +377,7 @@ fn default_env<'a>() -> Env<'a> {
};
Ok(Exp::Str(exp.to_string()))
}));
Env { data, outer: None }
Rc::new(RefCell::new(Env { data, outer: None }))
}
fn list_of_symbols(form: &Exp) -> Result<Vec<String>, Err> {
@ -328,6 +398,10 @@ fn list_of_floats(args: &[Exp]) -> Result<Vec<f64>, Err> {
args.iter().map(float).collect()
}
fn list_of_strings(args: &[Exp]) -> Result<Vec<String>, Err> {
args.iter().map(string).collect()
}
fn float(exp: &Exp) -> Result<f64, Err> {
match exp {
Exp::Num(num) => Ok(*num),
@ -349,7 +423,7 @@ fn eval_quote_args(args: &[Exp]) -> Result<Exp, Err> {
Ok(args[0].clone())
}
fn eval_atom_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_atom_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::List(_) => Ok(Exp::Bool(false)),
@ -357,14 +431,14 @@ fn eval_atom_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
}
fn eval_eq_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_eq_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let a = eval(&args[0], env)?;
let b = eval(&args[1], env)?;
Ok(Exp::Bool(a == b))
}
fn eval_car_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_car_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::List(list) => {
@ -375,7 +449,7 @@ fn eval_car_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
}
fn eval_cdr_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_cdr_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::List(list) => {
@ -386,7 +460,7 @@ fn eval_cdr_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
}
fn eval_cons_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_cons_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match eval(&args[1], env)? {
Exp::List(mut list) => {
@ -397,7 +471,7 @@ fn eval_cons_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
}
fn eval_cond_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_cond_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
for arg in args {
match arg {
@ -416,12 +490,12 @@ fn eval_cond_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
Ok(Exp::List(Vec::new()))
}
fn eval_label_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_label_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match &args[0] {
Exp::Sym(key) => {
let exp = eval(&args[1], env)?;
env.data.insert(key.clone(), exp);
env.borrow_mut().data.insert(key.clone(), exp);
Ok(Exp::Sym(key.clone()))
}
_ => Err(Err::Reason("Expected first argument to be a symbol".to_string()))
@ -436,7 +510,7 @@ fn eval_lambda_args(args: &[Exp]) -> Result<Exp, Err> {
}))
}
fn eval_defun_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_defun_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 3);
let name = args[0].clone();
let params = args[1].clone();
@ -446,7 +520,7 @@ fn eval_defun_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
eval_label_args(&label_args, env)
}
fn eval_mapcar_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_mapcar_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match eval(&args[1], env) {
Ok(Exp::List(list)) => {
@ -458,7 +532,7 @@ fn eval_mapcar_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
}
fn eval_progn_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_progn_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
let mut res = Ok(Exp::List(vec![]));
for arg in args {
res = Ok(eval(arg, env)?);
@ -466,7 +540,7 @@ fn eval_progn_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
res
}
fn eval_load_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
fn eval_load_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let path = string(&args[0])?;
let mut code = fs::read_to_string(&path).or(Err(Err::Reason("Could not read file".to_string())))?;
@ -481,7 +555,7 @@ fn eval_load_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
Ok(Exp::Bool(true))
}
fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Env) -> Option<Result<Exp, Err>> {
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() {
@ -509,19 +583,20 @@ fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Env) -> Option<Result<E
}
}
fn env_get(key: &str, env: &Env) -> Result<Exp, Err> {
fn env_get(key: &str, env: &Rc<RefCell<Env>>) -> Result<Exp, Err> {
let env = env.borrow_mut();
match env.data.get(key) {
Some(exp) => Ok(exp.clone()),
None => {
match &env.outer {
Some(outer_env) => env_get(key, outer_env),
Some(outer_env) => env_get(key, outer_env.borrow()),
None => Err(Err::Reason(format!("Unexpected symbol '{}'", key))),
}
}
}
}
fn env_for_lambda<'a>(params: Rc<Exp>, args: &[Exp], outer: &'a mut Env) -> Result<Env<'a>, Err> {
fn env_for_lambda(params: Rc<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" };
@ -532,16 +607,16 @@ fn env_for_lambda<'a>(params: Rc<Exp>, args: &[Exp], outer: &'a mut Env) -> Resu
for (k, v) in ks.iter().zip(vs.iter()) {
data.insert(k.clone(), v.clone());
}
Ok(Env { data, outer: Some(outer) })
Ok(Rc::new(RefCell::new(Env { data, outer: Some(Rc::new(RefCell::new(outer.borrow_mut().clone()))) })))
}
fn eval_args(args: &[Exp], env: &mut Env) -> Result<Vec<Exp>, Err> {
fn eval_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Vec<Exp>, Err> {
args.iter().map(|x| eval(x, env)).collect()
}
fn eval(exp: &Exp, env: &mut Env) -> Result<Exp, Err> {
fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
match exp {
Exp::Sym(key) => env_get(key, env),
Exp::Sym(key) => env_get(&key, &env),
Exp::Bool(_) => Ok(exp.clone()),
Exp::Num(_) => Ok(exp.clone()),
Exp::Str(_) => Ok(exp.clone()),
@ -558,8 +633,8 @@ fn eval(exp: &Exp, env: &mut Env) -> Result<Exp, Err> {
func(&eval_args(args, env)?)
},
Exp::Lambda(lambda) => {
let env = &mut env_for_lambda(lambda.params, args, env)?;
eval(&lambda.body, env)
let mut env = env_for_lambda(lambda.params, args, env)?;
eval(&lambda.body, &mut env)
},
_ => Err(Err::Reason("First form must be a function".to_string())),
}
@ -573,7 +648,7 @@ fn eval(exp: &Exp, env: &mut Env) -> Result<Exp, Err> {
// REPL
fn parse_eval(exp: &str, env: &mut Env) -> Result<Exp, Err> {
fn parse_eval(exp: &str, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
let (_, exp) = parse(exp)?;
let exp = eval(&exp, env)?;
Ok(exp)
@ -583,27 +658,7 @@ fn strip_comments(s: &str) -> String {
s.split('#').next().unwrap().into()
}
const COMPLETER_FORMS: [&str; 11] = [
"quote", "atom?", "eq?", "first", "rest", "cons", "cond", "def", "fn",
"defn", "print",
];
fn lisp_completer(line: &str) -> Vec<String> {
let mut entries = Vec::new();
if let Some(last_word) = line.split_whitespace().next_back() {
if let Some(f) = last_word.strip_prefix('(') {
for form in COMPLETER_FORMS {
if let Some(entry) = form.strip_prefix(f) {
entries.push(entry.into());
}
}
}
}
entries
}
fn repl(env: &mut Env) -> usr::shell::ExitCode {
fn repl(env: &mut Rc<RefCell<Env>>) -> usr::shell::ExitCode {
let csi_color = Style::color("Cyan");
let csi_error = Style::color("LightRed");
let csi_reset = Style::reset();
@ -644,48 +699,56 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
let reset = Style::reset();
let env = &mut default_env();
match args.len() {
1 => {
repl(env)
},
2 => {
let pathname = args[1];
if let Ok(code) = api::fs::read_to_string(pathname) {
let mut block = String::new();
let mut opened = 0;
let mut closed = 0;
for (i, line) in code.split('\n').enumerate() {
let line = strip_comments(line);
if !line.is_empty() {
opened += line.matches('(').count();
closed += line.matches(')').count();
block.push_str(&line);
if closed >= opened {
if let Err(e) = parse_eval(&block, env) {
match e {
Err::Reason(msg) => {
eprintln!("{}Error:{} {}", error_color, reset, msg);
eprintln!();
eprintln!(" {}{}:{} {}", line_color, i, reset, line);
return usr::shell::ExitCode::CommandError;
}
// Store args in env
let key = Exp::Sym("args".to_string());
let list = Exp::List(if args.len() < 2 {
vec![]
} else {
args[2..].iter().map(|arg| Exp::Str(arg.to_string())).collect()
});
let quote = Exp::List(vec![Exp::Sym("quote".to_string()), list]);
if eval_label_args(&[key, quote], env).is_err() {
error!("Could not parse args");
return usr::shell::ExitCode::CommandError;
}
if args.len() < 2 {
repl(env)
} else {
let pathname = args[1];
if let Ok(code) = api::fs::read_to_string(pathname) {
let mut block = String::new();
let mut opened = 0;
let mut closed = 0;
for (i, line) in code.split('\n').enumerate() {
let line = strip_comments(line);
if !line.is_empty() {
opened += line.matches('(').count();
closed += line.matches(')').count();
block.push_str(&line);
if closed >= opened {
if let Err(e) = parse_eval(&block, env) {
match e {
Err::Reason(msg) => {
eprintln!("{}Error:{} {}", error_color, reset, msg);
eprintln!();
eprintln!(" {}{}:{} {}", line_color, i, reset, line);
return usr::shell::ExitCode::CommandError;
}
}
block.clear();
opened = 0;
closed = 0;
}
block.clear();
opened = 0;
closed = 0;
}
}
usr::shell::ExitCode::CommandSuccessful
} else {
error!("File not found '{}'", pathname);
usr::shell::ExitCode::CommandError
}
},
_ => {
usr::shell::ExitCode::CommandSuccessful
} else {
error!("File not found '{}'", pathname);
usr::shell::ExitCode::CommandError
},
}
}
}
@ -795,6 +858,18 @@ fn test_lisp() {
assert_eq!(eval!("(= 6 6)"), "true");
assert_eq!(eval!("(= (+ 0.15 0.15) (+ 0.1 0.2))"), "true");
// and
assert_eq!(eval!("(and true true)"), "true");
assert_eq!(eval!("(and true false)"), "false");
assert_eq!(eval!("(and false true)"), "false");
assert_eq!(eval!("(and false false)"), "false");
// or
assert_eq!(eval!("(or true true)"), "true");
assert_eq!(eval!("(or true false)"), "true");
assert_eq!(eval!("(or false true)"), "true");
assert_eq!(eval!("(or false false)"), "false");
// string
assert_eq!(eval!("(eq \"Hello, World!\" \"foo\")"), "false");
assert_eq!(eval!("(lines \"a\nb\nc\")"), "(\"a\" \"b\" \"c\")");
@ -806,6 +881,12 @@ fn test_lisp() {
assert_eq!(eval!("(map parse '(\"1\" \"2\" \"3\"))"), "(1 2 3)");
assert_eq!(eval!("(map (fn (n) (* n 2)) '(1 2 3))"), "(2 4 6)");
// cat
assert_eq!(eval!("(cat \"a\" \"b\" \"c\")"), "\"abc\"");
// join
assert_eq!(eval!("(join '(\"a\" \"b\" \"c\") \" \")"), "\"a b c\"");
eval!("(defn apply2 (f arg1 arg2) (f arg1 arg2))");
assert_eq!(eval!("(apply2 + 1 2)"), "3");
}