EPrintlns and libs

- Moving all repl prints to stderr
- Adding small functions to module string
This commit is contained in:
teo3300
2024-06-24 00:42:49 +09:00
parent 287b96ea7d
commit d86bd7f7ae
9 changed files with 38 additions and 10 deletions

View File

@ -71,6 +71,10 @@
"open a mal file and evaluate its content" "open a mal file and evaluate its content"
(eval (read-string (str "(do " (slurp f) "\nnil)"))))) (eval (read-string (str "(do " (slurp f) "\nnil)")))))
(def! module (fn* [f]
"load a file from the library path"
(load-file (str MAL_HOME "/libs/" f ".mal"))))
(def! conf-reload (fn* [] (def! conf-reload (fn* []
"reload mal config file" "reload mal config file"
(load-file (str MAL_HOME "/" "config.mal")))) (load-file (str MAL_HOME "/" "config.mal"))))
@ -139,8 +143,8 @@
(def! BANNER (def! BANNER
(str (str
"; rust-mal: a toy lisp interpreter written in rust\n" "; rust-mal: a toy lisp interpreter written in rust\n"
"; $ mal [filename ...] : load specified modules at start\n" "; $ mal [filename ...] : load specified files at start\n"
"; (load-file <name>) : load specified module while mal is running\n" "; (load-file <name>) : load specified file while mal is running\n"
"; (find [pattern...]) : list symbols matching all patterns\n" "; (find [pattern...]) : list symbols matching all patterns\n"
"; (help <symbol>) : print information about a symbol\n" "; (help <symbol>) : print information about a symbol\n"
";\n" ";\n"
@ -186,3 +190,5 @@
c c
(collect-r (f c (car l)) (cdr l))))) (collect-r (f c (car l)) (cdr l)))))
(collect-r i l))) (collect-r i l)))
(conf-reload)

View File

