use koto::{PtrMut, prelude::*, runtime::Result};
use koto_bytecode::CompilerSettings;
fn run_repl_mode_test(inputs_and_expected_outputs: &[(&str, &str)]) {
let output = PtrMut::from(String::new());
let mut koto = Koto::with_settings(
KotoSettings::default()
.with_stdout(OutputCapture {
output: output.clone(),
})
.with_stderr(OutputCapture {
output: output.clone(),
}),
);
let mut chunks = Vec::with_capacity(inputs_and_expected_outputs.len());
for (input, expected_output) in inputs_and_expected_outputs {
let chunk = match koto.compile(CompileArgs {
script: input,
script_path: None,
compiler_settings: CompilerSettings {
export_top_level_ids: true,
..Default::default()
},
}) {
Ok(chunk) => {
chunks.push((input, chunk.clone()));
chunk
}
Err(error) => {
panic!("{error}");
}
};
if let Err(error) = koto.run(chunk) {
for (input, chunk) in chunks.iter() {
println!("\n--------\n{input}\n--------\n");
println!("Constants\n---------\n{}\n", chunk.constants);
println!(
"Instructions\n------------\n{}",
Chunk::instructions_as_string(chunk.clone(), &[input])
);
}
panic!("{error}");
}
assert_eq!(&output.borrow().trim(), &expected_output.trim());
output.borrow_mut().clear();
}
}
#[derive(Debug)]
struct OutputCapture {
output: PtrMut<String>,
}
impl KotoFile for OutputCapture {
fn id(&self) -> KString {
"_stdout_".into()
}
}
impl KotoRead for OutputCapture {}
impl KotoWrite for OutputCapture {
fn write(&self, bytes: &[u8]) -> Result<()> {
let bytes_str = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(e) => return Err(e.to_string().into()),
};
self.output.borrow_mut().push_str(bytes_str);
Ok(())
}
fn write_line(&self, output: &str) -> Result<()> {
let mut unlocked = self.output.borrow_mut();
unlocked.push_str(output);
unlocked.push('\n');
Ok(())
}
fn flush(&self) -> Result<()> {
Ok(())
}
}
mod repl_mode {
use super::*;
#[test]
fn basic_arithmetic() {
run_repl_mode_test(&[("a = 2", ""), ("print a + a", "4")]);
}
#[test]
fn import_single_item() {
run_repl_mode_test(&[
("from string import to_number", ""),
("print to_number '0x7f'", "127"),
]);
}
#[test]
fn import_multiple_items() {
run_repl_mode_test(&[
("from string import to_lowercase, to_uppercase", ""),
("print to_lowercase 'HEY'", "hey"),
("print to_uppercase 'hey'", "HEY"),
]);
}
#[test]
fn import_with_assignment() {
run_repl_mode_test(&[
("print2 = from io import print", ""),
("print2 'hello!'", "hello!"),
]);
}
#[test]
fn wildcard_import() {
run_repl_mode_test(&[("from number import *", ""), ("print abs -1", "1")]);
}
#[test]
fn for_loop() {
run_repl_mode_test(&[
("min = 1", ""),
("max = 3", ""),
(
"
for x in min..=max
print 'x: {x}'
",
"
x: 1
x: 2
x: 3
",
),
]);
}
#[test]
fn negated_id() {
run_repl_mode_test(&[("a = 2", ""), ("b = -a", ""), ("print a + b", "0")]);
}
#[test]
fn multi_assign() {
run_repl_mode_test(&[("x, y = 1, 2", ""), ("print x + y", " 3")]);
}
#[test]
fn add_assign_number() {
run_repl_mode_test(&[
("print x = 1", "1"),
("print x += 1", "2"),
("print x += 1", "3"),
]);
}
#[test]
fn subtract_assign_number() {
run_repl_mode_test(&[
("print x = 1", "1"),
("print x -= 1", "0"),
("print x -= 1", "-1"),
]);
}
}