mirror of https://github.com/vinc/moros.git
Improve Lisp with Advent of Code 2023 🎄 (#556)
* Add contains? to Lisp * Use ls everywhere instead of lst * Rename system command to shell * Exec '/ini/lisp.lsp' at startup * Check if init file exist * Add missing lisp init file * Remove contains? * Add empty? function * Add reject? function * Update doc * Add dict type to Lisp * Add push function * Add binary literals to doc * Move push function from primitive to core lib * Update lisp doc * Add escape character to parser * Add backspace to parser * Update doc * Add host function * Fix regex.match? * Test if and while for truthiness instead of comparing with true * Add tests for empty string and 0 * Mention truthiness in changelog * Check for unspecified IPv4 in usr::host::resolv * Transform host error into nil * Update ntp.lsp to support hostnames * Fix typo * Fix escape display * Use '/' instead of '.' as namespace separator * Add doc.lsp * Fix comment color * Update changelog
This commit is contained in:
parent
525b1dcca3
commit
9f525990a5
|
@ -2,6 +2,10 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
- Improve lisp with Advent of Code 2023 (#556)
|
||||
- Improve system information (#553)
|
||||
- Add hash command (#554)
|
||||
- Improve binary support (#552)
|
||||
- Add basic userspace shell (#548)
|
||||
- Add basic userspace allocator (#544)
|
||||
- Improve documentation (#547)
|
||||
|
|
40
doc/lisp.md
40
doc/lisp.md
|
@ -12,8 +12,9 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!
|
|||
- Number: `float`, `int`, `bigint`
|
||||
|
||||
### Literals
|
||||
- Number: `2.5`, `-25`, `255`, `0xFF`, `0xDEAD_C0DE`, `0b101010`
|
||||
- String: `"Hello, World!"`
|
||||
- Number: `2.5`, `-25`, `255`, `0xFF`, `0xDEAD_C0DE`
|
||||
- Escape: `\b`, `\e`, `\n`, `\r`, `\t`, `\"`, `\\`
|
||||
|
||||
### Built-in Operators
|
||||
- `quote` (abbreviated with `'`)
|
||||
|
@ -44,32 +45,34 @@ MOROS Lisp is a Lisp-1 dialect inspired by Scheme, Clojure, and Ruby!
|
|||
- `load`
|
||||
|
||||
### Primitive Operators
|
||||
- `type`, `number.type` (aliased to `num.type`)
|
||||
- `type`, `number/type` (aliased to `num/type`), `parse`
|
||||
- `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`
|
||||
- `system`
|
||||
- Arithmetic operations: `+`, `-`, `*`, `/`, `^`, `abs`, `mod`, `rem` (aliased to `%`)
|
||||
- `regex/find`
|
||||
- `shell` (aliased to `sh`)
|
||||
- Arithmetic operations: `+`, `-`, `*`, `/`, `^`, `rem` (aliased to `%`), `trunc`
|
||||
- Trigonometric functions: `acos`, `asin`, `atan`, `cos`, `sin`, `tan`
|
||||
- Comparisons: `>`, `<`, `>=`, `<=`, `=`
|
||||
- Enumerable: `length` (aliased to `len`), `nth`, `first`, `second`, `third`, `last`, `rest`, `slice`
|
||||
- String: `string.trim` and `string.split` (aliased to `str.trim` and `str.split`)
|
||||
- List: `concat`, `chunks`, `sort`, `unique` (aliased to `uniq`), `min`, `max`
|
||||
- File: `file.size`, `file.open`, `file.close`, `file.read`, `file.write`
|
||||
- Enumerable: `length` (aliased to `len`), `put`, `get`, `slice`, `contains?`
|
||||
- String: `string/trim` and `string/split` (aliased to `str/trim` and `str/split`)
|
||||
- List: `list`, `concat`, `chunks`, `sort`, `unique` (aliased to `uniq`)
|
||||
- Dict: `dict`
|
||||
- File: `file/size`, `file/open`, `file/close`, `file/read`, `file/write`
|
||||
- Net: `host`, `socket/connect`, `socket/listen`, `socket/accept`
|
||||
|
||||
### Core Library
|
||||
- `nil`, `nil?`, `list?`
|
||||
- `nil`, `nil?`, `list?`, `empty?`
|
||||
- `boolean?` (aliased to `bool?`), `string?` (aliased to `str?`), `symbol?` (aliased to `sym?`), `number?` (aliased to `num?`)
|
||||
- `function?` (aliased to `fun?`), `macro?` (aliased to `mac?`)
|
||||
- `first`, `second`, `third`, `rest`
|
||||
- `map`, `reduce`, `reverse` (aliased to `rev`), `range`, `filter`, `intersection`
|
||||
- `abs`, `mod`, `min`, `max`
|
||||
- `first`, `second`, `third`, `last`, `rest`, `push`
|
||||
- `map`, `reduce`, `reverse` (aliased to `rev`), `range`, `filter`, `reject`, `intersection`
|
||||
- `not`, `and`, `or`
|
||||
- `let`
|
||||
- `string.join` (aliased to `str.join`), `lines`, `words`, `chars`
|
||||
- `regex.match?`
|
||||
- `socket.connect`, `socket.listen`, `socket.accept`
|
||||
- `string/join` (aliased to `str/join`), `lines`, `words`, `chars`
|
||||
- `regex/match?`
|
||||
|
||||
### File Library
|
||||
- `read`, `write`, `append`
|
||||
|
@ -207,4 +210,9 @@ language and reading from the filesystem.
|
|||
- Add socket functions
|
||||
|
||||
### Unreleased
|
||||
- Add hexadecimal number literals
|
||||
- Add binary and hexadecimal number literals
|
||||
- Test for truthiness (neither `false` nor `nil`) in conditions of `if` and `while`
|
||||
- Rename `nth` to `get`
|
||||
- Add `empty?`, `reject`, `put`, `push`, and `host` functions`
|
||||
- Add `dict` type
|
||||
- Use `/` instead of `.` as namespace separator
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
(load "/lib/lisp/core.lsp")
|
|
@ -30,11 +30,16 @@
|
|||
(if x false true))
|
||||
|
||||
(def-mac (or x y)
|
||||
`(if ,x true (if ,y true false)))
|
||||
`(if ,x ,x (if ,y ,y false)))
|
||||
|
||||
(def-mac (and x y)
|
||||
`(if ,x (if ,y true false) false))
|
||||
|
||||
(def (empty? x)
|
||||
(or
|
||||
(equal? x nil)
|
||||
(equal? x "")))
|
||||
|
||||
(def-mac (let params values body)
|
||||
`((fun ,params ,body) ,@values))
|
||||
|
||||
|
@ -57,27 +62,34 @@
|
|||
(cons (head ls) (filter f (tail ls)))
|
||||
(filter f (tail ls)))))
|
||||
|
||||
(def (reject f ls)
|
||||
"Reject the elements of the list with the function"
|
||||
(if (nil? ls) nil
|
||||
(if (not (f (head ls)))
|
||||
(cons (head ls) (reject f (tail ls)))
|
||||
(reject f (tail ls)))))
|
||||
|
||||
(def (intersection a b)
|
||||
"Return elements found in both lists"
|
||||
(filter (fun (x) (contains? b x)) a))
|
||||
|
||||
(def (reverse x)
|
||||
(def (reverse ls)
|
||||
"Reverse list"
|
||||
(if (nil? x) x
|
||||
(concat (reverse (tail x)) (cons (head x) '()))))
|
||||
(if (nil? ls) ls
|
||||
(concat (reverse (tail ls)) (cons (head ls) '()))))
|
||||
|
||||
(def (range start stop)
|
||||
"Return a list of integers from start to stop excluded"
|
||||
(if (= start stop) nil
|
||||
(concat (list start) (range (+ start 1) stop))))
|
||||
|
||||
(def (min lst)
|
||||
(def (min ls)
|
||||
"Return the minimum element of the list"
|
||||
(head (sort lst)))
|
||||
(head (sort ls)))
|
||||
|
||||
(def (max lst)
|
||||
(def (max ls)
|
||||
"Return the maximum element of the list"
|
||||
(head (reverse (sort lst))))
|
||||
(head (reverse (sort ls))))
|
||||
|
||||
(def (abs x)
|
||||
(if (> x 0) x (- x)))
|
||||
|
@ -85,46 +97,52 @@
|
|||
(def (mod a b)
|
||||
(rem (+ (rem a b) b) b))
|
||||
|
||||
(def (string.join ls s)
|
||||
(def (string/join ls s)
|
||||
"Join the elements of the list with the string"
|
||||
(reduce (fun (x y) (string x s y)) ls))
|
||||
|
||||
(def (regex.match? pattern s)
|
||||
(not (nil? (regex.find pattern str))))
|
||||
(def (regex/match? r s)
|
||||
"Return true if the string match the pattern"
|
||||
(not (nil? (regex/find r s))))
|
||||
|
||||
(def (lines text)
|
||||
"Split text into a list of lines"
|
||||
(string.split (string.trim text) "\n"))
|
||||
(string/split (string/trim text) "\n"))
|
||||
|
||||
(def (words text)
|
||||
"Split text into a list of words"
|
||||
(string.split text " "))
|
||||
(string/split text " "))
|
||||
|
||||
(def (chars text)
|
||||
"Split text into a list of chars"
|
||||
(string.split text ""))
|
||||
(string/split text ""))
|
||||
|
||||
(def (first lst)
|
||||
(nth lst 0))
|
||||
(def (push ls x)
|
||||
"Push element to the end of the list"
|
||||
(put ls (len ls) x))
|
||||
|
||||
(def (second lst)
|
||||
(nth lst 1))
|
||||
(def (first ls)
|
||||
(get ls 0))
|
||||
|
||||
(def (third lst)
|
||||
(nth lst 2))
|
||||
(def (second ls)
|
||||
(get ls 1))
|
||||
|
||||
(def (last lst)
|
||||
(nth lst
|
||||
(if (= (length lst) 0) 0 (- (length lst) 1))))
|
||||
(def (third ls)
|
||||
(get ls 2))
|
||||
|
||||
(def (last ls)
|
||||
(get ls
|
||||
(if (= (length ls) 0) 0 (- (length ls) 1))))
|
||||
|
||||
# Short aliases
|
||||
|
||||
(var sh shell)
|
||||
(var % rem)
|
||||
(var str string)
|
||||
(var str.split string.split)
|
||||
(var str.join string.join)
|
||||
(var str.trim string.trim)
|
||||
(var num.type number.type)
|
||||
(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)
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
(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)
|
||||
(var size (file/size path))
|
||||
(var file (file/open path "r"))
|
||||
(var data (file/read file size))
|
||||
(file/close file)
|
||||
data))
|
||||
|
||||
(def (read path)
|
||||
|
@ -22,9 +22,9 @@
|
|||
(def (write-binary path data)
|
||||
"Write binary to file"
|
||||
(do
|
||||
(var file (file.open path "w"))
|
||||
(file.write file data)
|
||||
(file.close file)))
|
||||
(var file (file/open path "w"))
|
||||
(file/write file data)
|
||||
(file/close file)))
|
||||
|
||||
(def (write path text)
|
||||
"Write text to file"
|
||||
|
@ -35,9 +35,9 @@
|
|||
(def (append-binary path data)
|
||||
"Append binary to file"
|
||||
(do
|
||||
(var file (file.open path "a"))
|
||||
(file.write file data)
|
||||
(file.close file)))
|
||||
(var file (file/open path "a"))
|
||||
(file/write file data)
|
||||
(file/close file)))
|
||||
|
||||
(def (append path text)
|
||||
"Append text to file"
|
||||
|
@ -47,16 +47,16 @@
|
|||
|
||||
(def (read-line)
|
||||
"Read line from the console"
|
||||
(string.trim (binary->string (file.read stdin 256))))
|
||||
(string/trim (binary->string (file/read stdin 256))))
|
||||
|
||||
(def (read-char)
|
||||
"Read char from the console"
|
||||
(binary->string (file.read stdin 4)))
|
||||
(binary->string (file/read stdin 4)))
|
||||
|
||||
(def (p exp)
|
||||
"Print expression to the console"
|
||||
(do
|
||||
(file.write stdout (string->binary (string exp)))
|
||||
(file/write stdout (string->binary (string exp)))
|
||||
'()))
|
||||
|
||||
(def (print exp)
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
(load "/lib/lisp/core.lsp")
|
||||
|
||||
(var esc (bin->str '(27)))
|
||||
|
||||
(def (ansi-color x y)
|
||||
(str esc "[" x ";" y "m"))
|
||||
(str "\e[" x ";" y "m"))
|
||||
|
||||
(def (fg c)
|
||||
(ansi-color c 40))
|
||||
|
@ -15,7 +13,7 @@
|
|||
(str " " (f c) (if (< c 100) " " "") c (ansi-color 0 0)))
|
||||
|
||||
(def (colors fs i j)
|
||||
(str.join (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))
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
(load "/lib/lisp/core.lsp")
|
||||
|
||||
(def (print-doc f) (do
|
||||
(var s (second (parse (str (eval f)))))
|
||||
(var d (doc (eval f)))
|
||||
(print (str
|
||||
"("
|
||||
(if (function? (eval f)) "\e[96m" "\e[92m") f "\e[0m" # name
|
||||
(if (nil? s) "" (str " " (if (list? s) (str/join s " ") s))) # args
|
||||
")"
|
||||
"\e[90m" (if (empty? d) "" " # ") d "\e[0m")))) # desc
|
||||
|
||||
(var fs
|
||||
(filter
|
||||
(fun (f) (or (fun? (eval f)) (mac? (eval f))))
|
||||
(rev (env))))
|
||||
|
||||
(map print-doc fs)
|
|
@ -6,10 +6,10 @@
|
|||
(var proxy-port 8888)
|
||||
|
||||
(var stdout 1)
|
||||
(var socket (socket.connect "tcp" proxy-host proxy-port))
|
||||
(file.write socket (str->bin (str "GET " (nth args 0) "\n")))
|
||||
(var socket (socket/connect "tcp" proxy-host proxy-port))
|
||||
(file/write socket (str->bin (str "GET " (get args 0) "\n")))
|
||||
(var open true)
|
||||
(while open (do
|
||||
(var buf (file.read socket 2048))
|
||||
(file.write stdout buf)
|
||||
(var buf (file/read socket 2048))
|
||||
(file/write stdout buf)
|
||||
(set open (not (nil? buf)))))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
(load "/lib/lisp/core.lsp")
|
||||
|
||||
(var addr (nth args 0)) # Run `host pool.ntp.org` to get an address
|
||||
(var addr (or (host (head args)) (head args)))
|
||||
(var port 123)
|
||||
(var socket (socket.connect "udp" addr port))
|
||||
(var socket (socket/connect "udp" addr port))
|
||||
|
||||
(var req (map (fun (i) (if (eq? i 0) 0x33 0)) (range 0 48)))
|
||||
(file.write socket req)
|
||||
(var res (file.read socket 48))
|
||||
(file/write socket req)
|
||||
(var res (file/read socket 48))
|
||||
|
||||
(var buf (slice res 40 4))
|
||||
(var time (- (bin->num (concat '(0 0 0 0) buf) "int") 2208988800))
|
||||
|
|
|
@ -160,13 +160,19 @@ pub fn resolve(name: &str) -> Result<IpAddress, ResponseCode> {
|
|||
let message = Message::from(&data);
|
||||
if message.id() == query.id() && message.is_response() {
|
||||
syscall::close(handle);
|
||||
//usr::hex::print_hex(&message.datagram);
|
||||
return match message.code() {
|
||||
ResponseCode::NoError => {
|
||||
// TODO: Parse the datagram instead of extracting
|
||||
// the last 4 bytes
|
||||
let n = message.datagram.len();
|
||||
let data = &message.datagram[(n - 4)..];
|
||||
Ok(IpAddress::from(Ipv4Address::from_bytes(data)))
|
||||
let ipv4 = Ipv4Address::from_bytes(data);
|
||||
if ipv4.is_unspecified() {
|
||||
Err(ResponseCode::NameError) // FIXME
|
||||
} else {
|
||||
Ok(IpAddress::from(ipv4))
|
||||
}
|
||||
}
|
||||
code => {
|
||||
Err(code)
|
||||
|
|
|
@ -41,6 +41,7 @@ pub fn copy_files(verbose: bool) {
|
|||
|
||||
copy_file("/ini/banner.txt", include_bytes!("../../dsk/ini/banner.txt"), verbose);
|
||||
copy_file("/ini/boot.sh", include_bytes!("../../dsk/ini/boot.sh"), verbose);
|
||||
copy_file("/ini/lisp.lsp", include_bytes!("../../dsk/ini/lisp.lsp"), verbose);
|
||||
copy_file("/ini/shell.sh", include_bytes!("../../dsk/ini/shell.sh"), verbose);
|
||||
copy_file("/ini/version.txt", include_bytes!("../../dsk/ini/version.txt"), verbose);
|
||||
|
||||
|
@ -64,11 +65,12 @@ pub fn copy_files(verbose: bool) {
|
|||
|
||||
create_dir("/tmp/lisp", verbose);
|
||||
copy_file("/tmp/lisp/colors.lsp", include_bytes!("../../dsk/tmp/lisp/colors.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/doc.lsp", include_bytes!("../../dsk/tmp/lisp/doc.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/factorial.lsp", include_bytes!("../../dsk/tmp/lisp/factorial.lsp"), verbose);
|
||||
//copy_file("/tmp/lisp/fetch.lsp", include_bytes!("../../dsk/tmp/lisp/fetch.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/fibonacci.lsp", include_bytes!("../../dsk/tmp/lisp/fibonacci.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/geotime.lsp", include_bytes!("../../dsk/tmp/lisp/geotime.lsp"), verbose);
|
||||
//copy_file("/tmp/lisp/ntp.lsp", include_bytes!("../../dsk/tmp/lisp/ntp.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/ntp.lsp", include_bytes!("../../dsk/tmp/lisp/ntp.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/pi.lsp", include_bytes!("../../dsk/tmp/lisp/pi.lsp"), verbose);
|
||||
copy_file("/tmp/lisp/sum.lsp", include_bytes!("../../dsk/tmp/lisp/sum.lsp"), verbose);
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ pub fn default_env() -> Rc<RefCell<Env>> {
|
|||
data.insert("sin".to_string(), Exp::Primitive(primitive::lisp_sin));
|
||||
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("shell".to_string(), Exp::Primitive(primitive::lisp_shell));
|
||||
data.insert("string".to_string(), Exp::Primitive(primitive::lisp_string));
|
||||
data.insert("string->binary".to_string(), Exp::Primitive(primitive::lisp_string_binary));
|
||||
data.insert("binary->string".to_string(), Exp::Primitive(primitive::lisp_binary_string));
|
||||
|
@ -56,26 +56,32 @@ pub fn default_env() -> Rc<RefCell<Env>> {
|
|||
data.insert("list".to_string(), Exp::Primitive(primitive::lisp_list));
|
||||
data.insert("sort".to_string(), Exp::Primitive(primitive::lisp_sort));
|
||||
data.insert("unique".to_string(), Exp::Primitive(primitive::lisp_unique));
|
||||
data.insert("nth".to_string(), Exp::Primitive(primitive::lisp_nth));
|
||||
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("length".to_string(), Exp::Primitive(primitive::lisp_length));
|
||||
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("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));
|
||||
data.insert("socket.connect".to_string(), Exp::Primitive(primitive::lisp_socket_connect));
|
||||
data.insert("socket.listen".to_string(), Exp::Primitive(primitive::lisp_socket_listen));
|
||||
data.insert("socket.accept".to_string(), Exp::Primitive(primitive::lisp_socket_accept));
|
||||
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));
|
||||
|
||||
data.insert("socket/connect".to_string(), Exp::Primitive(primitive::lisp_socket_connect));
|
||||
data.insert("socket/listen".to_string(), Exp::Primitive(primitive::lisp_socket_listen));
|
||||
data.insert("socket/accept".to_string(), Exp::Primitive(primitive::lisp_socket_accept));
|
||||
|
||||
data.insert("host".to_string(), Exp::Primitive(primitive::lisp_host));
|
||||
|
||||
data.insert("dict".to_string(), Exp::Primitive(primitive::lisp_dict));
|
||||
data.insert("get".to_string(), Exp::Primitive(primitive::lisp_get));
|
||||
data.insert("put".to_string(), Exp::Primitive(primitive::lisp_put));
|
||||
|
||||
// Setup autocompletion
|
||||
*FUNCTIONS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();
|
||||
|
|
|
@ -101,7 +101,7 @@ fn eval_while_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
|
|||
ensure_length_gt!(args, 1);
|
||||
let cond = &args[0];
|
||||
let mut res = Exp::List(vec![]);
|
||||
while eval(cond, env)? == Exp::Bool(true) {
|
||||
while eval(cond, env)?.is_truthy() {
|
||||
for arg in &args[1..] {
|
||||
res = eval(arg, env)?;
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ pub fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
|
|||
}
|
||||
Exp::Sym(s) if s == "if" => {
|
||||
ensure_length_gt!(args, 1);
|
||||
if eval(&args[0], env)? == Exp::Bool(true) { // consequent
|
||||
if eval(&args[0], env)?.is_truthy() { // consequent
|
||||
exp_tmp = args[1].clone();
|
||||
} else if args.len() > 2 { // alternate
|
||||
exp_tmp = args[2].clone();
|
||||
|
|
|
@ -13,7 +13,7 @@ use eval::{eval, eval_variable_args};
|
|||
use expand::expand;
|
||||
use parse::parse;
|
||||
|
||||
use crate::api;
|
||||
use crate::api::fs;
|
||||
use crate::api::console::Style;
|
||||
use crate::api::process::ExitCode;
|
||||
use crate::api::prompt::Prompt;
|
||||
|
@ -24,9 +24,11 @@ use alloc::rc::Rc;
|
|||
use alloc::string::String;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use alloc::vec;
|
||||
use core::cell::RefCell;
|
||||
use core::convert::TryInto;
|
||||
use core::cmp;
|
||||
use core::fmt;
|
||||
use lazy_static::lazy_static;
|
||||
use spin::Mutex;
|
||||
|
@ -52,18 +54,30 @@ pub enum Exp {
|
|||
Function(Box<Function>),
|
||||
Macro(Box<Function>),
|
||||
List(Vec<Exp>),
|
||||
Dict(BTreeMap<String, Exp>),
|
||||
Bool(bool),
|
||||
Num(Number),
|
||||
Str(String),
|
||||
Sym(String),
|
||||
}
|
||||
|
||||
impl Exp {
|
||||
pub fn is_truthy(&self) -> bool {
|
||||
match self {
|
||||
Exp::Bool(b) => *b,
|
||||
Exp::List(l) => !l.is_empty(),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Exp {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Exp::Function(a), Exp::Function(b)) => a == b,
|
||||
(Exp::Macro(a), Exp::Macro(b)) => a == b,
|
||||
(Exp::List(a), Exp::List(b)) => a == b,
|
||||
(Exp::Dict(a), Exp::Dict(b)) => a == b,
|
||||
(Exp::Bool(a), Exp::Bool(b)) => a == b,
|
||||
(Exp::Num(a), Exp::Num(b)) => a == b,
|
||||
(Exp::Str(a), Exp::Str(b)) => a == b,
|
||||
|
@ -73,13 +87,13 @@ impl PartialEq for Exp {
|
|||
}
|
||||
}
|
||||
|
||||
use core::cmp::Ordering;
|
||||
impl PartialOrd for Exp {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
match (self, other) {
|
||||
(Exp::Function(a), Exp::Function(b)) => a.partial_cmp(b),
|
||||
(Exp::Macro(a), Exp::Macro(b)) => a.partial_cmp(b),
|
||||
(Exp::List(a), Exp::List(b)) => a.partial_cmp(b),
|
||||
(Exp::Dict(a), Exp::Dict(b)) => a.partial_cmp(b),
|
||||
(Exp::Bool(a), Exp::Bool(b)) => a.partial_cmp(b),
|
||||
(Exp::Num(a), Exp::Num(b)) => a.partial_cmp(b),
|
||||
(Exp::Str(a), Exp::Str(b)) => a.partial_cmp(b),
|
||||
|
@ -98,11 +112,15 @@ impl fmt::Display for Exp {
|
|||
Exp::Bool(a) => a.to_string(),
|
||||
Exp::Num(n) => n.to_string(),
|
||||
Exp::Sym(s) => s.clone(),
|
||||
Exp::Str(s) => format!("{:?}", s),
|
||||
Exp::Str(s) => format!("{:?}", s).replace("\\u{8}", "\\b").replace("\\u{1b}", "\\e"),
|
||||
Exp::List(list) => {
|
||||
let xs: Vec<String> = list.iter().map(|x| x.to_string()).collect();
|
||||
format!("({})", xs.join(" "))
|
||||
},
|
||||
Exp::Dict(dict) => {
|
||||
let xs: Vec<String> = dict.iter().map(|(k, v)| format!("{} {}", k, v)).collect();
|
||||
format!("(dict {})", xs.join(" "))
|
||||
},
|
||||
};
|
||||
write!(f, "{}", out)
|
||||
}
|
||||
|
@ -274,6 +292,29 @@ fn repl(env: &mut Rc<RefCell<Env>>) -> Result<(), ExitCode> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn exec(env: &mut Rc<RefCell<Env>>, path: &str) -> Result<(), ExitCode> {
|
||||
if let Ok(mut input) = fs::read_to_string(path) {
|
||||
loop {
|
||||
match parse_eval(&input, env) {
|
||||
Ok((rest, _)) => {
|
||||
if rest.is_empty() {
|
||||
break;
|
||||
}
|
||||
input = rest;
|
||||
}
|
||||
Err(Err::Reason(msg)) => {
|
||||
error!("{}", msg);
|
||||
return Err(ExitCode::Failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
error!("Could not find file '{}'", path);
|
||||
Err(ExitCode::Failure)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main(args: &[&str]) -> Result<(), ExitCode> {
|
||||
let env = &mut default_env();
|
||||
|
||||
|
@ -291,32 +332,16 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
|
|||
}
|
||||
|
||||
if args.len() < 2 {
|
||||
let init = "/ini/lisp.lsp";
|
||||
if fs::exists(init) {
|
||||
exec(env, init)?;
|
||||
}
|
||||
repl(env)
|
||||
} else {
|
||||
if args[1] == "-h" || args[1] == "--help" {
|
||||
return help();
|
||||
}
|
||||
let path = args[1];
|
||||
if let Ok(mut input) = api::fs::read_to_string(path) {
|
||||
loop {
|
||||
match parse_eval(&input, env) {
|
||||
Ok((rest, _)) => {
|
||||
if rest.is_empty() {
|
||||
break;
|
||||
}
|
||||
input = rest;
|
||||
}
|
||||
Err(Err::Reason(msg)) => {
|
||||
error!("{}", msg);
|
||||
return Err(ExitCode::Failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
error!("Could not find file '{}'", path);
|
||||
Err(ExitCode::Failure)
|
||||
}
|
||||
exec(env, args[1])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,6 +353,14 @@ fn help() -> Result<(), ExitCode> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn test_exp() {
|
||||
assert_eq!(Exp::Bool(true).is_truthy(), true);
|
||||
assert_eq!(Exp::Bool(false).is_truthy(), false);
|
||||
assert_eq!(Exp::Num(Number::Int(42)).is_truthy(), true);
|
||||
assert_eq!(Exp::List(vec![]).is_truthy(), false);
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn test_lisp() {
|
||||
use core::f64::consts::PI;
|
||||
|
@ -411,6 +444,12 @@ fn test_lisp() {
|
|||
assert_eq!(eval!("(if (> 2 4) 1)"), "()");
|
||||
assert_eq!(eval!("(if (< 2 4) 1 2)"), "1");
|
||||
assert_eq!(eval!("(if (> 2 4) 1 2)"), "2");
|
||||
assert_eq!(eval!("(if true 1 2)"), "1");
|
||||
assert_eq!(eval!("(if false 1 2)"), "2");
|
||||
assert_eq!(eval!("(if '() 1 2)"), "2");
|
||||
assert_eq!(eval!("(if 0 1 2)"), "1");
|
||||
assert_eq!(eval!("(if 42 1 2)"), "1");
|
||||
assert_eq!(eval!("(if \"\" 1 2)"), "1");
|
||||
|
||||
// while
|
||||
assert_eq!(eval!("(do (variable i 0) (while (< i 5) (set i (+ i 1))) i)"), "5");
|
||||
|
@ -495,8 +534,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!("(string.trim \"abc\n\")"), "\"abc\"");
|
||||
assert_eq!(eval!("(string.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");
|
||||
|
@ -536,9 +575,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)");
|
||||
|
@ -570,4 +609,23 @@ fn test_lisp() {
|
|||
assert_eq!(eval!("# comment\n# comment"), "()");
|
||||
assert_eq!(eval!("(+ 1 2 3) # comment"), "6");
|
||||
assert_eq!(eval!("(+ 1 2 3) # comment\n# comment"), "6");
|
||||
|
||||
// list
|
||||
assert_eq!(eval!("(list 1 2 3)"), "(1 2 3)");
|
||||
|
||||
// dict
|
||||
assert_eq!(eval!("(dict \"a\" 1 \"b\" 2 \"c\" 3)"), "(dict \"a\" 1 \"b\" 2 \"c\" 3)");
|
||||
|
||||
// get
|
||||
assert_eq!(eval!("(get \"Hello\" 0)"), "\"H\"");
|
||||
assert_eq!(eval!("(get \"Hello\" 6)"), "\"\"");
|
||||
assert_eq!(eval!("(get (list 1 2 3) 0)"), "1");
|
||||
assert_eq!(eval!("(get (list 1 2 3) 3)"), "()");
|
||||
assert_eq!(eval!("(get (dict \"a\" 1 \"b\" 2 \"c\" 3) \"a\")"), "1");
|
||||
assert_eq!(eval!("(get (dict \"a\" 1 \"b\" 2 \"c\" 3) \"d\")"), "()");
|
||||
|
||||
// put
|
||||
assert_eq!(eval!("(put (dict \"a\" 1 \"b\" 2) \"c\" 3)"), "(dict \"a\" 1 \"b\" 2 \"c\" 3)");
|
||||
assert_eq!(eval!("(put (list 1 3) 1 2)"), "(1 2 3)");
|
||||
assert_eq!(eval!("(put \"Heo\" 2 \"ll\")"), "\"Hello\"");
|
||||
}
|
||||
|
|
|
@ -96,17 +96,19 @@ fn float(input: &str) -> IResult<&str, &str> {
|
|||
}
|
||||
|
||||
fn is_symbol_letter(c: char) -> bool {
|
||||
let chars = "<>=-+*/%^?.";
|
||||
let chars = "$<>=-+*/%^?.";
|
||||
c.is_alphanumeric() || chars.contains(c)
|
||||
}
|
||||
|
||||
fn parse_str(input: &str) -> IResult<&str, Exp> {
|
||||
let escaped = map(opt(escaped_transform(is_not("\\\""), '\\', alt((
|
||||
value("\\", tag("\\")),
|
||||
value("\"", tag("\"")),
|
||||
value("\n", tag("n")),
|
||||
value("\r", tag("r")),
|
||||
value("\t", tag("t")),
|
||||
value("\\", tag("\\")),
|
||||
value("\"", tag("\"")),
|
||||
value("\n", tag("n")),
|
||||
value("\r", tag("r")),
|
||||
value("\t", tag("t")),
|
||||
value("\x08", tag("b")),
|
||||
value("\x1B", tag("e")),
|
||||
)))), |inner| inner.unwrap_or("".to_string()));
|
||||
let (input, s) = delimited(char('"'), escaped, char('"'))(input)?;
|
||||
Ok((input, Exp::Str(s)))
|
||||
|
|
|
@ -8,7 +8,9 @@ use crate::api::regex::Regex;
|
|||
use crate::api::syscall;
|
||||
use crate::sys::fs::OpenFlag;
|
||||
use crate::usr::shell;
|
||||
use crate::usr::host;
|
||||
|
||||
use alloc::collections::btree_map::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::string::ToString;
|
||||
|
@ -156,7 +158,7 @@ pub fn lisp_trunc(args: &[Exp]) -> Result<Exp, Err> {
|
|||
Ok(Exp::Num(number(&args[0])?.trunc()))
|
||||
}
|
||||
|
||||
pub fn lisp_system(args: &[Exp]) -> Result<Exp, Err> {
|
||||
pub fn lisp_shell(args: &[Exp]) -> Result<Exp, Err> {
|
||||
ensure_length_gt!(args, 0);
|
||||
let cmd = strings(args)?.join(" ");
|
||||
match shell::exec(&cmd) {
|
||||
|
@ -228,6 +230,7 @@ pub fn lisp_type(args: &[Exp]) -> Result<Exp, Err> {
|
|||
Exp::Function(_) => "function",
|
||||
Exp::Macro(_) => "macro",
|
||||
Exp::List(_) => "list",
|
||||
Exp::Dict(_) => "dict",
|
||||
Exp::Bool(_) => "boolean",
|
||||
Exp::Str(_) => "string",
|
||||
Exp::Sym(_) => "symbol",
|
||||
|
@ -278,28 +281,6 @@ pub fn lisp_contains(args: &[Exp]) -> Result<Exp, Err> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn lisp_nth(args: &[Exp]) -> Result<Exp, Err> {
|
||||
ensure_length_eq!(args, 2);
|
||||
let i = usize::try_from(number(&args[1])?)?;
|
||||
match &args[0] {
|
||||
Exp::List(l) => {
|
||||
if let Some(e) = l.get(i) {
|
||||
Ok(e.clone())
|
||||
} else {
|
||||
Ok(Exp::List(Vec::new()))
|
||||
}
|
||||
}
|
||||
Exp::Str(s) => {
|
||||
if let Some(c) = s.chars().nth(i) {
|
||||
Ok(Exp::Str(c.to_string()))
|
||||
} else {
|
||||
Ok(Exp::Str("".to_string()))
|
||||
}
|
||||
}
|
||||
_ => expected!("first argument to be a list or a string")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lisp_slice(args: &[Exp]) -> Result<Exp, Err> {
|
||||
let (a, b) = match args.len() {
|
||||
2 => (usize::try_from(number(&args[1])?)?, 1),
|
||||
|
@ -339,8 +320,8 @@ pub fn lisp_length(args: &[Exp]) -> Result<Exp, Err> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: This could also concat strings
|
||||
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 {
|
||||
|
@ -520,3 +501,88 @@ pub fn lisp_socket_accept(args: &[Exp]) -> Result<Exp, Err> {
|
|||
could_not!("accept connections")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lisp_dict(args: &[Exp]) -> Result<Exp, Err> {
|
||||
let mut dict = BTreeMap::new();
|
||||
for chunk in args.chunks(2) {
|
||||
match chunk {
|
||||
[k, v] => dict.insert(format!("{}", k), v.clone()),
|
||||
[k] => dict.insert(format!("{}", k), Exp::List(vec![])),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
Ok(Exp::Dict(dict))
|
||||
}
|
||||
|
||||
pub fn lisp_get(args: &[Exp]) -> Result<Exp, Err> {
|
||||
ensure_length_eq!(args, 2);
|
||||
match &args[0] {
|
||||
Exp::Dict(dict) => {
|
||||
let key = format!("{}", args[1]);
|
||||
if let Some(val) = dict.get(&key) {
|
||||
Ok(val.clone())
|
||||
} else {
|
||||
Ok(Exp::List(vec![]))
|
||||
}
|
||||
}
|
||||
Exp::List(l) => {
|
||||
let i = usize::try_from(number(&args[1])?)?;
|
||||
if let Some(e) = l.get(i) {
|
||||
Ok(e.clone())
|
||||
} else {
|
||||
Ok(Exp::List(Vec::new()))
|
||||
}
|
||||
}
|
||||
Exp::Str(s) => {
|
||||
let i = usize::try_from(number(&args[1])?)?;
|
||||
if let Some(c) = s.chars().nth(i) {
|
||||
Ok(Exp::Str(c.to_string()))
|
||||
} else {
|
||||
Ok(Exp::Str("".to_string()))
|
||||
}
|
||||
}
|
||||
_ => expected!("first argument to be a dict, a list, or a string")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lisp_put(args: &[Exp]) -> Result<Exp, Err> {
|
||||
ensure_length_eq!(args, 3);
|
||||
match &args[0] {
|
||||
Exp::Dict(dict) => {
|
||||
let mut dict = dict.clone();
|
||||
let key = format!("{}", args[1]);
|
||||
let val = args[2].clone();
|
||||
dict.insert(key, val);
|
||||
Ok(Exp::Dict(dict))
|
||||
}
|
||||
Exp::List(list) => {
|
||||
let i = usize::try_from(number(&args[1])?)?;
|
||||
let val = args[2].clone();
|
||||
let mut list = list.clone();
|
||||
list.insert(i, val);
|
||||
Ok(Exp::List(list))
|
||||
}
|
||||
Exp::Str(s) => {
|
||||
let i = usize::try_from(number(&args[1])?)?;
|
||||
let v: Vec<char> = string(&args[2])?.chars().collect();
|
||||
let mut s: Vec<char> = s.chars().collect();
|
||||
s.splice(i..i, v);
|
||||
let s: String = s.into_iter().collect();
|
||||
Ok(Exp::Str(s))
|
||||
}
|
||||
_ => expected!("first argument to be a dict, a list, or a string")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lisp_host(args: &[Exp]) -> Result<Exp, Err> {
|
||||
ensure_length_eq!(args, 1);
|
||||
let hostname = string(&args[0])?;
|
||||
match host::resolve(&hostname) {
|
||||
Ok(addr) => {
|
||||
Ok(Exp::Str(format!("{}", addr)))
|
||||
}
|
||||
Err(_) => {
|
||||
Ok(Exp::List(vec![]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,9 @@ of the Shell.</p>
|
|||
<h3>Literals</h3>
|
||||
|
||||
<ul>
|
||||
<li>Number: <code>2.5</code>, <code>-25</code>, <code>255</code>, <code>0xFF</code>, <code>0xDEAD_C0DE</code>, <code>0b101010</code></li>
|
||||
<li>String: <code>"Hello, World!"</code></li>
|
||||
<li>Number: <code>2.5</code>, <code>-25</code>, <code>255</code>, <code>0xFF</code>, <code>0xDEAD_C0DE</code></li>
|
||||
<li>Escape: <code>\b</code>, <code>\e</code>, <code>\n</code>, <code>\r</code>, <code>\t</code>, <code>\"</code>, <code>\\</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Built-in Operators</h3>
|
||||
|
@ -63,35 +64,37 @@ of the Shell.</p>
|
|||
<h3>Primitive Operators</h3>
|
||||
|
||||
<ul>
|
||||
<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>), <code>parse</code></li>
|
||||
<li><code>string</code> (aliased to <code>str</code>)</li>
|
||||
<li><code>string->number</code> (aliased to to <code>str->num</code>)</li>
|
||||
<li><code>string->binary</code> and <code>binary->string</code> (aliased to <code>str->bin</code> and <code>bin->str</code>)</li>
|
||||
<li><code>number->binary</code> and <code>binary->number</code> (aliased to <code>num->bin</code> and <code>bin->num</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>abs</code>, <code>mod</code>, <code>rem</code> (aliased to <code>%</code>)</li>
|
||||
<li><code>regex/find</code></li>
|
||||
<li><code>shell</code> (aliased to <code>sh</code>)</li>
|
||||
<li>Arithmetic operations: <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>^</code>, <code>rem</code> (aliased to <code>%</code>), <code>trunc</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>></code>, <code><</code>, <code>>=</code>, <code><=</code>, <code>=</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> and <code>string.split</code> (aliased to <code>str.trim</code> and <code>str.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>
|
||||
<li>Enumerable: <code>length</code> (aliased to <code>len</code>), <code>put</code>, <code>get</code>, <code>slice</code>, <code>contains?</code></li>
|
||||
<li>String: <code>string/trim</code> and <code>string/split</code> (aliased to <code>str/trim</code> and <code>str/split</code>)</li>
|
||||
<li>List: <code>list</code>, <code>concat</code>, <code>chunks</code>, <code>sort</code>, <code>unique</code> (aliased to <code>uniq</code>)</li>
|
||||
<li>Dict: <code>dict</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>
|
||||
<li>Net: <code>host</code>, <code>socket/connect</code>, <code>socket/listen</code>, <code>socket/accept</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Core Library</h3>
|
||||
|
||||
<ul>
|
||||
<li><code>nil</code>, <code>nil?</code>, <code>list?</code></li>
|
||||
<li><code>nil</code>, <code>nil?</code>, <code>list?</code>, <code>empty?</code></li>
|
||||
<li><code>boolean?</code> (aliased to <code>bool?</code>), <code>string?</code> (aliased to <code>str?</code>), <code>symbol?</code> (aliased to <code>sym?</code>), <code>number?</code> (aliased to <code>num?</code>)</li>
|
||||
<li><code>function?</code> (aliased to <code>fun?</code>), <code>macro?</code> (aliased to <code>mac?</code>)</li>
|
||||
<li><code>first</code>, <code>second</code>, <code>third</code>, <code>rest</code></li>
|
||||
<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>abs</code>, <code>mod</code>, <code>min</code>, <code>max</code></li>
|
||||
<li><code>first</code>, <code>second</code>, <code>third</code>, <code>last</code>, <code>rest</code>, <code>push</code></li>
|
||||
<li><code>map</code>, <code>reduce</code>, <code>reverse</code> (aliased to <code>rev</code>), <code>range</code>, <code>filter</code>, <code>reject</code>, <code>intersection</code></li>
|
||||
<li><code>not</code>, <code>and</code>, <code>or</code></li>
|
||||
<li><code>let</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>
|
||||
<li><code>socket.connect</code>, <code>socket.listen</code>, <code>socket.accept</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>
|
||||
|
@ -253,7 +256,12 @@ language and reading from the filesystem.</p>
|
|||
<h3>Unreleased</h3>
|
||||
|
||||
<ul>
|
||||
<li>Add hexadecimal number literals</li>
|
||||
<li>Add binary and hexadecimal number literals</li>
|
||||
<li>Test for truthiness (neither <code>false</code> nor <code>nil</code>) in conditions of <code>if</code> and <code>while</code></li>
|
||||
<li>Rename <code>nth</code> to <code>get</code></li>
|
||||
<li>Add <code>empty?</code>, <code>reject</code>, <code>put</code>, <code>push</code>, and <code>host</code> functions`</li>
|
||||
<li>Add <code>dict</code> type</li>
|
||||
<li>Use <code>/</code> instead of <code>.</code> as namespace separator</li>
|
||||
</ul>
|
||||
<footer><p><a href="/">MOROS</a></footer>
|
||||
</body>
|
||||
|
|
Loading…
Reference in New Issue