From f806c3a33844701ab9d22df9ebd431b5aecfbb61 Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Sun, 2 Jul 2023 09:20:49 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + doc/lisp.md | 27 +++-- dsk/lib/lisp/core.lsp | 73 +++++-------- dsk/lib/lisp/file.lsp | 72 ++++++++++++ dsk/tmp/lisp/colors.lsp | 2 +- src/usr/install.rs | 3 +- src/usr/lisp/env.rs | 31 +++--- src/usr/lisp/expand.rs | 2 +- src/usr/lisp/mod.rs | 19 ++-- src/usr/lisp/parse.rs | 4 +- src/usr/lisp/primitive.rs | 223 ++++++++++++++++++++++---------------- www/lisp.html | 33 ++++-- 12 files changed, 303 insertions(+), 187 deletions(-) create mode 100644 dsk/lib/lisp/file.lsp 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
    • +