Add namespaces to lisp (#511)

* Add file module

* Add r and t literals

* Add string support in contains

* Add number, string, and regex modules

* Fix tests

* Fix lib

* Fix split in lib

* The % operator is for remainder instead of modulo

* Rename internal binary functions

* Add mod and rem and alias % to the latter

* Use dot instead of colon for namespacing

* Rename *-file* functions

* Update www

* Update changelog
This commit is contained in:
Vincent Ollivier 2023-07-02 09:20:49 +02:00 committed by GitHub
parent 314118c35f
commit f806c3a338
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 303 additions and 187 deletions

View File

@ -1,6 +1,7 @@
# Changelog
## Unreleased
- Add namespaces to lisp ([#511](https://github.com/vinc/moros/pull/511))
- Update smoltcp from 0.9.1 to 0.10.0 ([#510](https://github.com/vinc/moros/pull/510))
## 0.10.0 (2023-06-21)

View File

@ -40,21 +40,20 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!
- `load`
### Primitive Operators
- `append`
- `type`, `number-type` (aliased to `num-type`)
- `type`, `number.type` (aliased to `num.type`)
- `string` (aliased to `str`)
- `string->number` (aliased to to `str->num`)
- `string->binary` and `binary->string` (aliased to `str->bin` and `bin->str`)
- `number->binary` and `binary->number` (aliased to `num->bin` and `bin->num`)
- `regex-find`
- `regex.find`
- `system`
- Arithmetic operations: `+`, `-`, `*`, `/`, `%`, `^`, `abs`
- Arithmetic operations: `+`, `-`, `*`, `/`, `^`, `abs`, `mod`, `rem` (aliased to `%`)
- Trigonometric functions: `acos`, `asin`, `atan`, `cos`, `sin`, `tan`
- Comparisons: `>`, `<`, `>=`, `<=`, `=`
- File IO: `read-file`, `read-file-binary`, `write-file-binary`, `append-file-binary`
- List: `chunks`, `sort`, `unique` (aliased to `uniq`), `min`, `max`
- String: `trim`, `split`
- Enumerable: `length` (aliased to `len`), `nth`, `first`, `second`, `third`, `last`, `rest`, `slice`
- String: `string.trim`, `string.split`
- List: `concat`, `chunks`, `sort`, `unique` (aliased to `uniq`), `min`, `max`
- File: `file.size`, `file.open`, `file.close`, `file.read`, `file.write`
### Core Library
- `nil`, `nil?`, `list?`
@ -64,12 +63,15 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!
- `map`, `reduce`, `reverse` (aliased to `rev`), `range`, `filter`, `intersection`
- `not`, `and`, `or`
- `let`
- `join-string` (aliased to `join-str`), `lines`, `words`, `chars`
- `string.join` (aliased to `str.join`), `lines`, `words`, `chars`
- `regex.match?`
### File Library
- `read`, `write`, `append`
- `read-binary`, `write-binary`, `append-binary`
- `read-line`, `read-char`
- `p`, `print`
- `write-file`, `append-file`
- `uptime`, `realtime`
- `regex-match?`
- `p`, `print`
### Compatibility Library
@ -194,3 +196,6 @@ language and reading from the filesystem.
- Add full support for line and inline comments
- Add params to function representations
- Add docstring to functions
### Unreleased
- Add file, number, string, and regex namespaces

View File

@ -64,12 +64,12 @@
(def (reverse x)
"Reverse list"
(if (nil? x) x
(append (reverse (tail x)) (cons (head x) '()))))
(concat (reverse (tail x)) (cons (head x) '()))))
(def (range start stop)
"Return a list of integers from start to stop excluded"
(if (= start stop) nil
(append (list start) (range (+ start 1) stop))))
(concat (list start) (range (+ start 1) stop))))
(def (min lst)
"Return the minimum element of the list"
@ -82,56 +82,27 @@
(def (abs x)
(if (> x 0) x (- x)))
(def (join-string ls s)
(def (mod a b)
(rem (+ (rem a b) b) b))
(def (string.join 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 (regex.match? pattern s)
(not (nil? (regex.find pattern str))))
(def (read-char)
"Read char from the console"
(binary->string (read-file-binary "/dev/console" 4)))
(def (lines text)
"Split text into a list of lines"
(string.split (string.trim text) "\n"))
(def (p exp)
"Print expression to the console"
(do
(append-file-binary "/dev/console" (string->binary (string exp)))
'()))
(def (words text)
"Split text into a list of words"
(string.split text " "))
(def (print exp)
"Print expression to the console with a newline"
(p (string exp "\n")))
(def (uptime)
(binary->number (read-file-binary "/dev/clk/uptime" 8) "float"))
(def (realtime)
(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 (chars text)
"Split text into a list of chars"
(string.split text ""))
(def (first lst)
(nth lst 0))
@ -147,9 +118,13 @@
(if (= (length lst) 0) 0 (- (length lst) 1))))
# Short aliases
(var % rem)
(var str string)
(var num-type number-type)
(var join-str join-string)
(var str.split string.split)
(var str.join string.join)
(var str.trim string.trim)
(var num.type number.type)
(var str->num string->number)
(var str->bin string->binary)
(var num->bin number->binary)
@ -164,3 +139,5 @@
(var len length)
(var rev reverse)
(var uniq unique)
(load "/lib/lisp/file.lsp")

72
dsk/lib/lisp/file.lsp Normal file
View File

@ -0,0 +1,72 @@
(var stdin 0)
(var stdout 1)
(var stderr 2)
# Read
(def (read-binary path)
"Read binary file"
(do
(var size (file.size path))
(var file (file.open path "r"))
(var data (file.read file size))
(file.close file)
data))
(def (read path)
"Read text file"
(binary->string (read-binary path)))
# Write
(def (write-binary path data)
"Write binary to file"
(do
(var file (file.open path "w"))
(file.write file data)
(file.close file)))
(def (write path text)
"Write text to file"
(write-binary path (string->binary text)))
# Append
(def (append-binary path data)
"Append binary to file"
(do
(var file (file.open path "a"))
(file.write file data)
(file.close file)))
(def (append path text)
"Append text to file"
(append-binary path (string->binary text)))
# Console
(def (read-line)
"Read line from the console"
(string.trim (binary->string (file.read stdin 256))))
(def (read-char)
"Read char from the console"
(binary->string (file.read stdin 4)))
(def (p exp)
"Print expression to the console"
(do
(file.write stdout (string->binary (string exp)))
'()))
(def (print exp)
"Print expression to the console with a newline"
(p (string exp "\n")))
# Special
(def (uptime)
(binary->number (read-binary "/dev/clk/uptime") "float"))
(def (realtime)
(binary->number (read-binary "/dev/clk/realtime") "float"))

View File

@ -15,7 +15,7 @@
(str " " (f c) (if (< c 100) " " "") c (ansi-color 0 0)))
(def (colors fs i j)
(join-str (map (fun (c) (color fs c)) (range i j)) ""))
(str.join (map (fun (c) (color fs c)) (range i j)) ""))
(print (colors fg 30 38))
(print (colors fg 90 98))

View File

@ -50,8 +50,9 @@ pub fn copy_files(verbose: bool) {
copy_file("/ini/fonts/zap-vga-8x16.psf", include_bytes!("../../dsk/ini/fonts/zap-vga-8x16.psf"), verbose);
create_dir("/lib/lisp", verbose);
copy_file("/lib/lisp/core.lsp", include_bytes!("../../dsk/lib/lisp/core.lsp"), verbose);
copy_file("/lib/lisp/alias.lsp", include_bytes!("../../dsk/lib/lisp/alias.lsp"), verbose);
copy_file("/lib/lisp/core.lsp", include_bytes!("../../dsk/lib/lisp/core.lsp"), verbose);
copy_file("/lib/lisp/file.lsp", include_bytes!("../../dsk/lib/lisp/file.lsp"), verbose);
//copy_file("/lib/lisp/legacy.lsp", include_bytes!("../../dsk/lib/lisp/legacy.lsp"), verbose);
copy_file("/tmp/alice.txt", include_bytes!("../../dsk/tmp/alice.txt"), verbose);

View File

@ -34,10 +34,10 @@ pub fn default_env() -> Rc<RefCell<Env>> {
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("rem".to_string(), Exp::Primitive(primitive::lisp_rem));
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));
@ -46,19 +46,13 @@ pub fn default_env() -> Rc<RefCell<Env>> {
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-binary".to_string(), Exp::Primitive(primitive::lisp_read_file_bytes));
data.insert("write-file-binary".to_string(), Exp::Primitive(primitive::lisp_write_file_bytes));
data.insert("append-file-binary".to_string(), Exp::Primitive(primitive::lisp_append_file_bytes));
data.insert("string".to_string(), Exp::Primitive(primitive::lisp_string));
data.insert("string->binary".to_string(), Exp::Primitive(primitive::lisp_string_bytes));
data.insert("binary->string".to_string(), Exp::Primitive(primitive::lisp_bytes_string));
data.insert("binary->number".to_string(), Exp::Primitive(primitive::lisp_bytes_number));
data.insert("number->binary".to_string(), Exp::Primitive(primitive::lisp_number_bytes));
data.insert("regex-find".to_string(), Exp::Primitive(primitive::lisp_regex_find));
data.insert("string->binary".to_string(), Exp::Primitive(primitive::lisp_string_binary));
data.insert("binary->string".to_string(), Exp::Primitive(primitive::lisp_binary_string));
data.insert("binary->number".to_string(), Exp::Primitive(primitive::lisp_binary_number));
data.insert("number->binary".to_string(), Exp::Primitive(primitive::lisp_number_binary));
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("sort".to_string(), Exp::Primitive(primitive::lisp_sort));
@ -67,10 +61,19 @@ pub fn default_env() -> Rc<RefCell<Env>> {
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));
data.insert("concat".to_string(), Exp::Primitive(primitive::lisp_concat));
data.insert("number.type".to_string(), Exp::Primitive(primitive::lisp_number_type));
data.insert("regex.find".to_string(), Exp::Primitive(primitive::lisp_regex_find));
data.insert("string.split".to_string(), Exp::Primitive(primitive::lisp_string_split));
data.insert("string.trim".to_string(), Exp::Primitive(primitive::lisp_string_trim));
data.insert("file.size".to_string(), Exp::Primitive(primitive::lisp_file_size));
data.insert("file.open".to_string(), Exp::Primitive(primitive::lisp_file_open));
data.insert("file.read".to_string(), Exp::Primitive(primitive::lisp_file_read));
data.insert("file.write".to_string(), Exp::Primitive(primitive::lisp_file_write));
data.insert("file.close".to_string(), Exp::Primitive(primitive::lisp_file_close));
// Setup autocompletion
*FUNCTIONS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();

View File

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

View File

@ -445,8 +445,12 @@ fn test_lisp() {
assert_eq!(eval!("(^ 2 4)"), "16");
assert_eq!(eval!("(^ 2 4 2)"), "256"); // Left to right
// modulo
assert_eq!(eval!("(% 3 2)"), "1");
// remainder
assert_eq!(eval!("(rem 0 2)"), "0");
assert_eq!(eval!("(rem 1 2)"), "1");
assert_eq!(eval!("(rem 2 2)"), "0");
assert_eq!(eval!("(rem 3 2)"), "1");
assert_eq!(eval!("(rem -1 2)"), "-1");
// comparisons
assert_eq!(eval!("(< 6 4)"), "false");
@ -469,7 +473,8 @@ fn test_lisp() {
assert_eq!(eval!("(string \"foo \" 3)"), "\"foo 3\"");
assert_eq!(eval!("(equal? \"foo\" \"foo\")"), "true");
assert_eq!(eval!("(equal? \"foo\" \"bar\")"), "false");
assert_eq!(eval!("(split \"a\nb\nc\" \"\n\")"), "(\"a\" \"b\" \"c\")");
assert_eq!(eval!("(string.trim \"abc\n\")"), "\"abc\"");
assert_eq!(eval!("(string.split \"a\nb\nc\" \"\n\")"), "(\"a\" \"b\" \"c\")");
// apply
assert_eq!(eval!("(apply + '(1 2 3))"), "6");
@ -506,9 +511,9 @@ fn test_lisp() {
assert_eq!(eval!("(^ 2 128)"), "340282366920938463463374607431768211456"); // -> bigint
assert_eq!(eval!("(^ 2.0 128)"), "340282366920938500000000000000000000000.0"); // -> float
assert_eq!(eval!("(number-type 9223372036854775807)"), "\"int\"");
assert_eq!(eval!("(number-type 9223372036854775808)"), "\"bigint\"");
assert_eq!(eval!("(number-type 9223372036854776000.0)"), "\"float\"");
assert_eq!(eval!("(number.type 9223372036854775807)"), "\"int\"");
assert_eq!(eval!("(number.type 9223372036854775808)"), "\"bigint\"");
assert_eq!(eval!("(number.type 9223372036854776000.0)"), "\"float\"");
// quasiquote
eval!("(variable x 'a)");
@ -532,7 +537,7 @@ fn test_lisp() {
assert_eq!(eval!("foo"), "10");
// args
eval!("(variable list* (function args (append args '())))");
eval!("(variable list* (function args (concat args '())))");
assert_eq!(eval!("(list* 1 2 3)"), "(1 2 3)");
// comments

View File

@ -25,7 +25,7 @@ use nom::sequence::preceded;
use nom::sequence::tuple;
fn is_symbol_letter(c: char) -> bool {
let chars = "<>=-+*/%^?:";
let chars = "<>=-+*/%^?.";
c.is_alphanumeric() || chars.contains(c)
}
@ -34,6 +34,8 @@ fn parse_str(input: &str) -> IResult<&str, Exp> {
value("\\", tag("\\")),
value("\"", tag("\"")),
value("\n", tag("n")),
value("\r", tag("r")),
value("\t", tag("t")),
)))), |inner| inner.unwrap_or("".to_string()));
let (input, s) = delimited(char('"'), escaped, char('"'))(input)?;
Ok((input, Exp::Str(s)))

View File

@ -4,8 +4,9 @@ use super::{float, number, string};
use super::{bytes, numbers, strings};
use crate::{ensure_length_eq, ensure_length_gt, expected, could_not};
use crate::api::fs;
use crate::api::regex::Regex;
use crate::api::syscall;
use crate::sys::fs::OpenFlag;
use crate::usr::shell;
use alloc::format;
@ -75,7 +76,7 @@ pub fn lisp_div(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Num(res))
}
pub fn lisp_mod(args: &[Exp]) -> Result<Exp, Err> {
pub fn lisp_rem(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = numbers(args)?;
for arg in &args[1..] {
@ -162,49 +163,6 @@ 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(could_not!("read file"))?;
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 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()))
}
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 = bytes(list)?;
let n = fs::write(&path, &buf).or(could_not!("write file"))?;
Ok(Exp::Num(Number::from(n)))
}
_ => expected!("second argument to be a list")
}
}
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 = bytes(list)?;
let n = fs::append(&path, &buf).or(could_not!("write file"))?;
Ok(Exp::Num(Number::from(n)))
}
_ => expected!("second argument to be a list")
}
}
pub fn lisp_string(args: &[Exp]) -> Result<Exp, Err> {
let args: Vec<String> = args.iter().map(|arg| match arg {
Exp::Str(s) => format!("{}", s),
@ -213,14 +171,14 @@ pub fn lisp_string(args: &[Exp]) -> Result<Exp, Err> {
Ok(Exp::Str(args.join("")))
}
pub fn lisp_string_bytes(args: &[Exp]) -> Result<Exp, Err> {
pub fn lisp_string_binary(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> {
pub fn lisp_binary_string(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => {
@ -232,7 +190,7 @@ pub fn lisp_bytes_string(args: &[Exp]) -> Result<Exp, Err> {
}
}
pub fn lisp_bytes_number(args: &[Exp]) -> Result<Exp, Err> {
pub fn lisp_binary_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)) => {
@ -248,25 +206,12 @@ pub fn lisp_bytes_number(args: &[Exp]) -> Result<Exp, Err> {
}
}
pub fn lisp_number_bytes(args: &[Exp]) -> Result<Exp, Err> {
pub fn lisp_number_binary(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))
}
_ => 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])?;
@ -289,16 +234,6 @@ pub fn lisp_type(args: &[Exp]) -> Result<Exp, Err> {
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())),
_ => expected!("argument to be a number")
}
}
pub fn lisp_parse(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
@ -334,10 +269,10 @@ pub fn lisp_sort(args: &[Exp]) -> Result<Exp, Err> {
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 {
expected!("first argument to be a list")
match &args[0] {
Exp::List(l) => Ok(Exp::Bool(l.contains(&args[1]))),
Exp::Str(s) => Ok(Exp::Bool(s.contains(&string(&args[1])?))),
_ => expected!("first argument to be a list or a string"),
}
}
@ -393,7 +328,58 @@ pub fn lisp_chunks(args: &[Exp]) -> Result<Exp, Err> {
}
}
pub fn lisp_split(args: &[Exp]) -> Result<Exp, Err> {
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()))),
_ => expected!("a list or a string")
}
}
pub fn lisp_concat(args: &[Exp]) -> Result<Exp, Err> {
// TODO: This could also concat strings
let mut res = vec![];
for arg in args {
if let Exp::List(list) = arg {
res.extend_from_slice(list);
} else {
return expected!("a list")
}
}
Ok(Exp::List(res))
}
// Number module
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())),
_ => expected!("argument to be a number")
}
}
// Regex module
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))
}
_ => expected!("arguments to be a regex and a string")
}
}
// String module
pub fn lisp_string_split(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::Str(string), Exp::Str(pattern)) => {
@ -409,7 +395,7 @@ pub fn lisp_split(args: &[Exp]) -> Result<Exp, Err> {
}
}
pub fn lisp_trim(args: &[Exp]) -> Result<Exp, Err> {
pub fn lisp_string_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()))
@ -418,23 +404,76 @@ pub fn lisp_trim(args: &[Exp]) -> Result<Exp, Err> {
}
}
pub fn lisp_length(args: &[Exp]) -> Result<Exp, Err> {
// File module
pub fn lisp_file_size(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()))),
_ => expected!("a list or a string")
let path = string(&args[0])?;
match syscall::info(&path) {
Some(info) => Ok(Exp::Num(Number::from(info.size() as usize))),
None => return could_not!("open file"),
}
}
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 expected!("a list")
}
pub fn lisp_file_open(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
let mode = string(&args[1])?;
let mut flags = match mode.as_ref() {
"a" => OpenFlag::Append as usize,
"r" => OpenFlag::Read as usize,
"w" => OpenFlag::Write as usize,
_ => return expected!("valid mode"),
};
flags |= match syscall::info(&path) {
Some(info) if info.is_device() => OpenFlag::Device as usize,
Some(info) if info.is_dir() => OpenFlag::Dir as usize,
None if &mode == "r" => return could_not!("open file"),
None => OpenFlag::Create as usize,
_ => 0
};
match syscall::open(&path, flags) {
Some(handle) => Ok(Exp::Num(Number::from(handle))),
None => could_not!("open file"),
}
}
pub fn lisp_file_close(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let handle = number(&args[0])?.try_into()?;
syscall::close(handle);
Ok(Exp::List(vec![]))
}
pub fn lisp_file_read(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let handle = number(&args[0])?.try_into()?;
let len = number(&args[1])?;
let mut buf = vec![0; len.try_into()?];
match syscall::read(handle, &mut buf) {
Some(n) => {
buf.resize(n, 0);
Ok(Exp::List(buf.iter().map(|b| Exp::Num(Number::from(*b))).collect()))
}
None => could_not!("read file"),
}
}
pub fn lisp_file_write(args: &[Exp]) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let handle = number(&args[0])?.try_into()?;
match &args[1] {
Exp::List(list) => {
let buf = bytes(list)?;
match syscall::write(handle, &buf) {
Some(n) => Ok(Exp::Num(Number::from(n))),
None => could_not!("write file"),
}
}
_ => expected!("second argument to be a list")
}
Ok(Exp::List(res))
}

