diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1300e25..dd1e559 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/doc/lisp.md b/doc/lisp.md
index 32fb378..e18dab1 100644
--- a/doc/lisp.md
+++ b/doc/lisp.md
@@ -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
diff --git a/dsk/lib/lisp/core.lsp b/dsk/lib/lisp/core.lsp
index 53b80d5..b9a681f 100644
--- a/dsk/lib/lisp/core.lsp
+++ b/dsk/lib/lisp/core.lsp
@@ -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")
diff --git a/dsk/lib/lisp/file.lsp b/dsk/lib/lisp/file.lsp
new file mode 100644
index 0000000..7afd5dc
--- /dev/null
+++ b/dsk/lib/lisp/file.lsp
@@ -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"))
diff --git a/dsk/tmp/lisp/colors.lsp b/dsk/tmp/lisp/colors.lsp
index 0d2f3ef..8dd8da6 100644
--- a/dsk/tmp/lisp/colors.lsp
+++ b/dsk/tmp/lisp/colors.lsp
@@ -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))
diff --git a/src/usr/install.rs b/src/usr/install.rs
index 6d44545..8c691c9 100644
--- a/src/usr/install.rs
+++ b/src/usr/install.rs
@@ -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);
diff --git a/src/usr/lisp/env.rs b/src/usr/lisp/env.rs
index 2a9b872..3611eeb 100644
--- a/src/usr/lisp/env.rs
+++ b/src/usr/lisp/env.rs
@@ -34,10 +34,10 @@ pub fn default_env() -> Rc> {
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> {
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> {
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();
diff --git a/src/usr/lisp/expand.rs b/src/usr/lisp/expand.rs
index 85d0c9d..2f303bd 100644
--- a/src/usr/lisp/expand.rs
+++ b/src/usr/lisp/expand.rs
@@ -20,7 +20,7 @@ pub fn expand_quasiquote(exp: &Exp) -> Result {
}
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()))?
]))
diff --git a/src/usr/lisp/mod.rs b/src/usr/lisp/mod.rs
index b92aeac..ded693d 100644
--- a/src/usr/lisp/mod.rs
+++ b/src/usr/lisp/mod.rs
@@ -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
diff --git a/src/usr/lisp/parse.rs b/src/usr/lisp/parse.rs
index 8f0858a..f67f819 100644
--- a/src/usr/lisp/parse.rs
+++ b/src/usr/lisp/parse.rs
@@ -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)))
diff --git a/src/usr/lisp/primitive.rs b/src/usr/lisp/primitive.rs
index 5bef80b..071b1b8 100644
--- a/src/usr/lisp/primitive.rs
+++ b/src/usr/lisp/primitive.rs
@@ -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 {
Ok(Exp::Num(res))
}
-pub fn lisp_mod(args: &[Exp]) -> Result {
+pub fn lisp_rem(args: &[Exp]) -> Result {
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 {
}
}
-pub fn lisp_read_file(args: &[Exp]) -> Result {
- 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 {
- 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 {
- 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 {
- 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 {
let args: Vec = args.iter().map(|arg| match arg {
Exp::Str(s) => format!("{}", s),
@@ -213,14 +171,14 @@ pub fn lisp_string(args: &[Exp]) -> Result {
Ok(Exp::Str(args.join("")))
}
-pub fn lisp_string_bytes(args: &[Exp]) -> Result {
+pub fn lisp_string_binary(args: &[Exp]) -> Result {
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 {
+pub fn lisp_binary_string(args: &[Exp]) -> Result {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => {
@@ -232,7 +190,7 @@ pub fn lisp_bytes_string(args: &[Exp]) -> Result {
}
}
-pub fn lisp_bytes_number(args: &[Exp]) -> Result {
+pub fn lisp_binary_number(args: &[Exp]) -> Result {
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 {
}
}
-pub fn lisp_number_bytes(args: &[Exp]) -> Result {
+pub fn lisp_number_binary(args: &[Exp]) -> Result {
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 {
- 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 {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
@@ -289,16 +234,6 @@ pub fn lisp_type(args: &[Exp]) -> Result {
Ok(Exp::Str(exp.to_string()))
}
-pub fn lisp_number_type(args: &[Exp]) -> Result {
- 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 {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
@@ -334,10 +269,10 @@ pub fn lisp_sort(args: &[Exp]) -> Result {
pub fn lisp_contains(args: &[Exp]) -> Result {
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 {
}
}
-pub fn lisp_split(args: &[Exp]) -> Result {
+pub fn lisp_length(args: &[Exp]) -> Result {
+ 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 {
+ // 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 {
+ 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 {
+ 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 {
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 {
}
}
-pub fn lisp_trim(args: &[Exp]) -> Result {
+pub fn lisp_string_trim(args: &[Exp]) -> Result {
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 {
}
}
-pub fn lisp_length(args: &[Exp]) -> Result {
+// File module
+
+pub fn lisp_file_size(args: &[Exp]) -> Result {
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 {
- 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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))
}
diff --git a/www/lisp.html b/www/lisp.html
index 2e870de..7029c91 100644
--- a/www/lisp.html
+++ b/www/lisp.html
@@ -56,21 +56,20 @@ of the Shell.
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
@@ -83,12 +82,18 @@ of the Shell.
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
@@ -229,5 +234,11 @@ language and reading from the filesystem.
Add params to function representations
Add docstring to functions
+
+ Unreleased
+
+
+ - Add file, number, string, and regex namespaces
+