use std::fmt::Write; use std::rc::Rc; use tvix_eval::observer::TracingObserver; use tvix_eval::observer::{DisassemblingObserver, NoOpObserver}; use web_sys::HtmlInputElement; use web_sys::HtmlTextAreaElement; use yew::prelude::*; use yew::TargetCast; enum Msg { CodeChange(String), ToggleTrace(bool), } struct Model { code: String, trace: bool, } fn tvixbolt_overview() -> Html { html! { <>

{"This page lets you explore the bytecode generated by the "} {"Tvix"} {" compiler for the Nix language. See the "} {"Tvix announcement"} {" for some background information on Tvix itself."}

{"Tvix is still "}{"extremely work-in-progress"}{" and you "} {"should expect to be able to cause bugs and errors in this tool."}

} } fn footer_link(location: &'static str, name: &str) -> Html { html! { <> {name}{" | "} } } fn footer() -> Html { html! { <>
} } impl Component for Model { type Message = Msg; type Properties = (); fn create(_ctx: &Context) -> Self { Self { code: String::new(), trace: false, } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { match msg { Msg::ToggleTrace(trace) => { self.trace = trace; true } Msg::CodeChange(new_code) => { self.code = new_code; true } } } fn view(&self, ctx: &Context) -> Html { // This gives us a component's "`Scope`" which allows us to send messages, etc to the component. let link = ctx.link(); html! { <>

{"tvixbolt 0.1-alpha"}

{tvixbolt_overview()}
{"Input"}
().checked(); Msg::ToggleTrace(trace) })} />

{self.run()} {footer()}
} } } impl Model { fn run(&self) -> Html { if self.code.is_empty() { return html! {

{"Enter some Nix code above to get started. Don't know Nix yet? "} {"Check out "} {"nix-1p"} {"!"}

}; } html! { <>

{"Result:"}

{eval(self.trace, &self.code).display()} } } } #[derive(Default)] struct Output { parse_errors: String, warnings: String, compiler_errors: String, runtime_errors: String, output: String, bytecode: Vec, trace: Vec, } fn maybe_show(title: &str, s: &str) -> Html { if s.is_empty() { html! {} } else { html! { <>

{title}

{s}
} } } impl Output { fn display(self) -> Html { html! { <> {maybe_show("Parse errors:", &self.parse_errors)} {maybe_show("Warnings:", &self.warnings)} {maybe_show("Output:", &self.output)} {maybe_show("Compiler errors:", &self.compiler_errors)} {maybe_show("Bytecode:", &String::from_utf8_lossy(&self.bytecode))} {maybe_show("Runtime errors:", &self.runtime_errors)} {maybe_show("Runtime trace:", &String::from_utf8_lossy(&self.trace))} } } } fn eval(trace: bool, code: &str) -> Output { let mut out = Output::default(); if code.is_empty() { return out; } let mut codemap = codemap::CodeMap::new(); let file = codemap.add_file("nixbolt".to_string(), code.into()); let parsed = rnix::ast::Root::parse(code); let errors = parsed.errors(); if !errors.is_empty() { for err in errors { writeln!(&mut out.parse_errors, "parse error: {}", err).unwrap(); } return out; } // If we've reached this point, there are no errors. let root_expr = parsed .tree() .expr() .expect("expression should exist if no errors occured"); let codemap = Rc::new(codemap); let mut compilation_observer = DisassemblingObserver::new(codemap.clone(), &mut out.bytecode); let result = tvix_eval::compile( root_expr, Some("/nixbolt".into()), &file, tvix_eval::global_builtins(), &mut compilation_observer, ) .unwrap(); for warning in result.warnings { writeln!( &mut out.warnings, "{}\n", warning.fancy_format_str(&codemap).trim(), ) .unwrap(); } if !result.errors.is_empty() { for error in &result.errors { writeln!( &mut out.compiler_errors, "error: {:?} at `{}` [line {}]", error.kind, file.source_slice(error.span), file.find_line(error.span.low()) + 1 ) .unwrap(); } return out; } let result = if trace { tvix_eval::run_lambda(&mut TracingObserver::new(&mut out.trace), result.lambda) } else { tvix_eval::run_lambda(&mut NoOpObserver::default(), result.lambda) }; match result { Ok(value) => writeln!(&mut out.output, "{}", value).unwrap(), Err(err) => writeln!(&mut out.runtime_errors, "runtime error: {:?}", err).unwrap(), }; out } fn main() { yew::start_app::(); }