Improve lisp (#449)

* Add sort function

* Add slice function

* Fix empty string conversion into number

* Fix failing test

* Update test memory in makefile

* Add string to slice

* Add string to length

* Replace builtin lines with split and trim

* Fix splitting on empty pattern

* Fix slice for strings

* Add uniq

* Refactor slice

* Refactor sort to accept any expression

* Add contains?

* Add chunks

* Split default_env

* Add filter and intersection to core lib

* Update doc

* Update set to return value instead of symbol

* Add splice for variadic functions
This commit is contained in:
Vincent Ollivier 2022-12-12 18:43:51 +01:00 committed by GitHub
parent c18171ec95
commit 87eb51d1ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 559 additions and 319 deletions

View File

@ -89,7 +89,7 @@ qemu:
test:
cargo test --release --lib --no-default-features --features serial -- \
-m 32 -display none -serial stdio -device isa-debug-exit,iobase=0xf4,iosize=0x04
-m $(memory) -display none -serial stdio -device isa-debug-exit,iobase=0xf4,iosize=0x04
clean:
cargo clean

View File

@ -46,7 +46,8 @@ Rewrite parts of the code and add new functions and examples.
- `quote` (with the `'` syntax)
- `quasiquote` (with the `` ` ``)
- `unquote` (with the `,` syntax)
- `unquote-splicing` (with the `,@` syntax)
- `unquote-splice` (with the `,@` syntax)
- `splice` (with the `@` syntax)
- `atom` (aliased to `atom?`)
- `eq` (aliased to `eq?`)
- `car` (aliased to `first`)
@ -80,24 +81,24 @@ Rewrite parts of the code and add new functions and examples.
- Arithmetic operations: `+`, `-`, `*`, `/`, `%`, `^`
- Trigonometric functions: `acos`, `asin`, `atan`, `cos`, `sin`, `tan`
- Comparisons: `>`, `<`, `>=`, `<=`, `=`
- String operations: `lines`
- File IO: `read-file`, `read-file-bytes`, `write-file-bytes`, `append-file-bytes`
- List: `chunks`, `slice`, `sort`, `uniq`, `length`
- String: `trim`, `split`
### Core Library
- `nil`, `nil?`, `eq?`
- `atom?`, `string?`, `boolean?`, `symbol?`, `number?`, `list?`, `function?`, `macro?`
- `caar`, `cadr`, `cdar`, `cddr`, `first`, `second`, `third`, `rest`
- `map`, `reduce`, `reverse`, `range`
- `map`, `reduce`, `reverse`, `range`, `filter`, `intersection`
- `not`, `and`, `or`
- `let`
- `string-join`
- `string-join`, `lines`, `words`, `chars`
- `read-line`, `read-char`
- `print`, `println`
- `write-file`, `append-file`
- `uptime`, `realtime`
- `regex-match?`
- Boolean operations: `not`, `and`, `or`
## Usage
The interpreter can be invoked from the shell:

View File

@ -78,6 +78,15 @@
(f (first ls))
(map f (rest ls)))))
(define (filter f ls)
(if (nil? ls) nil
(if (f (first ls))
(cons (first ls) (filter f (rest ls)))
(filter f (rest ls)))))
(define (intersection a b)
(filter (function (x) (contains? b x)) a))
(define (reverse x)
(if (nil? x) x
(append (reverse (rest x)) (cons (first x) '()))))
@ -117,3 +126,12 @@
(define (regex-match? pattern str)
(not (nil? (regex-find pattern str))))
(define (lines contents)
(split (trim contents) "\n"))
(define (words contents)
(split contents " "))
(define (chars contents)
(split contents ""))

View File

@ -1,27 +1,16 @@
use super::FORMS;
use super::primitive;
use super::eval::BUILT_INS;
use super::eval::eval_args;
use super::list_of_bytes;
use super::list_of_numbers;
use super::parse::parse;
use super::{Err, Exp, Number};
use super::{float, number, string};
use crate::{ensure_length_eq, ensure_length_gt};
use crate::api::fs;
use crate::api::regex::Regex;
use crate::usr::shell;
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::convert::TryInto;
use core::f64::consts::PI;
#[derive(Clone)]
@ -32,290 +21,53 @@ pub struct Env {
pub fn default_env() -> Rc<RefCell<Env>> {
let mut data: BTreeMap<String, Exp> = BTreeMap::new();
data.insert("pi".to_string(), Exp::Num(Number::from(PI)));
data.insert("=".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] == nums[1])))
}));
data.insert(">".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] > nums[1])))
}));
data.insert(">=".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] >= nums[1])))
}));
data.insert("<".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] < nums[1])))
}));
data.insert("<=".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] <= nums[1])))
}));
data.insert("*".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
let res = list_of_numbers(args)?.iter().fold(Number::Int(1), |acc, a| acc * a.clone());
Ok(Exp::Num(res))
}));
data.insert("+".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
let res = list_of_numbers(args)?.iter().fold(Number::Int(0), |acc, a| acc + a.clone());
Ok(Exp::Num(res))
}));
data.insert("-".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_numbers(args)?;
let car = args[0].clone();
if args.len() == 1 {
Ok(Exp::Num(-car))
} else {
let res = args[1..].iter().fold(Number::Int(0), |acc, a| acc + a.clone());
Ok(Exp::Num(car - res))
}
}));
data.insert("/".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let mut args = list_of_numbers(args)?;
if args.len() == 1 {
args.insert(0, Number::Int(1));
}
for arg in &args[1..] {
if arg.is_zero() {
return Err(Err::Reason("Division by zero".to_string()));
}
}
let car = args[0].clone();
let res = args[1..].iter().fold(car, |acc, a| acc / a.clone());
Ok(Exp::Num(res))
}));
data.insert("%".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_numbers(args)?;
for arg in &args[1..] {
if arg.is_zero() {
return Err(Err::Reason("Division by zero".to_string()));
}
}
let car = args[0].clone();
let res = args[1..].iter().fold(car, |acc, a| acc % a.clone());
Ok(Exp::Num(res))
}));
data.insert("^".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_numbers(args)?;
let car = args[0].clone();
let res = args[1..].iter().fold(car, |acc, a| acc.pow(a));
Ok(Exp::Num(res))
}));
data.insert("<<".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let args = list_of_numbers(args)?;
let res = args[0].clone() << args[1].clone();
Ok(Exp::Num(res))
}));
data.insert(">>".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let args = list_of_numbers(args)?;
let res = args[0].clone() >> args[1].clone();
Ok(Exp::Num(res))
}));
data.insert("cos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.cos()))
}));
data.insert("acos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if -1.0 <= float(&args[0])? && float(&args[0])? <= 1.0 {
Ok(Exp::Num(number(&args[0])?.acos()))
} else {
Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string()))
}
}));
data.insert("asin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if -1.0 <= float(&args[0])? && float(&args[0])? <= 1.0 {
Ok(Exp::Num(number(&args[0])?.asin()))
} else {
Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string()))
}
}));
data.insert("atan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.atan()))
}));
data.insert("sin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.sin()))
}));
data.insert("tan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.tan()))
}));
data.insert("trunc".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.trunc()))
}));
data.insert("system".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let cmd = string(&args[0])?;
match shell::exec(&cmd) {
Ok(()) => Ok(Exp::Num(Number::from(0 as u8))),
Err(code) => Ok(Exp::Num(Number::from(code as u8))),
}
}));
data.insert("read-file".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let path = string(&args[0])?;
let contents = fs::read_to_string(&path).or(Err(Err::Reason("Could not read file".to_string())))?;
Ok(Exp::Str(contents))
}));
data.insert("read-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
let len = number(&args[1])?;
let mut buf = vec![0; len.try_into()?];
let bytes = fs::read(&path, &mut buf).or(Err(Err::Reason("Could not read file".to_string())))?;
buf.resize(bytes, 0);
Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}));
data.insert("write-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
match &args[1] {
Exp::List(list) => {
let buf = list_of_bytes(list)?;
let bytes = fs::write(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?;
Ok(Exp::Num(Number::from(bytes)))
}
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
}
}));
data.insert("append-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
match &args[1] {
Exp::List(list) => {
let buf = list_of_bytes(list)?;
let bytes = fs::append(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?;
Ok(Exp::Num(Number::from(bytes)))
}
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
}
}));
data.insert("string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
let args: Vec<String> = args.iter().map(|arg| match arg {
Exp::Str(s) => format!("{}", s),
exp => format!("{}", exp),
}).collect();
Ok(Exp::Str(args.join("")))
}));
data.insert("string->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let buf = s.as_bytes();
Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}));
data.insert("bytes->string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => {
let buf = list_of_bytes(list)?;
let s = String::from_utf8(buf).or(Err(Err::Reason("Could not convert to valid UTF-8 string".to_string())))?;
Ok(Exp::Str(s))
}
_ => Err(Err::Reason("Expected arg to be a list".to_string()))
}
}));
data.insert("bytes->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) { // TODO: default type to "int" and make it optional
(Exp::List(list), Exp::Str(kind)) => {
let bytes = list_of_bytes(list)?;
ensure_length_eq!(bytes, 8);
match kind.as_str() { // TODO: bigint
"int" => Ok(Exp::Num(Number::Int(i64::from_be_bytes(bytes[0..8].try_into().unwrap())))),
"float" => Ok(Exp::Num(Number::Float(f64::from_be_bytes(bytes[0..8].try_into().unwrap())))),
_ => Err(Err::Reason("Invalid number type".to_string())),
}
}
_ => Err(Err::Reason("Expected args to be the number type and a list of bytes".to_string()))
}
}));
data.insert("number->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let n = number(&args[0])?;
Ok(Exp::List(n.to_be_bytes().iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}));
data.insert("regex-find".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::Str(regex), Exp::Str(s)) => {
let res = Regex::new(regex).find(s).map(|(a, b)| {
vec![Exp::Num(Number::from(a)), Exp::Num(Number::from(b))]
}).unwrap_or(vec![]);
Ok(Exp::List(res))
}
_ => Err(Err::Reason("Expected args to be a regex and a string".to_string()))
}
}));
data.insert("lines".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let lines = s.lines().map(|line| Exp::Str(line.to_string())).collect();
Ok(Exp::List(lines))
}));
data.insert("string->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let n = s.parse().or(Err(Err::Reason("Could not parse number".to_string())))?;
Ok(Exp::Num(n))
}));
data.insert("type".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let exp = match args[0] {
Exp::Primitive(_) => "function",
Exp::Function(_) => "function",
Exp::Macro(_) => "macro",
Exp::List(_) => "list",
Exp::Bool(_) => "boolean",
Exp::Str(_) => "string",
Exp::Sym(_) => "symbol",
Exp::Num(_) => "number",
};
Ok(Exp::Str(exp.to_string()))
}));
data.insert("number-type".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match args[0] {
Exp::Num(Number::Int(_)) => Ok(Exp::Str("int".to_string())),
Exp::Num(Number::BigInt(_)) => Ok(Exp::Str("bigint".to_string())),
Exp::Num(Number::Float(_)) => Ok(Exp::Str("float".to_string())),
_ => Err(Err::Reason("Expected arg to be a number".to_string()))
}
}));
data.insert("parse".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let (_, exp) = parse(&s)?;
Ok(exp)
}));
data.insert("list".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::List(args.to_vec()))
}));
data.insert("length".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if let Exp::List(list) = &args[0] {
Ok(Exp::Num(Number::from(list.len())))
} else {
return Err(Err::Reason("Expected arg to be a list".to_string()))
}
}));
data.insert("append".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
let mut res = vec![];
for arg in args {
if let Exp::List(list) = arg {
res.extend_from_slice(list);
} else {
return Err(Err::Reason("Expected arg to be a list".to_string()))
}
}
Ok(Exp::List(res))
}));
data.insert("pi".to_string(), Exp::Num(Number::from(PI)));
data.insert("=".to_string(), Exp::Primitive(primitive::lisp_eq));
data.insert(">".to_string(), Exp::Primitive(primitive::lisp_gt));
data.insert(">=".to_string(), Exp::Primitive(primitive::lisp_gte));
data.insert("<".to_string(), Exp::Primitive(primitive::lisp_lt));
data.insert("<=".to_string(), Exp::Primitive(primitive::lisp_lte));
data.insert("*".to_string(), Exp::Primitive(primitive::lisp_mul));
data.insert("+".to_string(), Exp::Primitive(primitive::lisp_add));
data.insert("-".to_string(), Exp::Primitive(primitive::lisp_sub));
data.insert("/".to_string(), Exp::Primitive(primitive::lisp_div));
data.insert("%".to_string(), Exp::Primitive(primitive::lisp_mod));
data.insert("^".to_string(), Exp::Primitive(primitive::lisp_exp));
data.insert("<<".to_string(), Exp::Primitive(primitive::lisp_shl));
data.insert(">>".to_string(), Exp::Primitive(primitive::lisp_shr));
data.insert("cos".to_string(), Exp::Primitive(primitive::lisp_cos));
data.insert("acos".to_string(), Exp::Primitive(primitive::lisp_acos));
data.insert("asin".to_string(), Exp::Primitive(primitive::lisp_asin));
data.insert("atan".to_string(), Exp::Primitive(primitive::lisp_atan));
data.insert("sin".to_string(), Exp::Primitive(primitive::lisp_sin));
data.insert("tan".to_string(), Exp::Primitive(primitive::lisp_tan));
data.insert("trunc".to_string(), Exp::Primitive(primitive::lisp_trunc));
data.insert("system".to_string(), Exp::Primitive(primitive::lisp_system));
data.insert("read-file".to_string(), Exp::Primitive(primitive::lisp_read_file));
data.insert("read-file-bytes".to_string(), Exp::Primitive(primitive::lisp_read_file_bytes));
data.insert("write-file-bytes".to_string(), Exp::Primitive(primitive::lisp_write_file_bytes));
data.insert("append-file-bytes".to_string(), Exp::Primitive(primitive::lisp_append_file_bytes));
data.insert("string".to_string(), Exp::Primitive(primitive::lisp_string));
data.insert("string->bytes".to_string(), Exp::Primitive(primitive::lisp_string_bytes));
data.insert("bytes->string".to_string(), Exp::Primitive(primitive::lisp_bytes_string));
data.insert("bytes->number".to_string(), Exp::Primitive(primitive::lisp_bytes_number));
data.insert("number->bytes".to_string(), Exp::Primitive(primitive::lisp_number_bytes));
data.insert("regex-find".to_string(), Exp::Primitive(primitive::lisp_regex_find));
data.insert("string->number".to_string(), Exp::Primitive(primitive::lisp_string_number));
data.insert("type".to_string(), Exp::Primitive(primitive::lisp_type));
data.insert("number-type".to_string(), Exp::Primitive(primitive::lisp_number_type));
data.insert("parse".to_string(), Exp::Primitive(primitive::lisp_parse));
data.insert("list".to_string(), Exp::Primitive(primitive::lisp_list));
data.insert("uniq".to_string(), Exp::Primitive(primitive::lisp_uniq));
data.insert("sort".to_string(), Exp::Primitive(primitive::lisp_sort));
data.insert("contains?".to_string(), Exp::Primitive(primitive::lisp_contains));
data.insert("slice".to_string(), Exp::Primitive(primitive::lisp_slice));
data.insert("chunks".to_string(), Exp::Primitive(primitive::lisp_chunks));
data.insert("split".to_string(), Exp::Primitive(primitive::lisp_split));
data.insert("trim".to_string(), Exp::Primitive(primitive::lisp_trim));
data.insert("length".to_string(), Exp::Primitive(primitive::lisp_length));
data.insert("append".to_string(), Exp::Primitive(primitive::lisp_append));
// Setup autocompletion
*FORMS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();
@ -336,12 +88,12 @@ pub fn env_get(key: &str, env: &Rc<RefCell<Env>>) -> Result<Exp, Err> {
}
}
pub fn env_set(key: &str, val: Exp, env: &Rc<RefCell<Env>>) -> Result<(), Err> {
pub fn env_set(key: &str, val: Exp, env: &Rc<RefCell<Env>>) -> Result<Exp, Err> {
let mut env = env.borrow_mut();
match env.data.get(key) {
Some(_) => {
env.data.insert(key.to_string(), val);
Ok(())
env.data.insert(key.to_string(), val.clone());
Ok(val)
}
None => {
match &env.outer {
@ -355,7 +107,7 @@ pub fn env_set(key: &str, val: Exp, env: &Rc<RefCell<Env>>) -> Result<(), Err> {
enum InnerEnv { Function, Macro }
fn inner_env(kind: InnerEnv, params: &Exp, args: &[Exp], outer: &mut Rc<RefCell<Env>>) -> Result<Rc<RefCell<Env>>, Err> {
let args = match kind {
let mut args = match kind {
InnerEnv::Function => eval_args(args, outer)?,
InnerEnv::Macro => args.to_vec(),
};
@ -365,9 +117,31 @@ fn inner_env(kind: InnerEnv, params: &Exp, args: &[Exp], outer: &mut Rc<RefCell<
data.insert(s.clone(), Exp::List(args));
}
Exp::List(list) => {
if list.len() != args.len() {
let plural = if list.len() == 1 { "" } else { "s" };
return Err(Err::Reason(format!("Expected {} argument{}, got {}", list.len(), plural, args.len())));
let mut list = list.to_vec();
let n = list.len();
let m = args.len();
let mut is_variadic = false;
if n > 0 {
if let Exp::List(l) = &list[n - 1] {
if l.len() == 2 && l[0] == Exp::Sym("splice".to_string()) {
if let Exp::Sym(_) = &l[1] {
is_variadic = true;
list[n - 1] = l[1].clone();
if n <= m {
let rest = args.drain((n - 1)..).collect();
args.push(Exp::List(rest));
}
}
}
}
}
let m = args.len();
if n != m {
let s = if n != 1 { "s" } else { "" };
let a = if is_variadic { "at least " } else { "" };
return Err(Err::Reason(format!("Expected {}{} argument{}, got {}", a, n, s, m)));
}
for (exp, arg) in list.iter().zip(args.iter()) {
if let Exp::Sym(s) = exp {

View File

@ -85,8 +85,7 @@ fn eval_set_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
match &args[0] {
Exp::Sym(name) => {
let exp = eval(&args[1], env)?;
env_set(name, exp, env)?;
Ok(Exp::Sym(name.clone()))
Ok(env_set(name, exp, env)?)
}
_ => Err(Err::Reason("Expected first argument to be a symbol".to_string()))
}

View File

@ -18,7 +18,7 @@ pub fn expand_quasiquote(exp: &Exp) -> Result<Exp, Err> {
Exp::Sym(s) if s == "unquote" => {
Ok(list[1].clone())
}
Exp::List(l) if l.len() == 2 && l[0] == Exp::Sym("unquote-splicing".to_string()) => {
Exp::List(l) if l.len() == 2 && l[0] == Exp::Sym("unquote-splice".to_string()) => {
Ok(Exp::List(vec![
Exp::Sym("append".to_string()),
l[1].clone(),

View File

@ -3,6 +3,7 @@ mod eval;
mod expand;
mod number;
mod parse;
mod primitive;
pub use number::Number;
pub use env::Env;
@ -72,6 +73,22 @@ impl PartialEq for Exp {
}
}
use core::cmp::Ordering;
impl PartialOrd for Exp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Exp::Function(a), Exp::Function(b)) => a.partial_cmp(b),
(Exp::Macro(a), Exp::Macro(b)) => a.partial_cmp(b),
(Exp::List(a), Exp::List(b)) => a.partial_cmp(b),
(Exp::Bool(a), Exp::Bool(b)) => a.partial_cmp(b),
(Exp::Num(a), Exp::Num(b)) => a.partial_cmp(b),
(Exp::Str(a), Exp::Str(b)) => a.partial_cmp(b),
(Exp::Sym(a), Exp::Sym(b)) => a.partial_cmp(b),
_ => None,
}
}
}
impl fmt::Display for Exp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let out = match self {
@ -91,7 +108,7 @@ impl fmt::Display for Exp {
}
}
#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, PartialOrd)]
pub struct Function {
params: Exp,
body: Exp,
@ -422,7 +439,7 @@ fn test_lisp() {
assert_eq!(eval!("(string \"foo \" 3)"), "\"foo 3\"");
assert_eq!(eval!("(eq \"foo\" \"foo\")"), "true");
assert_eq!(eval!("(eq \"foo\" \"bar\")"), "false");
assert_eq!(eval!("(lines \"a\nb\nc\")"), "(\"a\" \"b\" \"c\")");
assert_eq!(eval!("(split \"a\nb\nc\" \"\n\")"), "(\"a\" \"b\" \"c\")");
// apply
assert_eq!(eval!("(apply + '(1 2 3))"), "6");
@ -468,11 +485,15 @@ fn test_lisp() {
assert_eq!(eval!("`(x ,x y)"), "(x a y)");
assert_eq!(eval!("`(x ,x y ,(+ 1 2))"), "(x a y 3)");
// unquote-splicing
// unquote-splice
eval!("(define x '(1 2 3))");
assert_eq!(eval!("`(+ ,x)"), "(+ (1 2 3))");
assert_eq!(eval!("`(+ ,@x)"), "(+ 1 2 3)");
// splice
assert_eq!(eval!("((function (a @b) a) 1 2 3)"), "1");
assert_eq!(eval!("((function (a @b) b) 1 2 3)"), "(2 3)");
// macro
eval!("(define foo 42)");
eval!("(define set-10 (macro (x) `(set ,x 10)))");

View File

@ -201,7 +201,9 @@ impl FromStr for Number {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = Err(Err::Reason("Could not parse number".to_string()));
if s.contains('.') {
if s.is_empty() {
return Ok(Number::Int(0));
} else if s.contains('.') {
if let Ok(n) = s.parse() {
return Ok(Number::Float(n));
}

View File

@ -67,9 +67,15 @@ fn parse_quote(input: &str) -> IResult<&str, Exp> {
Ok((input, Exp::List(list)))
}
fn parse_unquote_splicing(input: &str) -> IResult<&str, Exp> {
fn parse_unquote_splice(input: &str) -> IResult<&str, Exp> {
let (input, list) = preceded(tag(",@"), parse_exp)(input)?;
let list = vec![Exp::Sym("unquote-splicing".to_string()), list];
let list = vec![Exp::Sym("unquote-splice".to_string()), list];
Ok((input, Exp::List(list)))
}
fn parse_splice(input: &str) -> IResult<&str, Exp> {
let (input, list) = preceded(tag("@"), parse_exp)(input)?;
let list = vec![Exp::Sym("splice".to_string()), list];
Ok((input, Exp::List(list)))
}
@ -87,7 +93,7 @@ fn parse_quasiquote(input: &str) -> IResult<&str, Exp> {
fn parse_exp(input: &str) -> IResult<&str, Exp> {
delimited(multispace0, alt((
parse_num, parse_bool, parse_str, parse_list, parse_quote, parse_unquote_splicing, parse_unquote, parse_quasiquote, parse_sym
parse_num, parse_bool, parse_str, parse_list, parse_quote, parse_quasiquote, parse_unquote_splice, parse_unquote, parse_splice, parse_sym
)), multispace0)(input)
}

419
src/usr/lisp/primitive.rs Normal file
View File

@ -0,0 +1,419 @@
use super::list_of_bytes;
use super::list_of_numbers;
use super::parse::parse;
use super::{Err, Exp, Number};
use super::{float, number, string};
use crate::{ensure_length_eq, ensure_length_gt};
use crate::api::fs;
use crate::api::regex::Regex;
use crate::usr::shell;
use alloc::format;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use alloc::vec;
use core::cmp::Ordering::Equal;
use core::convert::TryFrom;
use core::convert::TryInto;
pub fn lisp_eq(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] == nums[1])))
}
pub fn lisp_gt(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] > nums[1])))
}
pub fn lisp_gte(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] >= nums[1])))
}
pub fn lisp_lt(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] < nums[1])))
}
pub fn lisp_lte(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Bool(list_of_numbers(args)?.windows(2).all(|nums| nums[0] <= nums[1])))
}
pub fn lisp_mul(args: &[Exp]) -> Result<Exp, Err> {
let res = list_of_numbers(args)?.iter().fold(Number::Int(1), |acc, a| acc * a.clone());
Ok(Exp::Num(res))
}
pub fn lisp_add(args: &[Exp]) -> Result<Exp, Err> {
let res = list_of_numbers(args)?.iter().fold(Number::Int(0), |acc, a| acc + a.clone());
Ok(Exp::Num(res))
}
pub fn lisp_sub(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_numbers(args)?;
let car = args[0].clone();
if args.len() == 1 {
Ok(Exp::Num(-car))
} else {
let res = args[1..].iter().fold(Number::Int(0), |acc, a| acc + a.clone());
Ok(Exp::Num(car - res))
}
}
pub fn lisp_div(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let mut args = list_of_numbers(args)?;
if args.len() == 1 {
args.insert(0, Number::Int(1));
}
for arg in &args[1..] {
if arg.is_zero() {
return Err(Err::Reason("Division by zero".to_string()));
}
}
let car = args[0].clone();
let res = args[1..].iter().fold(car, |acc, a| acc / a.clone());
Ok(Exp::Num(res))
}
pub fn lisp_mod(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_numbers(args)?;
for arg in &args[1..] {
if arg.is_zero() {
return Err(Err::Reason("Division by zero".to_string()));
}
}
let car = args[0].clone();
let res = args[1..].iter().fold(car, |acc, a| acc % a.clone());
Ok(Exp::Num(res))
}
pub fn lisp_exp(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_numbers(args)?;
let car = args[0].clone();
let res = args[1..].iter().fold(car, |acc, a| acc.pow(a));
Ok(Exp::Num(res))
}
pub fn lisp_shl(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let args = list_of_numbers(args)?;
let res = args[0].clone() << args[1].clone();
Ok(Exp::Num(res))
}
pub fn lisp_shr(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let args = list_of_numbers(args)?;
let res = args[0].clone() >> args[1].clone();
Ok(Exp::Num(res))
}
pub fn lisp_cos(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.cos()))
}
pub fn lisp_acos(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if -1.0 <= float(&args[0])? && float(&args[0])? <= 1.0 {
Ok(Exp::Num(number(&args[0])?.acos()))
} else {
Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string()))
}
}
pub fn lisp_asin(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if -1.0 <= float(&args[0])? && float(&args[0])? <= 1.0 {
Ok(Exp::Num(number(&args[0])?.asin()))
} else {
Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string()))
}
}
pub fn lisp_atan(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.atan()))
}
pub fn lisp_sin(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.sin()))
}
pub fn lisp_tan(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.tan()))
}
pub fn lisp_trunc(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
Ok(Exp::Num(number(&args[0])?.trunc()))
}
pub fn lisp_system(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let cmd = string(&args[0])?;
match shell::exec(&cmd) {
Ok(()) => Ok(Exp::Num(Number::from(0 as u8))),
Err(code) => Ok(Exp::Num(Number::from(code as u8))),
}
}
pub fn lisp_read_file(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let path = string(&args[0])?;
let contents = fs::read_to_string(&path).or(Err(Err::Reason("Could not read file".to_string())))?;
Ok(Exp::Str(contents))
}
pub fn lisp_read_file_bytes(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
let len = number(&args[1])?;
let mut buf = vec![0; len.try_into()?];
let bytes = fs::read(&path, &mut buf).or(Err(Err::Reason("Could not read file".to_string())))?;
buf.resize(bytes, 0);
Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}
pub fn lisp_write_file_bytes(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
match &args[1] {
Exp::List(list) => {
let buf = list_of_bytes(list)?;
let bytes = fs::write(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?;
Ok(Exp::Num(Number::from(bytes)))
}
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
}
}
pub fn lisp_append_file_bytes(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
match &args[1] {
Exp::List(list) => {
let buf = list_of_bytes(list)?;
let bytes = fs::append(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?;
Ok(Exp::Num(Number::from(bytes)))
}
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
}
}
pub fn lisp_string(args: &[Exp]) -> Result<Exp, Err> {
let args: Vec<String> = args.iter().map(|arg| match arg {
Exp::Str(s) => format!("{}", s),
exp => format!("{}", exp),
}).collect();
Ok(Exp::Str(args.join("")))
}
pub fn lisp_string_bytes(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let buf = s.as_bytes();
Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}
pub fn lisp_bytes_string(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => {
let buf = list_of_bytes(list)?;
let s = String::from_utf8(buf).or(Err(Err::Reason("Could not convert to valid UTF-8 string".to_string())))?;
Ok(Exp::Str(s))
}
_ => Err(Err::Reason("Expected arg to be a list".to_string()))
}
}
pub fn lisp_bytes_number(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) { // TODO: default type to "int" and make it optional
(Exp::List(list), Exp::Str(kind)) => {
let bytes = list_of_bytes(list)?;
ensure_length_eq!(bytes, 8);
match kind.as_str() { // TODO: bigint
"int" => Ok(Exp::Num(Number::Int(i64::from_be_bytes(bytes[0..8].try_into().unwrap())))),
"float" => Ok(Exp::Num(Number::Float(f64::from_be_bytes(bytes[0..8].try_into().unwrap())))),
_ => Err(Err::Reason("Invalid number type".to_string())),
}
}
_ => Err(Err::Reason("Expected args to be the number type and a list of bytes".to_string()))
}
}
pub fn lisp_number_bytes(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let n = number(&args[0])?;
Ok(Exp::List(n.to_be_bytes().iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}
pub fn lisp_regex_find(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::Str(regex), Exp::Str(s)) => {
let res = Regex::new(regex).find(s).map(|(a, b)| {
vec![Exp::Num(Number::from(a)), Exp::Num(Number::from(b))]
}).unwrap_or(vec![]);
Ok(Exp::List(res))
}
_ => Err(Err::Reason("Expected args to be a regex and a string".to_string()))
}
}
pub fn lisp_string_number(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let n = s.parse().or(Err(Err::Reason("Could not parse number".to_string())))?;
Ok(Exp::Num(n))
}
pub fn lisp_type(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let exp = match args[0] {
Exp::Primitive(_) => "function",
Exp::Function(_) => "function",
Exp::Macro(_) => "macro",
Exp::List(_) => "list",
Exp::Bool(_) => "boolean",
Exp::Str(_) => "string",
Exp::Sym(_) => "symbol",
Exp::Num(_) => "number",
};
Ok(Exp::Str(exp.to_string()))
}
pub fn lisp_number_type(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match args[0] {
Exp::Num(Number::Int(_)) => Ok(Exp::Str("int".to_string())),
Exp::Num(Number::BigInt(_)) => Ok(Exp::Str("bigint".to_string())),
Exp::Num(Number::Float(_)) => Ok(Exp::Str("float".to_string())),
_ => Err(Err::Reason("Expected arg to be a number".to_string()))
}
}
pub fn lisp_parse(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let (_, exp) = parse(&s)?;
Ok(exp)
}
pub fn lisp_list(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::List(args.to_vec()))
}
pub fn lisp_uniq(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if let Exp::List(list) = &args[0] {
let mut list = list.clone();
list.dedup();
Ok(Exp::List(list))
} else {
Err(Err::Reason("Expected arg to be a list".to_string()))
}
}
pub fn lisp_sort(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if let Exp::List(list) = &args[0] {
let mut list = list.clone();
list.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Equal));
Ok(Exp::List(list))
} else {
Err(Err::Reason("Expected arg to be a list".to_string()))
}
}
pub fn lisp_contains(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
if let Exp::List(list) = &args[0] {
Ok(Exp::Bool(list.contains(&args[1])))
} else {
Err(Err::Reason("Expected first arg to be a list".to_string()))
}
}
pub fn lisp_slice(args: &[Exp]) -> Result<Exp, Err> {
let (a, b) = match args.len() {
2 => (usize::try_from(number(&args[1])?)?, 1),
3 => (usize::try_from(number(&args[1])?)?, usize::try_from(number(&args[2])?)?),
_ => return Err(Err::Reason("Expected 2 or 3 args".to_string())),
};
match &args[0] {
Exp::List(l) => {
let l: Vec<Exp> = l.iter().cloned().skip(a).take(b).collect();
Ok(Exp::List(l))
}
Exp::Str(s) => {
let s: String = s.chars().skip(a).take(b).collect();
Ok(Exp::Str(s))
}
_ => Err(Err::Reason("Expected first arg to be a list or a number".to_string()))
}
}
pub fn lisp_chunks(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::List(list), Exp::Num(num)) => {
let n = usize::try_from(num.clone())?;
Ok(Exp::List(list.chunks(n).map(|a| Exp::List(a.to_vec())).collect()))
}
_ => Err(Err::Reason("Expected a list and a number".to_string()))
}
}
pub fn lisp_split(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::Str(string), Exp::Str(pattern)) => {
let list = if pattern.is_empty() {
// NOTE: "abc".split("") => ["", "b", "c", ""]
string.chars().map(|s| Exp::Str(s.to_string())).collect()
} else {
string.split(pattern).map(|s| Exp::Str(s.to_string())).collect()
};
Ok(Exp::List(list))
}
_ => Err(Err::Reason("Expected a string and a pattern".to_string()))
}
}
pub fn lisp_trim(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
if let Exp::Str(s) = &args[0] {
Ok(Exp::Str(s.trim().to_string()))
} else {
Err(Err::Reason("Expected a string and a pattern".to_string()))
}
}
pub fn lisp_length(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => Ok(Exp::Num(Number::from(list.len()))),
Exp::Str(string) => Ok(Exp::Num(Number::from(string.chars().count()))),
_ => Err(Err::Reason("Expected arg to be a list or a string".to_string()))
}
}
pub fn lisp_append(args: &[Exp]) -> Result<Exp, Err> {
let mut res = vec![];
for arg in args {
if let Exp::List(list) = arg {
res.extend_from_slice(list);
} else {
return Err(Err::Reason("Expected arg to be a list".to_string()))
}
}
Ok(Exp::List(res))
}