Proper quasiquote expansion rules, unquote-splice

This commit is contained in:
2026-05-22 10:10:57 +03:00
parent d21f927a24
commit 6936966455
21 changed files with 516 additions and 156 deletions
+51 -33
View File
@@ -1,57 +1,56 @@
use std::{
io::{self, BufReader, Read},
rc::Rc,
};
use std::{fs, path::Path, rc::Rc};
use lysp::{
error::MachineErrorAt,
read::{FileReader, read},
vm::{env::Environment, machine::Machine, prelude, value::Value},
};
struct SliceReader<'a>(&'a [u8]);
impl Read for SliceReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let count = self.0.len().min(buf.len());
buf[..count].copy_from_slice(&self.0[..count]);
self.0 = &self.0[count..];
Ok(count)
}
}
#[track_caller]
fn eval_str_in(code: &str, env: &Rc<Environment>) -> Result<Value, MachineErrorAt> {
let mut machine = Machine::default();
let reader = BufReader::new(SliceReader(code.as_bytes()));
let mut reader = FileReader::new(reader);
let mut last_value = None;
loop {
let value = match read(&mut reader, &mut machine, env) {
Ok(Some(value)) => value,
Ok(None) => break,
Err(error) => panic!("{error}"),
};
last_value = Some(machine.evaluate_value(Default::default(), None, env, value));
}
last_value.expect("no expressions evaluated")
machine.evaluate_str(Default::default(), None, env, code)
}
#[track_caller]
fn eval_str(code: &str) -> Value {
let env = Rc::new(Environment::default());
prelude::load(&env);
eval_str_in(code, &env).expect("expression evaluation failed")
match eval_str_in(code, &env) {
Ok(value) => value,
Err(error) => {
eprintln!("Couldn't evaluate expression:");
eprintln!();
eprintln!(" {code}");
eprintln!();
eprintln!(":: {error}");
panic!("TEST FAILED");
}
}
}
#[track_caller]
fn eval_str_err(code: &str) -> MachineErrorAt {
let env = Rc::new(Environment::default());
prelude::load(&env);
eval_str_in(code, &env).expect_err("expression was expected to fail")
match eval_str_in(code, &env) {
Ok(value) => {
eprintln!("Expected the code to fail to evaluate, but it returned success:");
eprintln!();
eprintln!(" {code}");
eprintln!();
eprintln!("Returned");
eprintln!();
eprintln!(":: {value}");
panic!("TEST FAILED");
}
Err(error) => error,
}
}
#[track_caller]
fn eval_file<P: AsRef<Path>>(path: P) -> Value {
let code = fs::read_to_string(path).expect("file read failed");
eval_str(&code)
}
#[test]
@@ -186,3 +185,22 @@ fn test_macro() {
])
);
}
#[test]
fn test_examples_work() {
const EXCLUDE: &[&str] = &["repl.lysp", "echo.lysp", "io.lysp"];
// None of them should crash at least
for file in fs::read_dir("examples").unwrap() {
let entry = file.unwrap();
let filename = entry.file_name();
let filename = filename.to_str().unwrap();
if !filename.ends_with(".lysp") || EXCLUDE.contains(&filename) {
continue;
}
eprintln!("Eval {}", entry.path().display());
eval_file(entry.path());
}
}