mirror of https://github.com/vinc/moros.git
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:
parent
c18171ec95
commit
87eb51d1ff
2
Makefile
2
Makefile
|
@ -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
|
||||
|
|
13
doc/lisp.md
13
doc/lisp.md
|
@ -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:
|
||||
|
|
|
@ -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 ""))
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)))");
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
Loading…
Reference in New Issue