@ -1,12 +1,18 @@
(def! char (fn* [l] (def! char (fn* [l]
(car (boom l)))) (car (boom l))))
(def! #n (char "\n"))
(def! #s (char " "))
(def! #t (char "\t"))
(def! char? (fn* [a] (def! char? (fn* [a]
(= (type a) :char))) (= (type a) :char)))
(def! string? (fn* [a] (def! string? (fn* [a]
(= (type a) :string))) (= (type a) :string)))
(def! strlen (fn* [s] (count (boom s))))
(def! strc (fn* [l] (def! strc (fn* [l]
(def! strc-r (fn* [l s] (def! strc-r (fn* [l s]
(if (empty? l) (if (empty? l)
@ -38,3 +44,5 @@
(def! chsub (fn* [s c1 c2] (def! chsub (fn* [s c1 c2]
(strc (map-if (fn* [x] (= x c1)) (fn* [x] c2) (boom s))))) (strc (map-if (fn* [x] (= x c1)) (fn* [x] c2) (boom s)))))

View File

@ -76,7 +76,7 @@ pub fn ns_init() -> Env {
"car" => Fun(|a| mal_car(car(a)?), "Returns the first element of the list, NIL if its empty"), "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"), "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) // 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"), "boom" => Fun(mal_boom, "Split a string into a list of characters\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"), "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"), "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"), "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

@ -129,7 +129,7 @@ pub fn help_form(list: &[MalType], env: Env) -> MalRet {
let (sym, _) = car_cdr(list)?; let (sym, _) = car_cdr(list)?;
let sym_str = sym.if_symbol()?; let sym_str = sym.if_symbol()?;
match eval(sym, env.clone())? { match eval(sym, env.clone())? {
M::Fun(_, desc) => println!("{}\t[builtin]: {}\n", sym_str, desc), M::Fun(_, desc) => eprintln!("{}\t[builtin]: {}\n", sym_str, desc),
M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast), M::MalFun { params, ast, .. } => print_malfun(sym_str, params, ast),
_ => eprintln!("{}\t[symbol]: {}\n", sym_str, prt(&env_get(&env, sym_str)?)), _ => eprintln!("{}\t[symbol]: {}\n", sym_str, prt(&env_get(&env, sym_str)?)),
} }

View File

@ -24,12 +24,15 @@ fn main() {
load_home_file("core.mal", &reply_env, true); load_home_file("core.mal", &reply_env, true);
// Load config files ($MAL_HOME/config.mal, or default $HOME/.config/mal/config.mal) // Load config files ($MAL_HOME/config.mal, or default $HOME/.config/mal/config.mal)
// [warn: false] since this file is optional // [warn: false] since this file is optional
load_home_file("config.mal", &reply_env, false); // replaced by (ok? '(reload-config)) at the end of core.mal
// - I used this to overwrite BANNER to prevent it from displaying
// based on conf
//load_home_file("config.mal", &reply_env, false);
// load all files passed as arguments // load all files passed as arguments
args().collect::<Vec<String>>()[1..].iter().for_each(|f| { args().collect::<Vec<String>>()[1..].iter().for_each(|f| {
if let Err(e) = load_file(f, &reply_env) { if let Err(e) = load_file(f, &reply_env) {
println!("{}", e.message()) eprintln!("{}", e.message())
} }
}); });

View File

@ -15,6 +15,8 @@ mod functional {
)); ));
}}; }};
} }
// TODO: modify to accept more parameters for test/libraries
// TODO: text 'boom' from within rust
#[test] #[test]
fn assert() { fn assert() {

View File

@ -109,16 +109,19 @@ pub fn interactive(env: Env) {
// Perform rep on whole available input // Perform rep on whole available input
match rep(&parser, &env) { match rep(&parser, &env) {
Ok(output) => output.iter().for_each(|el| println!("; [{}]> {}", num, el)), Ok(output) => output.iter().for_each(|el| {
eprintln!("; [{}]> {}", num, el);
num += 1;
}),
Err(error) => { Err(error) => {
if error.is_recoverable() { if error.is_recoverable() {
// && line != "\n" { // && line != "\n" {
continue; continue;
} }
eprintln!("; [{}]> Error @ {}", num, error.message()); eprintln!("; [{}]> Error @ {}", num, error.message());
num += 1
} }
} }
num += 1;
break; break;
} }
Err(ReadlineError::Interrupted) => { Err(ReadlineError::Interrupted) => {

View File

@ -98,14 +98,16 @@ impl Reader {
tk => { tk => {
if Regex::new(r"^-?[0-9]+$").unwrap().is_match(tk) { if Regex::new(r"^-?[0-9]+$").unwrap().is_match(tk) {
return Ok(Int(tk.parse::<isize>().unwrap())); return Ok(Int(tk.parse::<isize>().unwrap()));
} else if tk.starts_with('\"') { }
if tk.starts_with('\"') {
if tk.len() > 1 && tk.ends_with('\"') { if tk.len() > 1 && tk.ends_with('\"') {
return Ok(Str(unescape_str(tk).into())); return Ok(Str(unescape_str(tk).into()));
} }
return Err(MalErr::unrecoverable( return Err(MalErr::unrecoverable(
"End of line reached without closing string", "End of line reached without closing string",
)); ));
} else if tk.starts_with(':') { }
if tk.starts_with(':') {
return Ok(Key(format!("ʞ{}", tk).into())); return Ok(Key(format!("ʞ{}", tk).into()));
} }
Ok(Sym(tk.into())) Ok(Sym(tk.into()))

View File

@ -220,6 +220,8 @@ pub fn escape_str(s: &str) -> String {
String::from(s) String::from(s)
.replace('\\', "\\\\") .replace('\\', "\\\\")
.replace('\n', "\\n") .replace('\n', "\\n")
.replace('\u{000D}', "\\r")
.replace('\t', "\\t")
.replace('\"', "\\\"") .replace('\"', "\\\"")
) )
} }
@ -228,6 +230,8 @@ pub fn unescape_str(s: &str) -> String {
String::from(&s[1..s.len() - 1]) String::from(&s[1..s.len() - 1])
.replace("\\\\", "\\") .replace("\\\\", "\\")
.replace("\\n", "\n") .replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\\"", "\"") .replace("\\\"", "\"")
} }