mirror of https://github.com/vinc/moros.git
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:
parent
6826a9701d
commit
8af428beab
|
@ -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
|
||||||
|
```
|
|
@ -0,0 +1,7 @@
|
||||||
|
(label fib
|
||||||
|
(lambda (n)
|
||||||
|
(cond
|
||||||
|
((< n 2) n)
|
||||||
|
(true (+ (fib (- n 1)) (fib (- n 2)))))))
|
||||||
|
|
||||||
|
(print (fib 6))
|
|
@ -120,12 +120,18 @@ pub fn key_handle(key: char) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort() -> bool {
|
pub fn end_of_text() -> bool {
|
||||||
interrupts::without_interrupts(|| {
|
interrupts::without_interrupts(|| {
|
||||||
STDIN.lock().contains('\x03')
|
STDIN.lock().contains('\x03')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn end_of_transmission() -> bool {
|
||||||
|
interrupts::without_interrupts(|| {
|
||||||
|
STDIN.lock().contains('\x04')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn drain() {
|
pub fn drain() {
|
||||||
interrupts::without_interrupts(|| {
|
interrupts::without_interrupts(|| {
|
||||||
STDIN.lock().clear();
|
STDIN.lock().clear();
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode {
|
||||||
print!("Timeout reached\n");
|
print!("Timeout reached\n");
|
||||||
return usr::shell::ExitCode::CommandError;
|
return usr::shell::ExitCode::CommandError;
|
||||||
}
|
}
|
||||||
if sys::console::abort() {
|
if sys::console::end_of_text() {
|
||||||
print!("\n");
|
print!("\n");
|
||||||
return usr::shell::ExitCode::CommandError;
|
return usr::shell::ExitCode::CommandError;
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||||
print!("Timeout reached\n");
|
print!("Timeout reached\n");
|
||||||
return usr::shell::ExitCode::CommandError;
|
return usr::shell::ExitCode::CommandError;
|
||||||
}
|
}
|
||||||
if sys::console::abort() {
|
if sys::console::end_of_text() {
|
||||||
print!("\n");
|
print!("\n");
|
||||||
return usr::shell::ExitCode::CommandError;
|
return usr::shell::ExitCode::CommandError;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode {
|
||||||
let mut send_queue: VecDeque<Vec<u8>> = VecDeque::new();
|
let mut send_queue: VecDeque<Vec<u8>> = VecDeque::new();
|
||||||
let mut tcp_active = false;
|
let mut tcp_active = false;
|
||||||
loop {
|
loop {
|
||||||
if sys::console::abort() {
|
if sys::console::end_of_text() {
|
||||||
print!("\n");
|
print!("\n");
|
||||||
return usr::shell::ExitCode::CommandSuccessful;
|
return usr::shell::ExitCode::CommandSuccessful;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/version.txt", include_bytes!("../../dsk/ini/version.txt"));
|
||||||
copy_file("/ini/palette.csv", include_bytes!("../../dsk/ini/palette.csv"));
|
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/alice.txt", include_bytes!("../../dsk/tmp/alice.txt"));
|
||||||
|
copy_file("/tmp/fibonacci.lisp", include_bytes!("../../dsk/tmp/fibonacci.lisp"));
|
||||||
|
|
||||||
create_dir("/ini/fonts");
|
create_dir("/ini/fonts");
|
||||||
copy_file("/ini/fonts/lat15-terminus-8x16.psf", include_bytes!("../../dsk/ini/fonts/lat15-terminus-8x16.psf"));
|
copy_file("/ini/fonts/lat15-terminus-8x16.psf", include_bytes!("../../dsk/ini/fonts/lat15-terminus-8x16.psf"));
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ pub mod httpd;
|
||||||
pub mod install;
|
pub mod install;
|
||||||
pub mod ip;
|
pub mod ip;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
pub mod lisp;
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod print;
|
pub mod print;
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||||
let _server_handle = sockets.add(server_socket);
|
let _server_handle = sockets.add(server_socket);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if sys::console::abort() {
|
if sys::console::end_of_text() {
|
||||||
print!("\n");
|
print!("\n");
|
||||||
return usr::shell::ExitCode::CommandSuccessful;
|
return usr::shell::ExitCode::CommandSuccessful;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||||
if let Some(c) = core::char::from_u32(i) {
|
if let Some(c) = core::char::from_u32(i) {
|
||||||
print!("{}", c);
|
print!("{}", c);
|
||||||
}
|
}
|
||||||
if sys::console::abort() {
|
if sys::console::end_of_text() {
|
||||||
print!("\n");
|
print!("\n");
|
||||||
return usr::shell::ExitCode::CommandSuccessful;
|
return usr::shell::ExitCode::CommandSuccessful;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,13 +66,13 @@ impl Shell {
|
||||||
'\0' => {
|
'\0' => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
'\x04' => { // Ctrl D
|
'\x04' => { // Ctrl D => End of Transmission
|
||||||
if self.cmd.is_empty() {
|
if self.cmd.is_empty() {
|
||||||
sys::vga::clear_screen();
|
sys::vga::clear_screen();
|
||||||
return ExitCode::CommandSuccessful;
|
return ExitCode::CommandSuccessful;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'\x03' => { // Ctrl C
|
'\x03' => { // Ctrl C => End of Text
|
||||||
self.cmd.clear();
|
self.cmd.clear();
|
||||||
self.errored = false;
|
self.errored = false;
|
||||||
print!("\n\n");
|
print!("\n\n");
|
||||||
|
@ -430,6 +430,7 @@ impl Shell {
|
||||||
"disk" => usr::disk::main(&args),
|
"disk" => usr::disk::main(&args),
|
||||||
"user" => usr::user::main(&args),
|
"user" => usr::user::main(&args),
|
||||||
"mem" | "memory" => usr::mem::main(&args),
|
"mem" | "memory" => usr::mem::main(&args),
|
||||||
|
"lisp" => usr::lisp::main(&args),
|
||||||
_ => ExitCode::CommandUnknown,
|
_ => ExitCode::CommandUnknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode {
|
||||||
print!("Timeout reached\n");
|
print!("Timeout reached\n");
|
||||||
return usr::shell::ExitCode::CommandError;
|
return usr::shell::ExitCode::CommandError;
|
||||||
}
|
}
|
||||||
if sys::console::abort() {
|
if sys::console::end_of_text() {
|
||||||
print!("\n");
|
print!("\n");
|
||||||
return usr::shell::ExitCode::CommandError;
|
return usr::shell::ExitCode::CommandError;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue