Adding string processing

- 'character' type, only contructible by decomposing strings
- 'boom' builtin function ;) to create a list of characters from a string
- string library with basic manipulation functionalities
- slightly improved Makefile
This commit is contained in:
teo3300
2024-06-22 23:03:25 +09:00
parent 5996af1aea
commit 287b96ea7d
7 changed files with 73 additions and 7 deletions

View File

@ -1,17 +1,23 @@
default: build-release
build-release:
cargo build --release
@echo "Build release"
@cargo build --release
test:
cargo test --release
@echo "Test release"
@cargo test --release
conf:
mkdir -p ${HOME}/.config/mal
@echo "Copy core and libraries"
@mkdir -p ${HOME}/.config/mal
cp -f core/core.mal ${HOME}/.config/mal/
mkdir -p ${HOME}/.config/mal/libs
@mkdir -p ${HOME}/.config/mal/libs
cp -f libs/* ${HOME}/.config/mal/libs/
install: build-release test conf
@echo "Install mal"
sudo cp target/release/rust-mal /usr/local/bin/mal
sudo chown ${USER} /usr/local/bin/mal
@sudo chown ${USER} /usr/local/bin/mal
@echo "To start mal run:"
@printf "\tmal [path/module.mal ...]\n\n"

View File

@ -139,6 +139,8 @@
(def! BANNER
(str
"; rust-mal: a toy lisp interpreter written in rust\n"
"; $ mal [filename ...] : load specified modules at start\n"
"; (load-file <name>) : load specified module while mal is running\n"
"; (find [pattern...]) : list symbols matching all patterns\n"
"; (help <symbol>) : print information about a symbol\n"
";\n"

40
libs/string.mal Normal file
View File

@ -0,0 +1,40 @@
(def! char (fn* [l]
(car (boom l))))
(def! char? (fn* [a]
(= (type a) :char)))
(def! string? (fn* [a]
(= (type a) :string)))
(def! strc (fn* [l]
(def! strc-r (fn* [l s]
(if (empty? l)
s
(strc-r (cdr l) (str s (car l))))))
(strc-r l "")))
(def! split (fn* [s c]
"Split the string at every occurrence of character sc"
(if (and (string? s)
(char? c))
(def! split-r (fn* [l t r]
(if (empty? l)
(cons t r)
(do (def! cc (car l))
(if (= cc c)
(split-r (cdr l) "" (cons t r))
(split-r (cdr l) (str t cc) r))))))
(raise "split: accepts a string and a char as arguments"))
(reverse (split-r (boom s) "" '()))))
(def! join (fn* [l s]
"Join element of list l to a stiring, using s as separator"
(def! join-r (fn* [l t]
(if (empty? l)
t
(join-r (cdr l) (str t s (car l))))))
(join-r (cdr l) (car l))))
(def! chsub (fn* [s c1 c2]
(strc (map-if (fn* [x] (= x c1)) (fn* [x] c2) (boom s)))))

View File

@ -1,8 +1,8 @@
use std::{cell::RefCell, env, rc::Rc};
use crate::env::{
any_zero, arithmetic_op, car, comparison_op, env_new, env_set, mal_car, mal_cdr, mal_cons,
mal_exit, Env,
any_zero, arithmetic_op, car, comparison_op, env_new, env_set, mal_boom, mal_car, mal_cdr,
mal_cons, mal_exit, Env,
};
// This is the first time I implement a macro, and I'm copying it
@ -75,6 +75,8 @@ pub fn ns_init() -> Env {
"=" => Fun(mal_equals, "Return true if the first two parameters are the same type and content, in case of lists propagate to all elements (NOT IMPLEMENTED for 'Map', 'Fun' and 'MalFun')"),
"car" => Fun(|a| mal_car(car(a)?), "Returns the first element of the list, NIL if its empty"),
"cdr" => Fun(|a| mal_cdr(car(a)?), "Returns all the list but the first element"),
// A tribute to PHP's explode (PHP, a language I never used)
"boom" => Fun(mal_boom, "Split a string into a list of string\n; BE CAREFUL WHEN USING"),
"read-string" => Fun(|a| read_str(Reader::new().push(car(a)?.if_string()?)).map_err(MalErr::severe), "Tokenize and read the first argument"),
"slurp" => Fun(|a| Ok(Str(read_file(car(a)?.if_string()?)?)), "Read a file and return the content as a string"),
"atom" => Fun(|a| Ok(Atom(Rc::new(RefCell::new(car(a).unwrap_or_default().clone())))), "Return an atom pointing to the given arg"),

View File

@ -257,3 +257,9 @@ pub fn mal_exit(list: &[MalType]) -> MalRet {
_ => exit(-1),
}
}
// TODO: find another way to process strings
pub fn mal_boom(args: &[MalType]) -> MalRet {
let string = car(args)?.if_string()?;
Ok(M::List(string.chars().map(M::Ch).collect()))
}

View File

@ -54,6 +54,13 @@ pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
M::Fun(..) => "#<builtin>".to_string(),
M::MalFun { .. } => "#<function>".to_string(),
M::Atom(sub) => format!("Atom({})", pr_str(&sub.borrow(), print_readably)),
M::Ch(c) => {
if print_readably {
format!("#\\{}", c)
} else {
c.to_string()
}
}
}
}

View File

@ -23,6 +23,7 @@ pub enum MalType {
Sym(MalStr),
Key(MalStr),
Str(MalStr),
Ch(char),
Int(isize),
Bool(bool),
Atom(Rc<RefCell<MalType>>),
@ -95,6 +96,7 @@ impl MalType {
M::Vector(_) => "vector",
M::Map(_) => "map",
M::Atom(_) => "atom",
M::Ch(_) => "char",
})
.into())
}
@ -108,6 +110,7 @@ fn mal_compare(args: (&MalType, &MalType)) -> bool {
(M::Nil, M::Nil) => true,
(M::Bool(a), M::Bool(b)) => a == b,
(M::Int(a), M::Int(b)) => a == b,
(M::Ch(a), M::Ch(b)) => a == b,
(M::Key(a), M::Key(b)) | (M::Str(a), M::Str(b)) | (M::Sym(a), M::Sym(b)) => a == b,
(M::List(a), M::List(b)) | (M::Vector(a), M::Vector(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(mal_compare)