snix/corp/tvixbolt/src/main.rs
Vincent Ambo 85e3281f75 feat(corp/tvixbolt): add some additional information on the page
A little bit easier to grasp what's going on then just a blank page
with a textbox ...

Change-Id: I16f456035173813d60d88ff7e5ebd14712f77ec3
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6330
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
2022-09-04 18:50:06 +00:00

256 lines
6.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
fn tvixbolt_overview() -> Html {
html! {
<>
<p>
{"This page lets you explore the bytecode generated by the "}
<a href="https://cs.tvl.fyi/depot/-/tree/tvix">{"Tvix"}</a>
{" compiler for the Nix language. See the "}
<a href="https://tvl.fyi/blog/rewriting-nix">{"Tvix announcement"}</a>
{" for some background information on Tvix itself."}
</p>
<p>
{"Tvix is still "}<i>{"extremely work-in-progress"}</i>{" and you "}
{"should expect to be able to cause bugs and errors in this tool."}
</p>
</>
}
}
fn footer_link(location: &'static str, name: &str) -> Html {
html! {
<>
<a class="uncoloured-link" href={location}>{name}</a>{" | "}
</>
}
}
fn footer() -> Html {
html! {
<>
<hr/>
<footer>
<p class="footer">
{footer_link("https://tvl.su", "home")}
{footer_link("https://cs.tvl.fyi", "code")}
{footer_link("https://tvl.fyi/builds", "ci")}
{footer_link("https://b.tvl.fyi", "bugs")}
{"© ООО ТВЛ"}
</p>
<p class="lod">{"ಠ_ಠ"}</p>
</footer>
</>
}
}
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 0.1-alpha"}</h1>
{tvixbolt_overview()}
<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 />
{self.run()}
{footer()}
</div>
</>
}
}
}
impl Model {
fn run(&self) -> Html {
if self.code.is_empty() {
return html! {
<p>
{"Enter some Nix code above to get started. Don't know Nix yet? "}
{"Check out "}
<a href="https://code.tvl.fyi/about/nix/nix-1p/README.md">{"nix-1p"}</a>
{"!"}
</p>
};
}
html! {
<>
<h2>{"Result:"}</h2>
{eval(&self.code).display()}
</>
}
}
}
#[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>();
}