use std::fmt::Write; use std::rc::Rc; use tvix_eval::observer::DisassemblingObserver; use web_sys::HtmlTextAreaElement; use yew::prelude::*; use yew::TargetCast; enum Msg { CodeChange(String), } struct Model { code: String, } impl Component for Model { type Message = Msg; type Properties = (); fn create(_ctx: &Context<Self>) -> Self { Self { code: String::new(), } } fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { match msg { Msg::CodeChange(new_code) => { self.code = new_code; true } } } fn view(&self, ctx: &Context<Self>) -> Html { // This gives us a component's "`Scope`" which allows us to send messages, etc to the component. let link = ctx.link(); html! { <div class="container"> <h1>{"tvixbolt"}</h1> <form> <fieldset> <legend>{"Input"}</legend> <div class="form-group"> <label for="code">{"Nix code:"}</label> <textarea oninput={link.callback(|e: InputEvent| { let ta = e.target_unchecked_into::<HtmlTextAreaElement>().value(); Msg::CodeChange(ta) })} id="code" cols="30" rows="10"> </textarea> </div> <div class="form-group"> <label for="disable-bytecode">{"Disassemble:"}</label> <input for="disable-bytecode" type="checkbox" checked=true disabled=true /> </div> </fieldset> </form> <hr /> <h2>{"Result:"}</h2> {eval(&self.code).display()} </div> } } } #[derive(Default)] struct Output { parse_errors: String, warnings: String, compiler_errors: String, runtime_errors: String, output: String, bytecode: Vec<u8>, } fn maybe_show(title: &str, s: &str) -> Html { if s.is_empty() { html! {} } else { html! { <> <h3>{title}</h3> <pre>{s}</pre> </> } } } impl Output { fn display(self) -> Html { html! { <> {maybe_show("Parse errors:", &self.parse_errors)} {maybe_show("Warnings:", &self.warnings)} {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("Output:", &self.output)} </> } } } fn eval(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, &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, "warning: {:?} at `{}` [line {}]", warning.kind, file.source_slice(warning.span), file.find_line(warning.span.low()) + 1 ) .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 = tvix_eval::run_lambda(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::<Model>(); }