Skip to content
Open
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ clap_complete = "4.5.52"
glob = "0.3.3"
notify = "8.2.0"
rustyline = "14.0.0"
url = "2.5.3"

ivm = { path = "../ivm" }
ivy = { path = "../ivy" }
Expand Down
175 changes: 39 additions & 136 deletions cli/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
use std::{
collections::HashMap,
io::{self, Read, Write},
process::exit,
sync::{Mutex, MutexGuard},
};
use std::{collections::HashMap, io, process::exit};

use clap::Args;
use ivm::{
host::{Host, IVM, ext::common::IO},
program::Program,
runtime::{
flags::Flags,
heap::Heap,
port::{Port, Tag},
stats::Stats,
host::{
Host, IVM,
ext::common,
runner::{CaptureOutput, Runner},
},
runtime::{flags::Flags, heap::Heap, stats::Stats},
};
use ivy::{
name::{NameId, Table},
Expand All @@ -37,149 +30,59 @@ pub struct RunArgs {
}

impl RunArgs {
/// Run the nets forwarding stdout and stderr, exiting if any errors occur.
/// Run the nets forwarding stdin and stdout, exiting if any errors occur.
pub fn run(&self, table: &mut Table, nets: &HashMap<NameId, FlatNet>, debug_hint: bool) {
self.run_with(table, nets, io::stdin, io::stdout).report(debug_hint);
}
let mut heap = self.heap();
let mut ivm = IVM::new();
let mut host = Host::new(&mut ivm);
let extrinsics = common::all(&self.args, io::stdin, io::stdout);
let runner = Runner::new(&mut heap, &mut host, extrinsics, table, nets);

/// Run the nets capturing any stdout and stderr.
pub fn run_capture(
&self,
table: &mut Table,
nets: &HashMap<NameId, FlatNet>,
) -> (RunResult, Vec<u8>) {
pub struct SharedWriter<'a>(pub MutexGuard<'a, Vec<u8>>);
let (mut stats, flags) = runner.normalize(self.breadth_first, self.workers, ());

impl Write for SharedWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.extend_from_slice(buf);
Ok(buf.len())
}
if !flags.success() {
eprintln!("\n{}", flags.error_message(debug_hint));
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
if !self.no_stats {
if self.no_perf {
stats.clear_perf();
}
eprintln!("{stats}");
}

let input: &[u8] = &[];
let output = Mutex::default();
let result = self.run_with(table, nets, || input, || SharedWriter(output.lock().unwrap()));

(result, output.into_inner().unwrap())
if !flags.success() {
exit(1);
}
}

pub fn run_with<R: Read, W: Write>(
/// Run the nets with empty stdin and capturing any stdout.
pub fn run_capture(
&self,
table: &mut Table,
nets: &HashMap<NameId, FlatNet>,
stdin: impl Copy + Fn() -> R + Send + Sync,
stdout: impl Copy + Fn() -> W + Send + Sync,
) -> RunResult {
let mut heap = match self.heap {
Some(size) => Heap::with_size(size).expect("heap allocation failed"),
None => Heap::new(),
};

) -> (Stats, Flags, Vec<u8>) {
let capture = CaptureOutput::default();
let mut heap = self.heap();
let mut ivm = IVM::new();
let mut host = Host::new(&mut ivm);

let io = host.register_ext_ty::<IO>();

use ivm::host::ext::common;
host.register(
table,
(
common::fundamental(),
common::arithmetic(),
common::io_meta(),
common::io_stdio_with(stdin, stdout),
common::io_args(&self.args),
common::io_error(),
common::io_file(),
),
);

let program = Program::new(&host, table, nets);
let main = table.add_path_name("iv:main");

let mut runtime = host.init(&mut heap);

let main = program.graft(main).expect("missing main");
let node = unsafe { runtime.new_node(Tag::Comb, 0) };
runtime.link_wire(node.1, Port::new_ext_val(io.wrap_static(IO)));
runtime.link(Port::new_graft(main), node.0);
let (stats, flags) = {
let mut host = Host::new(&mut ivm);
let extrinsics = capture.extrinsics(&self.args);
let runner = Runner::new(&mut heap, &mut host, extrinsics, table, nets);

if self.breadth_first {
runtime.normalize_breadth_first();
} else if self.workers > 0 {
runtime.normalize_parallel(self.workers)
} else {
runtime.normalize();
}

let out = runtime.follow(Port::new_wire(node.2));
if self.no_perf {
runtime.stats.clear_perf();
}
runner.normalize(self.breadth_first, self.workers, ())
};

RunResult {
stats: (!self.no_stats).then_some(runtime.stats),
flags: runtime.flags,
no_io: out.tag() != Tag::ExtVal || unsafe { out.as_ext_val() }.ty_id() != io.id(),
vicious: runtime.stats.mem_free < runtime.stats.mem_alloc,
}
(stats, flags, capture.into_output())
}
}

pub struct RunResult {
pub stats: Option<Stats>,
pub flags: Flags,
pub no_io: bool,
pub vicious: bool,
}

impl RunResult {
pub fn report(&self, debug_hint: bool) {
let RunResult {
stats,
no_io,
vicious,
flags: Flags { ext_copy, ext_erase, ext_generic, invalid_interaction },
} = *self;
if 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 {
eprintln!("\nError: the net created a vicious circle");
}
if ext_copy {
eprintln!("\nError: a linear extrinsic was copied");
}
if ext_erase {
eprintln!("\nError: a linear extrinsic was erased");
}
if ext_generic {
eprintln!("\nError: an extrinsic function encountered an unspecified error");
}
if invalid_interaction {
eprintln!("\nError: an invalid interaction was performed");
}

if let Some(stats) = &stats {
eprintln!("{}", stats);
}

if !self.success() {
exit(1);
fn heap(&self) -> Box<Heap> {
match self.heap {
Some(size) => Heap::with_size(size).expect("heap allocation failed"),
None => Heap::new(),
}
}

pub fn success(&self) -> bool {
!self.no_io && !self.vicious && self.flags.success()
}
}

fn parse_size(size: &str) -> anyhow::Result<usize> {
Expand Down
94 changes: 58 additions & 36 deletions cli/src/vine_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ use ivy::{
};
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use rustyline::DefaultEditor;
use url::Url;
use vine::{
backend::{BackendConfig, Target, backend, vi_to_ivm},
compiler::Compiler,
components::loader::{FileId, Loader, RealFS},
structures::{
ast::Ident,
content::{Color, Element, Length, Output, Surround, Writer},
diag::Color as DiagColor,
resolutions::FragmentId,
},
tools::{
Expand All @@ -45,7 +47,7 @@ use vine::{
repl::{self, Repl},
},
};
use vine_lsp::lsp;
use vine_lsp::{Doc, lsp_stdio};
use vine_util::idx::IdxVec;

use crate::common::RunArgs;
Expand Down Expand Up @@ -264,14 +266,14 @@ impl VineTestCommand {
compiler.insert_main_net(table, &mut nets, test_id, &translator);

eprint!("{grey}test{reset} {bold}{path}{reset} {grey}...{reset} ");
let (result, output) = self.run_args.run_capture(table, &nets);
if result.success() {
let (_, flags, output) = self.run_args.run_capture(table, &nets);
if flags.success() {
eprintln!("{green}ok{reset}");
} else {
failed = true;
eprintln!("{red}FAILED{reset}");
}
if self.no_capture || !result.success() {
if self.no_capture || !flags.success() {
io::stderr().write_all(&output)?;
eprintln!();
}
Expand Down Expand Up @@ -680,11 +682,41 @@ impl VineLspCommand {
pub fn execute(self) -> Result<()> {
let mut compiler = Compiler::default();
let file_paths = self.check.libs.initialize(&mut compiler);
lsp(compiler, file_paths, self.check.entrypoints);
let hooks = VineCliLspHooks::new(self.check.entrypoints);
lsp_stdio(compiler, file_paths, hooks);
Ok(())
}
}

struct VineCliLspHooks {
entrypoints: Vec<String>,
}

impl VineCliLspHooks {
fn new(entrypoints: Vec<String>) -> Self {
Self { entrypoints }
}
}

impl vine_lsp::Hooks for VineCliLspHooks {
fn load_modules(
&self,
compiler: &mut Compiler,
file_paths: &mut IdxVec<FileId, PathBuf>,
_docs: &HashMap<Url, Doc>,
) {
let mut loader = Loader::new(compiler, RealFS, Some(file_paths));
for glob in &self.entrypoints {
for entry in glob::glob(glob).unwrap() {
let path = entry.unwrap();
if let Some(name) = RealFS::detect_name(&path) {
loader.load_mod(name, path);
}
}
}
}
}

#[derive(Debug, Args)]
pub struct VineCompletionCommand {
pub shell: clap_complete::Shell,
Expand Down Expand Up @@ -733,39 +765,29 @@ fn print_diags(compiler: &Compiler) {
}

fn _print_diags(mut w: impl io::Write + IsTerminal, compiler: &Compiler) -> io::Result<()> {
let Colors { reset, bold, underline, grey, red, yellow, .. } = colors(&w);

let errors = compiler.diags.errors.iter().map(|x| (red, "error", x));
let warnings = compiler.diags.warnings.iter().map(|x| (yellow, "warning", x));

for (color, severity, diag) in errors.chain(warnings) {
let Some(span) = diag.span() else { continue };
writeln!(w, "{color}{severity}{grey}:{reset} {bold}{diag}{reset}")?;
if let Some(span_str) = compiler.show_span(span) {
let file = &compiler.files[span.file];
let start = file.get_pos(span.start);
let end = file.get_pos(span.end);
let line_width = (end.line + 1).ilog10() as usize + 1;
writeln!(w, " {:>line_width$}{grey} @ {span_str}", "")?;
for line in start.line..=end.line {
let line_start = file.line_starts[line];
let line_end = file.line_starts.get(line + 1).copied().unwrap_or(file.src.len());
let line_str = &file.src[line_start..line_end];
let line_str =
line_str.strip_suffix("\r\n").or(line_str.strip_suffix("\n")).unwrap_or(line_str);
let start = if line == start.line { start.col } else { 0 };
let end = if line == end.line { end.col } else { line_str.len() };
writeln!(
w,
" {grey}{:>line_width$} |{reset} {}{color}{underline}{}{reset}{}",
line + 1,
&line_str[..start],
&line_str[start..end],
&line_str[end..],
)?;
let Colors { reset, bold, underline, grey, red, yellow, green } = colors(&w);

for diag_lines in compiler.format_diags() {
for diag_span in diag_lines {
if diag_span.bold {
write!(w, "{bold}")?;
}
match diag_span.color {
Some(DiagColor::Grey) => write!(w, "{grey}")?,
Some(DiagColor::Red) => write!(w, "{red}")?,
Some(DiagColor::Yellow) => write!(w, "{yellow}")?,
Some(DiagColor::Green) => write!(w, "{green}")?,
_ => (),
}
if diag_span.underline {
write!(w, "{underline}")?;
}
write!(w, "{}", diag_span.content)?;
if diag_span.is_styled() {
write!(w, "{reset}")?;
}
}
writeln!(w,)?;
writeln!(w)?;
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions ivm/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
};

pub mod ext;
pub mod runner;

pub struct IVM<'ivm> {
heaps: Arena<Heap>,
Expand Down
Loading
Loading