View File

@ -56,21 +56,20 @@ of the Shell.</p>
<h3>Primitive Operators</h3>
<ul>
<li><code>append</code></li>
<li><code>type</code>, <code>number-type</code> (aliased to <code>num-type</code>)</li>
<li><code>type</code>, <code>number.type</code> (aliased to <code>num.type</code>)</li>
<li><code>string</code> (aliased to <code>str</code>)</li>
<li><code>string-&gt;number</code> (aliased to to <code>str-&gt;num</code>)</li>
<li><code>string-&gt;binary</code> and <code>binary-&gt;string</code> (aliased to <code>str-&gt;bin</code> and <code>bin-&gt;str</code>)</li>
<li><code>number-&gt;binary</code> and <code>binary-&gt;number</code> (aliased to <code>num-&gt;bin</code> and <code>bin-&gt;num</code>)</li>
<li><code>regex-find</code></li>
<li><code>regex.find</code></li>
<li><code>system</code></li>
<li>Arithmetic operations: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>%</code>, <code>^</code>, <code>abs</code></li>
<li>Arithmetic operations: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>^</code>, <code>abs</code>, <code>mod</code>, <code>rem</code> (aliased to <code>%</code>)</li>
<li>Trigonometric functions: <code>acos</code>, <code>asin</code>, <code>atan</code>, <code>cos</code>, <code>sin</code>, <code>tan</code></li>
<li>Comparisons: <code>&gt;</code>, <code>&lt;</code>, <code>&gt;=</code>, <code>&lt;=</code>, <code>=</code></li>
<li>File IO: <code>read-file</code>, <code>read-file-binary</code>, <code>write-file-binary</code>, <code>append-file-binary</code></li>
<li>List: <code>chunks</code>, <code>sort</code>, <code>unique</code> (aliased to <code>uniq</code>), <code>min</code>, <code>max</code></li>
<li>String: <code>trim</code>, <code>split</code></li>
<li>Enumerable: <code>length</code> (aliased to <code>len</code>), <code>nth</code>, <code>first</code>, <code>second</code>, <code>third</code>, <code>last</code>, <code>rest</code>, <code>slice</code></li>
<li>String: <code>string.trim</code>, <code>string.split</code></li>
<li>List: <code>concat</code>, <code>chunks</code>, <code>sort</code>, <code>unique</code> (aliased to <code>uniq</code>), <code>min</code>, <code>max</code></li>
<li>File: <code>file.size</code>, <code>file.open</code>, <code>file.close</code>, <code>file.read</code>, <code>file.write</code></li>
</ul>
<h3>Core Library</h3>
@ -83,12 +82,18 @@ of the Shell.</p>
<li><code>map</code>, <code>reduce</code>, <code>reverse</code> (aliased to <code>rev</code>), <code>range</code>, <code>filter</code>, <code>intersection</code></li>
<li><code>not</code>, <code>and</code>, <code>or</code></li>
<li><code>let</code></li>
<li><code>join-string</code> (aliased to <code>join-str</code>), <code>lines</code>, <code>words</code>, <code>chars</code></li>
<li><code>string.join</code> (aliased to <code>str.join</code>), <code>lines</code>, <code>words</code>, <code>chars</code></li>
<li><code>regex.match?</code></li>
</ul>
<h3>File Library</h3>
<ul>
<li><code>read</code>, <code>write</code>, <code>append</code></li>
<li><code>read-binary</code>, <code>write-binary</code>, <code>append-binary</code></li>
<li><code>read-line</code>, <code>read-char</code></li>
<li><code>p</code>, <code>print</code></li>
<li><code>write-file</code>, <code>append-file</code></li>
<li><code>uptime</code>, <code>realtime</code></li>
<li><code>regex-match?</code></li>
<li><code>p</code>, <code>print</code></li>
</ul>
<h3>Compatibility Library</h3>
@ -229,5 +234,11 @@ language and reading from the filesystem.</p>
<li>Add params to function representations</li>
<li>Add docstring to functions</li>
</ul>
<h3>Unreleased</h3>
<ul>
<li>Add file, number, string, and regex namespaces</li>
</ul>
</body>
</html>