mirror of https://github.com/vinc/moros.git
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:
parent
db03702c2e
commit
b1d7a1f929
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,3 +21,6 @@
|
|||
|
||||
(var rest
|
||||
(macro args `(tail ,@args)))
|
||||
|
||||
(var help
|
||||
(macro args `(doc ,@args)))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()))) })))
|
||||
}
|
||||
|
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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('.') {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue