Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 67 additions & 12 deletions cli/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::process::exit;
use std::{
io::{self, Read, Write},
process::exit,
sync::{Mutex, MutexGuard},
};

use clap::Args;

Expand All @@ -7,6 +11,7 @@ use ivm::{
ext::Extrinsics,
heap::Heap,
port::{Port, Tag},
stats::Stats,
};
use ivy::{ast::Nets, host::Host, optimize::Optimizer};

Expand Down Expand Up @@ -37,16 +42,49 @@ pub struct RunArgs {
}

impl RunArgs {
pub fn run(self, nets: Nets, debug_hint: bool) {
pub fn run(&self, nets: &Nets) -> RunResult {
self._run(nets, io::stdin, io::stdout)
}

/// Runs the `nets` with an empty stdin and capturing any writes to stdout.
pub fn run_capturing_output(&self, nets: &Nets) -> RunResult {
pub struct SharedWriter<'a>(pub MutexGuard<'a, Vec<u8>>);

impl Write for SharedWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

let input: &[u8] = &[];
let output = Mutex::default();

let mut result = self._run(nets, || input, || SharedWriter(output.lock().unwrap()));
result.output = Some(output.into_inner().unwrap());

result
}

fn _run<R: Read, W: Write>(
&self,
nets: &Nets,
io_input_fn: impl Copy + Fn() -> R + Sync,
io_output_fn: impl Copy + Fn() -> W + Sync,
) -> RunResult {
let mut host = &mut Host::default();
let heap = match self.heap {
Some(size) => Heap::with_size(size).expect("heap allocation failed"),
None => Heap::new(),
};
let mut extrinsics = Extrinsics::default();

host.register_default_extrinsics(&mut extrinsics);
host.insert_nets(&nets);
host.register_default_extrinsics(&mut extrinsics, io_input_fn, io_output_fn);
host.insert_nets(nets);

let main = host.get("::").expect("missing main");
let mut ivm = IVM::new(&heap, &extrinsics);
Expand All @@ -63,24 +101,41 @@ impl RunArgs {
}

let out = ivm.follow(Port::new_wire(node.2));
let no_io =
out.tag() != Tag::ExtVal || unsafe { out.as_ext_val() }.bits() != host.new_io().bits();
let vicious = ivm.stats.mem_free < ivm.stats.mem_alloc;
if no_io {

RunResult {
stats: if self.no_stats { None } else { Some(ivm.stats) },
no_io: out.tag() != Tag::ExtVal || unsafe { out.as_ext_val() }.bits() != host.new_io().bits(),
vicious: ivm.stats.mem_free < ivm.stats.mem_alloc,
output: None,
}
}
}

pub struct RunResult {
pub stats: Option<Stats>,
pub no_io: bool,
pub vicious: bool,
pub output: Option<Vec<u8>>,
}

impl RunResult {
pub fn check(&self, debug_hint: bool) {
if self.no_io {
eprintln!("\nError: the net did not return its `IO` handle");
if debug_hint {
eprintln!(" hint: try running the program in `--debug` mode to see error messages");
}
}
if vicious {

if self.vicious {
eprintln!("\nError: the net created a vicious circle");
}

if !self.no_stats {
eprintln!("{}", ivm.stats);
if let Some(stats) = &self.stats {
eprintln!("{}", stats);
}

if no_io || vicious {
if self.no_io || self.vicious {
exit(1);
}
}
Expand Down
4 changes: 2 additions & 2 deletions cli/src/ivy_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl IvyRunCommand {
pub fn execute(self) -> Result<()> {
let src_contents = fs::read_to_string(self.src.clone())?;
let nets = IvyParser::parse(&src_contents).unwrap();
self.run_args.run(nets, false);
self.run_args.run(&nets).check(false);
Ok(())
}
}
Expand Down Expand Up @@ -83,7 +83,7 @@ impl IvyReplCommand {
let heap = Heap::new();
let mut extrinsics = Extrinsics::default();

host.register_default_extrinsics(&mut extrinsics);
host.register_default_extrinsics_with_stdio(&mut extrinsics);
host.insert_nets(&nets);

let mut ivm = IVM::new(&heap, &extrinsics);
Expand Down
80 changes: 54 additions & 26 deletions cli/src/vine_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,15 @@ impl VineRunCommand {
let debug = self.compile.debug;
let (mut nets, _) = self.compile.compile();
self.optimizations.apply(&mut nets);
self.run_args.run(nets, !debug);
self.run_args.run(&nets).check(!debug);
Ok(())
}
}

/// Runs entrypoints marked with the `#[test]` attribute.
///
/// The `debug` and `test` configuration options are always true when running
/// tests.
#[derive(Debug, Args)]
pub struct VineTestCommand {
#[command(flatten)]
Expand All @@ -147,41 +151,65 @@ pub struct VineTestCommand {
#[command(flatten)]
run_args: RunArgs,

/// The test path to run. If not provided, all test paths in MAIN are printed.
test_path: Option<String>,
/// If specified, only run tests containing this string in their paths.
/// If not provided, all tests are run.
test_filter: Option<String>,
}

impl VineTestCommand {
pub fn execute(mut self) -> Result<()> {
let debug = self.compile.debug;
self.compile.debug = true;
self.compile.test = true;

let (mut nets, mut compiler) = self.compile.compile();

let Some(test_path) = self.test_path else {
for concrete_fn_id in compiler.chart.tests {
let def_id = compiler.chart.concrete_fns[concrete_fn_id].def;
let path = &compiler.chart.defs[def_id].path;

println!("{path}");
// TODO: need to keep test entrypoints in order to run optimizer
// self.optimizations.apply(&mut nets);

let test_fn_ids: Vec<_> = compiler
.chart
.tests
.iter()
.filter(|concrete_fn_id| {
self.test_filter.as_ref().is_none_or(|test_filter| {
let def_id = compiler.chart.concrete_fns[**concrete_fn_id].def;
let test_path = &compiler.chart.defs[def_id].path;
test_path.contains(test_filter)
})
})
.cloned()
.collect();

eprintln!("running {} tests", test_fn_ids.len());
eprintln!();

let mut failed = false;

for test_fn_id in test_fn_ids {
let def_id = compiler.chart.concrete_fns[test_fn_id].def;
let test_path = &compiler.chart.defs[def_id].path;
let main_fragment_id = compiler.resolutions.fns[test_fn_id];

eprint!("test {test_path} ... ");

compiler.insert_main_net(&mut nets, main_fragment_id);

let result = self.run_args.run_capturing_output(&nets);
if result.no_io {
eprintln!("FAILED");
if let Some(output) = &result.output {
eprintln!();
eprintln!("{}", String::from_utf8_lossy(output));
}
} else {
eprintln!("ok");
failed = true;
}
}

return Ok(());
};

let Some(test_concrete_fn_id) = compiler.chart.tests.iter().find(|concrete_fn_id| {
let def_id = compiler.chart.concrete_fns[**concrete_fn_id].def;
compiler.chart.defs[def_id].path == test_path
}) else {
eprintln!("test path {test_path:?} does not exist");
if failed {
exit(1);
};

let main_fragment_id = compiler.resolutions.fns[*test_concrete_fn_id];
compiler.insert_main_net(&mut nets, main_fragment_id);

self.optimizations.apply(&mut nets);
self.run_args.run(nets, !debug);
}

Ok(())
}
Expand Down Expand Up @@ -231,7 +259,7 @@ impl VineReplCommand {
let host = &mut Host::default();
let heap = Heap::new();
let mut extrinsics = Extrinsics::default();
host.register_default_extrinsics(&mut extrinsics);
host.register_default_extrinsics_with_stdio(&mut extrinsics);

let mut ivm = IVM::new(&heap, &extrinsics);
let config = Config::new(!self.no_debug, false);
Expand Down
24 changes: 19 additions & 5 deletions ivy/src/host/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,21 @@ impl<'ivm> Host<'ivm> {
self.reverse_ext_tys.insert(ty, name);
}

pub fn register_default_extrinsics(&mut self, extrinsics: &mut Extrinsics<'ivm>) {
pub fn register_default_extrinsics_with_stdio(&mut self, extrinsics: &mut Extrinsics<'ivm>) {
self.register_default_extrinsics(extrinsics, io::stdin, io::stdout);
}

pub fn register_default_extrinsics<R, W, FI, FO>(
&mut self,
extrinsics: &mut Extrinsics<'ivm>,
io_input_fn: FI,
io_output_fn: FO,
) where
FI: Copy + Fn() -> R + Sync + 'ivm,
FO: Copy + Fn() -> W + Sync + 'ivm,
W: Write,
R: Read,
{
let n32 = extrinsics.register_n32_ext_ty();
let f32 = extrinsics.register_light_ext_ty();
let io = extrinsics.register_light_ext_ty();
Expand Down Expand Up @@ -89,24 +103,24 @@ impl<'ivm> Host<'ivm> {

"io_print_char" => |a, b| {
a.as_ty(&io);
print!("{}", char::try_from(as_n32(b)).unwrap());
write!((io_output_fn)(), "{}", char::try_from(as_n32(b)).unwrap()).unwrap();
ExtVal::new(io, 0)
},
"io_print_byte" => |a, b| {
a.as_ty(&io);
io::stdout().write_all(&[as_n32(b) as u8]).unwrap();
(io_output_fn)().write_all(&[as_n32(b) as u8]).unwrap();
ExtVal::new(io, 0)
},
"io_flush" => |a, _b| {
a.as_ty(&io);
io::stdout().flush().unwrap();
(io_output_fn)().flush().unwrap();
ExtVal::new(io, 0)
},
"io_read_byte" => |a, b| {
a.as_ty(&io);
let default = as_n32(b) as u8;
let mut buf = [default];
_ = io::stdin().read(&mut buf).unwrap();
_ = (io_input_fn)().read(&mut buf).unwrap();
new_n32(buf[0] as u32)
}
);
Expand Down
2 changes: 1 addition & 1 deletion ivy/src/optimize/pre_reduce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn pre_reduce(nets: &mut Nets) {
let heap = &Heap::new();
let mut host = &mut Host::default();
let mut extrinsics = Extrinsics::default();
host.register_default_extrinsics(&mut extrinsics);
host.register_default_extrinsics_with_stdio(&mut extrinsics);
host._insert_nets(nets, true);

for (name, net) in nets.iter_mut() {
Expand Down
8 changes: 4 additions & 4 deletions root/IO.vi
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub mod IO {
/// Prints the given character to stdout.
pub fn .print_char(&io: &IO, char: Char) {
inline_ivy! (io0 <- io, io1 -> io, char <- char) -> () { _
io0 = @io_print_char(char io1)
io0 = @io_print_char(char io1)
}
}

Expand All @@ -34,14 +34,14 @@ pub mod IO {

pub fn .print_byte(&io: &IO, byte: N32) {
inline_ivy! (io0 <- io, io1 -> io, byte <- byte) -> () { _
io0 = @io_print_byte(byte io1)
io0 = @io_print_byte(byte io1)
}
}

/// Flushes any buffered output to stdout.
pub fn .flush(&io: &IO) {
inline_ivy! (io0 <- io, io1 -> io) -> () { _
io0 = @io_flush(0 io1)
io0 = @io_flush(0 io1)
}
}

Expand Down Expand Up @@ -92,7 +92,7 @@ pub mod IO {
inline_ivy! (io0 <- io, io3 -> io, default <- default) -> Char {
byte
io0 = dup(io1 io2)
io1 = @io_read_byte(default dup(byte @seq$(io2 io3)))
io1 = @io_read_byte(default dup(byte @seq$(io2 io3)))
}
}

Expand Down
1 change: 1 addition & 0 deletions root/debug/debug.vi
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub mod debug {
for frame in state.stack.iter() {
state.io.print(" @ {frame}\n");
}
unsafe::erase(&state.io);
unsafe::eraser
}

Expand Down
Loading