Add docstring to lisp (#490)

* Add docstring to lisp

* Add expected macro

* Add could_not macro

* Improve naming conventions

* Refactor code

* Add env function

* Update changelog
This commit is contained in:
Vincent Ollivier 2023-05-29 10:15:11 +02:00 committed by GitHub
parent db03702c2e
commit b1d7a1f929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 200 additions and 98 deletions

View File

@ -1,6 +1,7 @@
# Changelog
## Unreleased
- Add docstring to lisp (#490)
- Add full support for comments in lisp (#489)
- Add parenthesis matching to editor (#488)
- Upgrade smoltcp from 0.8.2 to 0.9.1 (#484)

View File

@ -33,9 +33,10 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!
- `define-function` (aliased to `def-fun`)
- `define-macro` (aliased to `def-mac`)
- `apply`
- `do`
- `doc`
- `eval`
- `expand`
- `do`
- `load`
### Primitive Operators
@ -135,11 +136,14 @@ Would produce the following output:
(= i 10) # => true
(def (map f ls)
"Apply function to list"
(if (nil? ls) nil
(cons
(f (first ls))
(map f (rest ls)))))
(doc map) # => "Apply function to list"
(var bar (quote (1 2 3)))
(var bar '(1 2 3)) # Shortcut
@ -188,3 +192,5 @@ Rewrite parts of the code and add new functions and examples.
### 0.5.0 (unpublished)
- Rename or add aliases to many functions
- Add full support for line and inline comments
- Add params to function representations
- Add docstring to functions

View File

@ -21,3 +21,6 @@
(var rest
(macro args `(tail ,@args)))
(var help
(macro args `(doc ,@args)))

View File

@ -39,56 +39,69 @@
`((fun ,params ,body) ,@values))
(def (reduce f ls)
"Reduce the elements of the list with the function"
(if (nil? (tail ls)) (head ls)
(f (head ls) (reduce f (tail ls)))))
(def (map f ls)
"Apply the function to the elements of the list"
(if (nil? ls) nil
(cons
(f (head ls))
(map f (tail ls)))))
(def (filter f ls)
"Filter the elements of the list with the function"
(if (nil? ls) nil
(if (f (head ls))
(cons (head ls) (filter f (tail ls)))
(filter f (tail ls)))))
(def (intersection a b)
"Return elements found in both lists"
(filter (fun (x) (contains? b x)) a))
(def (reverse x)
"Reverse list"
(if (nil? x) x
(append (reverse (tail x)) (cons (head x) '()))))
(def (range i n)
(def (range start stop)
"Return a list of integers from start to stop excluded"
(if (= i n) nil
(append (list i) (range (+ i 1) n))))
(def (min lst)
"Return the minimum element of the list"
(head (sort lst)))
(def (max lst)
"Return the maximum element of the list"
(head (reverse (sort lst))))
(def (abs x)
(if (> x 0) x (- x)))
(def (join-string ls s)
"Join the elements of the list with the string"
(reduce (fun (x y) (string x s y)) ls))
(def (read-line)
"Read line from the console"
(binary->string (reverse (tail (reverse (read-file-binary "/dev/console" 256))))))
(def (read-char)
"Read char from the console"
(binary->string (read-file-binary "/dev/console" 4)))
(def (p exp)
"Print expression to the console"
(do
(append-file-binary "/dev/console" (string->binary (string exp)))
'()))
(def (print exp)
"Print expression to the console with a newline"
(p (string exp "\n")))
(def (uptime)
@ -98,21 +111,26 @@
(binary->number (read-file-binary "/dev/clk/realtime" 8) "float"))
(def (write-file path s)
"Write string to file"
(write-file-binary path (string->binary s)))
(def (append-file path s)
"Append string to file"
(append-file-binary path (string->binary s)))
(def (regex-match? pattern s)
(not (nil? (regex-find pattern str))))
(def (lines contents)
"Split contents into a list of lines"
(split (trim contents) "\n"))
(def (words contents)
"Split contents into a list of words"
(split contents " "))
(def (chars contents)
"Split contents into a list of chars"
(split contents ""))
(def (first lst)
@ -128,24 +146,21 @@
(nth lst
(if (= (length lst) 0) 0 (- (length lst) 1))))
# Short aliases
(var str string)
(var num-type number-type)
(var join-str join-string)
(var str->num string->number)
(var str->bin string->binary)
(var num->bin number->binary)
(var bin->str binary->string)
(var bin->num binary->number)
(var bool? boolean?)
(var str? string?)
(var sym? symbol?)
(var num? number?)
(var fun? function?)
(var mac? macro?)
(var len length)
(var rev reverse)
(var uniq unique)

View File

@ -1,14 +1,16 @@
use super::FORMS;
use super::FUNCTIONS;
use super::primitive;
use super::eval::BUILT_INS;
use super::eval::eval_args;
use super::{Err, Exp, Number};
use crate::{could_not, expected};
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::cell::RefCell;
use core::f64::consts::PI;
@ -71,11 +73,20 @@ pub fn default_env() -> Rc<RefCell<Env>> {
data.insert("append".to_string(), Exp::Primitive(primitive::lisp_append));
// Setup autocompletion
*FORMS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();
*FUNCTIONS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();
Rc::new(RefCell::new(Env { data, outer: None }))
}
pub fn env_keys(env: &Rc<RefCell<Env>>) -> Result<Vec<String>, Err> {
let env = env.borrow_mut();
let mut keys: Vec<String> = env.data.keys().map(|k| k.clone()).collect();
if let Some(outer_env) = &env.outer {
keys.extend_from_slice(&env_keys(outer_env)?);
}
Ok(keys)
}
pub fn env_get(key: &str, env: &Rc<RefCell<Env>>) -> Result<Exp, Err> {
let env = env.borrow_mut();
match env.data.get(key) {
@ -83,7 +94,7 @@ pub fn env_get(key: &str, env: &Rc<RefCell<Env>>) -> Result<Exp, Err> {
None => {
match &env.outer {
Some(outer_env) => env_get(key, outer_env.borrow()),
None => Err(Err::Reason(format!("Unexpected symbol '{}'", key))),
None => could_not!("find symbol '{}'", key),
}
}
}
@ -99,7 +110,7 @@ pub fn env_set(key: &str, val: Exp, env: &Rc<RefCell<Env>>) -> Result<Exp, Err>
None => {
match &env.outer {
Some(outer_env) => env_set(key, val, outer_env.borrow()),
None => Err(Err::Reason(format!("Unexpected symbol '{}'", key))),
None => could_not!("find symbol '{}'", key),
}
}
}
@ -142,17 +153,17 @@ fn inner_env(kind: InnerEnv, params: &Exp, args: &[Exp], outer: &mut Rc<RefCell<
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)));
return expected!("{}{} argument{}, got {}", a, n, s, m);
}
for (exp, arg) in list.iter().zip(args.iter()) {
if let Exp::Sym(s) = exp {
data.insert(s.clone(), arg.clone());
} else {
return Err(Err::Reason("Expected symbols in the argument list".to_string()));
return expected!("params to be a list of symbols");
}
}
}
_ => return Err(Err::Reason("Expected args form to be a list".to_string())),
_ => return expected!("params to be a list"),
}
Ok(Rc::new(RefCell::new(Env { data, outer: Some(Rc::new(RefCell::new(outer.borrow_mut().clone()))) })))
}

View File

@ -1,9 +1,10 @@
use super::{Err, Exp, Env, Function, parse_eval};
use super::env::{env_get, env_set, function_env};
use super::env::{env_keys, env_get, env_set, function_env};
use super::expand::expand;
use crate::could_not;
use super::string;
use crate::{ensure_length_eq, ensure_length_gt};
use crate::{ensure_length_eq, ensure_length_gt, expected};
use crate::api::fs;
use alloc::boxed::Box;
@ -41,7 +42,7 @@ fn eval_head_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
ensure_length_gt!(list, 0);
Ok(list[0].clone())
},
_ => Err(Err::Reason("Expected list form".to_string())),
_ => expected!("first argument to be a list"),
}
}
@ -52,7 +53,7 @@ fn eval_tail_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
ensure_length_gt!(list, 0);
Ok(Exp::List(list[1..].to_vec()))
},
_ => Err(Err::Reason("Expected list form".to_string())),
_ => expected!("first argument to be a list"),
}
}
@ -63,7 +64,7 @@ fn eval_cons_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
list.insert(0, eval(&args[0], env)?);
Ok(Exp::List(list))
},
_ => Err(Err::Reason("Expected list form".to_string())),
_ => expected!("first argument to be a list"),
}
}
@ -75,7 +76,7 @@ pub fn eval_variable_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Ex
env.borrow_mut().data.insert(name.clone(), exp);
Ok(Exp::Sym(name.clone()))
}
_ => Err(Err::Reason("Expected first argument to be a symbol".to_string()))
_ => expected!("first argument to be a symbol")
}
}
@ -86,10 +87,16 @@ fn eval_set_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
let exp = eval(&args[1], env)?;
Ok(env_set(name, exp, env)?)
}
_ => Err(Err::Reason("Expected first argument to be a symbol".to_string()))
_ => expected!("first argument to be a symbol")
}
}
fn eval_env_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 0);
let keys = env_keys(env)?.iter().map(|k| Exp::Sym(k.clone())).collect();
Ok(Exp::List(keys))
}
fn eval_while_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_gt!(args, 1);
let cond = &args[0];
@ -107,7 +114,7 @@ fn eval_apply_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
let mut args = args.to_vec();
match eval(&args.pop().unwrap(), env) {
Ok(Exp::List(rest)) => args.extend(rest),
_ => return Err(Err::Reason("Expected last argument to be a list".to_string())),
_ => return expected!("last argument to be a list"),
}
eval(&Exp::List(args.to_vec()), env)
}
@ -129,7 +136,7 @@ fn eval_do_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
fn eval_load_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let path = string(&args[0])?;
let mut input = fs::read_to_string(&path).or(Err(Err::Reason(format!("File not found '{}'", path))))?;
let mut input = fs::read_to_string(&path).or(could_not!("find file '{}'", path))?;
loop {
let (rest, _) = parse_eval(&input, env)?;
if rest.is_empty() {
@ -140,11 +147,21 @@ fn eval_load_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
Ok(Exp::Bool(true))
}
fn eval_doc_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::Primitive(_) => Ok(Exp::Str("".to_string())),
Exp::Function(f) => Ok(Exp::Str(f.doc.unwrap_or("".to_string()))),
Exp::Macro(m) => Ok(Exp::Str(m.doc.unwrap_or("".to_string()))),
_ => expected!("function or macro"),
}
}
pub fn eval_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Vec<Exp>, Err> {
args.iter().map(|x| eval(x, env)).collect()
}
pub const BUILT_INS: [&str; 24] = [
pub const BUILT_INS: [&str; 26] = [
"quote", "quasiquote", "unquote", "unquote-splicing",
"atom?", "equal?", "head", "tail", "cons",
"if", "cond", "while",
@ -154,7 +171,9 @@ pub const BUILT_INS: [&str; 24] = [
"set",
"apply", "eval", "expand",
"do",
"load"
"load",
"doc",
"env"
];
pub fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
@ -184,7 +203,9 @@ pub fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
Exp::Sym(s) if s == "eval" => return eval_eval_args(args, env),
Exp::Sym(s) if s == "do" => return eval_do_args(args, env),
Exp::Sym(s) if s == "load" => return eval_load_args(args, env),
Exp::Sym(s) if s == "doc" => return eval_doc_args(args, env),
Exp::Sym(s) if s == "variable" => return eval_variable_args(args, env),
Exp::Sym(s) if s == "env" => return eval_env_args(args, env),
Exp::Sym(s) if s == "expand" => {
ensure_length_eq!(args, 1);
return expand(&args[0], env);
@ -200,19 +221,15 @@ pub fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
}
exp = &exp_tmp;
}
Exp::Sym(s) if s == "function" => {
ensure_length_eq!(args, 2);
return Ok(Exp::Function(Box::new(Function {
params: args[0].clone(),
body: args[1].clone(),
})))
}
Exp::Sym(s) if s == "macro" => {
ensure_length_eq!(args, 2);
return Ok(Exp::Macro(Box::new(Function {
params: args[0].clone(),
body: args[1].clone(),
})))
Exp::Sym(s) if s == "function" || s == "macro" => {
let (params, body, doc) = match args.len() {
2 => (args[0].clone(), args[1].clone(), None),
3 => (args[0].clone(), args[2].clone(), Some(string(&args[1])?)),
_ => return expected!("3 or 4 arguments"),
};
let f = Box::new(Function { params, body, doc });
let exp = if s == "function" { Exp::Function(f) } else { Exp::Macro(f) };
return Ok(exp);
}
_ => {
match eval(&list[0], env)? {
@ -225,12 +242,12 @@ pub fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
Exp::Primitive(f) => {
return f(&eval_args(args, env)?)
},
_ => return Err(Err::Reason("First form must be a function".to_string())),
_ => return expected!("first argument to be a function"),
}
}
}
},
_ => return Err(Err::Reason("Unexpected form".to_string())),
_ => return Err(Err::Reason("Unexpected argument".to_string())),
}
}
}

View File

@ -2,7 +2,7 @@ use super::{Err, Exp, Env};
use super::env::{env_get, macro_env};
use super::eval::eval;
use crate::{ensure_length_eq, ensure_length_gt};
use crate::{ensure_length_eq, ensure_length_gt, ensure_list, ensure_string, expected};
use alloc::format;
use alloc::rc::Rc;
@ -56,21 +56,34 @@ pub fn expand(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
expand_quasiquote(&list[1])
}
Exp::Sym(s) if s == "define-function" || s == "define" => {
ensure_length_eq!(list, 3);
match (&list[1], &list[2]) {
(Exp::List(args), Exp::List(_)) => {
let (params, body) = match list.len() {
3 => {
ensure_list!(&list[2]);
(&list[1], &list[2])
}
4 => {
ensure_string!(&list[2]);
ensure_list!(&list[3]);
(&list[1], &list[3])
}
_ => return expected!("3 or 4 arguments"),
};
match params {
Exp::List(args) => {
ensure_length_gt!(args, 0);
let name = args[0].clone();
let args = Exp::List(args[1..].to_vec());
let body = expand(&list[2], env)?;
let body = expand(&body, env)?;
let mut function = vec![Exp::Sym("function".to_string()), args, body];
if list.len() == 4 {
function.insert(2, list[2].clone());
}
Ok(Exp::List(vec![
Exp::Sym("variable".to_string()), name, Exp::List(vec![
Exp::Sym("function".to_string()), args, body
])
Exp::Sym("variable".to_string()), name, Exp::List(function)
]))
}
(Exp::Sym(_), _) => expand_list(list, env),
_ => Err(Err::Reason("Expected first argument to be a symbol or a list".to_string()))
Exp::Sym(_) => expand_list(list, env),
_ => expected!("first argument to be a symbol or a list")
}
}
Exp::Sym(s) if s == "define-macro" => {
@ -88,7 +101,7 @@ pub fn expand(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
]))
}
(Exp::Sym(_), _) => expand_list(list, env),
_ => Err(Err::Reason("Expected first argument to be a symbol or a list".to_string()))
_ => expected!("first argument to be a symbol or a list")
}
}
Exp::Sym(s) if s == "cond" => {
@ -103,7 +116,7 @@ pub fn expand(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
}
Ok(Exp::List(res))
} else {
Err(Err::Reason("Expected lists of predicate and expression".to_string()))
expected!("lists of predicate and expression")
}
}
Exp::Sym(s) => {

View File

@ -92,9 +92,9 @@ impl PartialOrd for Exp {
impl fmt::Display for Exp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let out = match self {
Exp::Primitive(_) => "<function>".to_string(),
Exp::Function(_) => "<function>".to_string(),
Exp::Macro(_) => "<macro>".to_string(),
Exp::Primitive(_) => format!("(function args)"),
Exp::Function(f) => format!("(function {})", f.params),
Exp::Macro(m) => format!("(macro {})", m.params),
Exp::Bool(a) => a.to_string(),
Exp::Num(n) => n.to_string(),
Exp::Sym(s) => s.clone(),
@ -112,6 +112,7 @@ impl fmt::Display for Exp {
pub struct Function {
params: Exp,
body: Exp,
doc: Option<String>,
}
#[derive(Debug)]
@ -120,7 +121,7 @@ pub enum Err {
}
lazy_static! {
pub static ref FORMS: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub static ref FUNCTIONS: Mutex<Vec<String>> = Mutex::new(Vec::new());
}
#[macro_export]
@ -128,7 +129,7 @@ macro_rules! ensure_length_eq {
($list:expr, $count:expr) => {
if $list.len() != $count {
let plural = if $count != 1 { "s" } else { "" };
return Err(Err::Reason(format!("Expected {} expression{}", $count, plural)))
return expected!("{} expression{}", $count, plural);
}
};
}
@ -138,11 +139,47 @@ macro_rules! ensure_length_gt {
($list:expr, $count:expr) => {
if $list.len() <= $count {
let plural = if $count != 1 { "s" } else { "" };
return Err(Err::Reason(format!("Expected more than {} expression{}", $count, plural)))
return expected!("more than {} expression{}", $count, plural);
}
};
}
#[macro_export]
macro_rules! ensure_string {
($exp:expr) => {
match $exp {
Exp::Str(_) => {},
_ => return expected!("a string"),
}
};
}
#[macro_export]
macro_rules! ensure_list {
($exp:expr) => {
match $exp {
Exp::List(_) => {},
_ => return expected!("a list"),
}
};
}
#[macro_export]
macro_rules! expected {
($($arg:tt)*) => ({
use alloc::format;
Err(Err::Reason(format!("Expected {}", format_args!($($arg)*))))
});
}
#[macro_export]
macro_rules! could_not {
($($arg:tt)*) => ({
use alloc::format;
Err(Err::Reason(format!("Could not {}", format_args!($($arg)*))))
});
}
pub fn bytes(args: &[Exp]) -> Result<Vec<u8>, Err> {
args.iter().map(byte).collect()
}
@ -158,21 +195,21 @@ pub fn numbers(args: &[Exp]) -> Result<Vec<Number>, Err> {
pub fn string(exp: &Exp) -> Result<String, Err> {
match exp {
Exp::Str(s) => Ok(s.to_string()),
_ => Err(Err::Reason("Expected a string".to_string())),
_ => expected!("a string"),
}
}
pub fn number(exp: &Exp) -> Result<Number, Err> {
match exp {
Exp::Num(num) => Ok(num.clone()),
_ => Err(Err::Reason("Expected a number".to_string())),
_ => expected!("a number"),
}
}
pub fn float(exp: &Exp) -> Result<f64, Err> {
match exp {
Exp::Num(num) => Ok(num.into()),
_ => Err(Err::Reason("Expected a float".to_string())),
_ => expected!("a float"),
}
}
@ -193,8 +230,8 @@ fn lisp_completer(line: &str) -> Vec<String> {
let mut entries = Vec::new();
if let Some(last_word) = line.split_whitespace().next_back() {
if let Some(f) = last_word.strip_prefix('(') {
for form in &*FORMS.lock() {
if let Some(entry) = form.strip_prefix(f) {
for function in &*FUNCTIONS.lock() {
if let Some(entry) = function.strip_prefix(f) {
entries.push(entry.into());
}
}
@ -271,9 +308,7 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
input = rest;
}
Err(Err::Reason(msg)) => {
let csi_error = Style::color("LightRed");
let csi_reset = Style::reset();
eprintln!("{}Error:{} {}", csi_error, csi_reset, msg);
error!("{}", msg);
return Err(ExitCode::Failure);
}
}

View File

@ -1,7 +1,7 @@
use super::Err;
use crate::could_not;
use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;
use core::convert::TryFrom;
use core::fmt;
@ -200,7 +200,7 @@ impl FromStr for Number {
type Err = Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = Err(Err::Reason("Could not parse number".to_string()));
let err = could_not!("parse number");
if s.is_empty() {
return Ok(Number::Int(0));
} else if s.contains('.') {

View File

@ -1,4 +1,5 @@
use super::{Err, Exp, Number};
use crate::could_not;
use alloc::string::String;
use alloc::string::ToString;
@ -111,8 +112,8 @@ pub fn parse(input: &str)-> Result<(String, Exp), Err> {
Ok((input, exp)) => Ok((input.to_string(), exp)),
Err(Error(err)) if !err.input.is_empty() => {
let line = err.input.lines().next().unwrap();
Err(Err::Reason(format!("Could not parse '{}'", line)))
could_not!("parse '{}'", line)
}
_ => Err(Err::Reason(format!("Could not parse input"))),
_ => could_not!("parse input"),
}
}

View File

@ -3,7 +3,7 @@ use super::{Err, Exp, Number};
use super::{float, number, string};
use super::{bytes, numbers, strings};
use crate::{ensure_length_eq, ensure_length_gt};
use crate::{ensure_length_eq, ensure_length_gt, expected, could_not};
use crate::api::fs;
use crate::api::regex::Regex;
use crate::usr::shell;
@ -67,7 +67,7 @@ pub fn lisp_div(args: &[Exp]) -> Result<Exp, Err> {
}
for arg in &args[1..] {
if arg.is_zero() {
return Err(Err::Reason("Division by zero".to_string()));
return expected!("non-zero number");
}
}
let head = args[0].clone();
@ -80,7 +80,7 @@ pub fn lisp_mod(args: &[Exp]) -> Result<Exp, Err> {
let args = numbers(args)?;
for arg in &args[1..] {
if arg.is_zero() {
return Err(Err::Reason("Division by zero".to_string()));
return expected!("non-zero number");
}
}
let head = args[0].clone();
@ -120,7 +120,7 @@ pub fn lisp_acos(args: &[Exp]) -> Result<Exp, Err> {
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()))
expected!("argument to be between -1.0 and 1.0")
}
}
@ -129,7 +129,7 @@ pub fn lisp_asin(args: &[Exp]) -> Result<Exp, Err> {
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()))
expected!("argument to be between -1.0 and 1.0")
}
}
@ -165,7 +165,7 @@ pub fn lisp_system(args: &[Exp]) -> Result<Exp, Err> {
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())))?;
let contents = fs::read_to_string(&path).or(could_not!("read file"))?;
Ok(Exp::Str(contents))
}
@ -174,7 +174,7 @@ pub fn lisp_read_file_bytes(args: &[Exp]) -> Result<Exp, Err> {
let path = string(&args[0])?;
let len = number(&args[1])?;
let mut buf = vec![0; len.try_into()?];
let n = fs::read(&path, &mut buf).or(Err(Err::Reason("Could not read file".to_string())))?;
let n = fs::read(&path, &mut buf).or(could_not!("read file"))?;
buf.resize(n, 0);
Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}
@ -185,10 +185,10 @@ pub fn lisp_write_file_bytes(args: &[Exp]) -> Result<Exp, Err> {
match &args[1] {
Exp::List(list) => {
let buf = bytes(list)?;
let n = fs::write(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?;
let n = fs::write(&path, &buf).or(could_not!("write file"))?;
Ok(Exp::Num(Number::from(n)))
}
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
_ => expected!("second argument to be a list")
}
}
@ -198,10 +198,10 @@ pub fn lisp_append_file_bytes(args: &[Exp]) -> Result<Exp, Err> {
match &args[1] {
Exp::List(list) => {
let buf = bytes(list)?;
let n = fs::append(&path, &buf).or(Err(Err::Reason("Could not write file".to_string())))?;
let n = fs::append(&path, &buf).or(could_not!("write file"))?;
Ok(Exp::Num(Number::from(n)))
}
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
_ => expected!("second argument to be a list")
}
}
@ -225,10 +225,10 @@ pub fn lisp_bytes_string(args: &[Exp]) -> Result<Exp, Err> {
match &args[0] {
Exp::List(list) => {
let buf = bytes(list)?;
let s = String::from_utf8(buf).or(Err(Err::Reason("Could not convert to valid UTF-8 string".to_string())))?;
let s = String::from_utf8(buf).or(expected!("a valid UTF-8 string"))?;
Ok(Exp::Str(s))
}
_ => Err(Err::Reason("Expected arg to be a list".to_string()))
_ => expected!("argument to be a list")
}
}
@ -241,10 +241,10 @@ pub fn lisp_bytes_number(args: &[Exp]) -> Result<Exp, Err> {
match kind.as_str() { // TODO: bigint
"int" => Ok(Exp::Num(Number::Int(i64::from_be_bytes(buf[0..8].try_into().unwrap())))),
"float" => Ok(Exp::Num(Number::Float(f64::from_be_bytes(buf[0..8].try_into().unwrap())))),
_ => Err(Err::Reason("Invalid number type".to_string())),
_ => expected!("valid number type"),
}
}
_ => Err(Err::Reason("Expected args to be the number type and a list of bytes".to_string()))
_ => expected!("arguments to be the type of number and a list of bytes")
}
}
@ -263,14 +263,14 @@ pub fn lisp_regex_find(args: &[Exp]) -> Result<Exp, Err> {
}).unwrap_or(vec![]);
Ok(Exp::List(res))
}
_ => Err(Err::Reason("Expected args to be a regex and a string".to_string()))
_ => expected!("arguments to be a regex and a 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())))?;
let n = s.parse().or(could_not!("parse number"))?;
Ok(Exp::Num(n))
}
@ -295,7 +295,7 @@ pub fn lisp_number_type(args: &[Exp]) -> Result<Exp, Err> {
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()))
_ => expected!("argument to be a number")
}
}
@ -317,7 +317,7 @@ pub fn lisp_unique(args: &[Exp]) -> Result<Exp, Err> {
list.dedup();
Ok(Exp::List(list))
} else {
Err(Err::Reason("Expected arg to be a list".to_string()))
expected!("argument to be a list")
}
}
@ -328,7 +328,7 @@ pub fn lisp_sort(args: &[Exp]) -> Result<Exp, Err> {
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()))
expected!("argument to be a list")
}
}
@ -337,7 +337,7 @@ pub fn lisp_contains(args: &[Exp]) -> Result<Exp, Err> {
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()))
expected!("first argument to be a list")
}
}
@ -359,7 +359,7 @@ pub fn lisp_nth(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Str("".to_string()))
}
}
_ => Err(Err::Reason("Expected first arg to be a list or a string".to_string()))
_ => expected!("first argument to be a list or a string")
}
}
@ -367,7 +367,7 @@ 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())),
_ => return expected!("2 or 3 arguments"),
};
match &args[0] {
Exp::List(l) => {
@ -378,7 +378,7 @@ pub fn lisp_slice(args: &[Exp]) -> Result<Exp, Err> {
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 string".to_string()))
_ => expected!("first argument to be a list or a string")
}
}
@ -389,7 +389,7 @@ pub fn lisp_chunks(args: &[Exp]) -> Result<Exp, Err> {
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()))
_ => expected!("a list and a number")
}
}
@ -405,7 +405,7 @@ pub fn lisp_split(args: &[Exp]) -> Result<Exp, Err> {
};
Ok(Exp::List(list))
}
_ => Err(Err::Reason("Expected a string and a pattern".to_string()))
_ => expected!("a string and a pattern")
}
}
@ -414,7 +414,7 @@ pub fn lisp_trim(args: &[Exp]) -> Result<Exp, Err> {
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()))
expected!("a string and a pattern")
}
}
@ -423,7 +423,7 @@ pub fn lisp_length(args: &[Exp]) -> Result<Exp, Err> {
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()))
_ => expected!("a list or a string")
}
}
@ -433,7 +433,7 @@ pub fn lisp_append(args: &[Exp]) -> Result<Exp, Err> {
if let Exp::List(list) = arg {
res.extend_from_slice(list);
} else {
return Err(Err::Reason("Expected arg to be a list".to_string()))
return expected!("a list")
}
}
Ok(Exp::List(res))