tvl-depot/corp/tvixbolt/src/main.rs
Florian Klink c13759e89f feat(corp/tvixbolt): link to tvl.su too
Make it clear that Tvixbolt is a project of TVL LLC, and link to the
community website too.

See https://b.tvl.fyi/issues/248

Change-Id: Iefefe0263fa5ef01587d49c5a130a38b78ca7981
Reviewed-on: https://cl.tvl.fyi/c/depot/+/8019
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
Autosubmit: flokli <flokli@flokli.de>
2023-02-03 19:04:21 +00:00

342 lines
8.9 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 serde::{Deserialize, Serialize};
use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
use web_sys::HtmlDetailsElement;
use web_sys::HtmlTextAreaElement;
use yew::prelude::*;
use yew::TargetCast;
use yew_router::{prelude::*, AnyRoute};
#[derive(Clone)]
enum Msg {
CodeChange(String),
ToggleTrace(bool),
ToggleDisplayAst(bool),
// Required because browsers are stupid and it's easy to get into
// infinite loops with `ontoggle` events.
NoOp,
}
#[derive(Clone, Serialize, Deserialize)]
struct Model {
code: String,
// #[serde(skip_serializing)]
trace: bool,
// #[serde(skip_serializing)]
display_ast: bool,
}
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>
<p>
{"Tvixbolt is a project from "}
<a href="https://tvl.su">
{"TVL LLC"}
</a>
{". If you're looking for the TVL Community, click "}
<a href="https://tvl.fyi">
{"here"}
</a>
{"."}
</p>
</>
}
}
/// This renders an ad in the Tvixbolt footer. Most people that end up
/// on Tvixbolt will probably block this anyways, but might as well.
fn ad() -> Html {
let ad_code = r#"
window.yaContextCb.push(()=>{
Ya.Context.AdvManager.render({
renderTo: 'yandex_rtb_R-A-1943274-1',
blockId: 'R-A-1943274-1'
})
})
"#;
html! {
<div id="ad">
<div id="yandex_rtb_R-A-1943274-1"></div>
<script>{ad_code}</script>
</div>
}
}
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>
{ad()}
</footer>
</>
}
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: &Context<Self>) -> Self {
BrowserHistory::new()
.location()
.query::<Self>()
.unwrap_or_else(|_| Self {
code: String::new(),
trace: false,
display_ast: false,
})
}
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ToggleTrace(trace) => {
self.trace = trace;
}
Msg::ToggleDisplayAst(display_ast) => {
self.display_ast = display_ast;
}
Msg::CodeChange(new_code) => {
self.code = new_code;
}
Msg::NoOp => {}
}
let _ = BrowserHistory::new().replace_with_query(AnyRoute::new("/"), self.clone());
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" value={self.code.clone()}>
</textarea>
</div>
</fieldset>
</form>
<hr />
{self.run(ctx)}
{footer()}
</div>
</>
}
}
}
impl Model {
fn run(&self, ctx: &Context<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).display(ctx, self)}
</>
}
}
}
#[derive(Default)]
struct Output {
errors: String,
warnings: String,
output: String,
bytecode: Vec<u8>,
trace: Vec<u8>,
ast: String,
}
fn maybe_show(title: &str, s: &str) -> Html {
if s.is_empty() {
html! {}
} else {
html! {
<>
<h3>{title}</h3>
<pre>{s}</pre>
</>
}
}
}
fn maybe_details(
ctx: &Context<Model>,
title: &str,
s: &str,
display: bool,
toggle: fn(bool) -> Msg,
) -> Html {
let link = ctx.link();
if display {
let msg = toggle(false);
html! {
<details open=true
ontoggle={link.callback(move |e: Event| {
let details = e.target_unchecked_into::<HtmlDetailsElement>();
if !details.open() {
msg.clone()
} else {
Msg::NoOp
}
})}>
<summary><h3 style="display: inline;">{title}</h3></summary>
<pre>{s}</pre>
</details>
}
} else {
let msg = toggle(true);
html! {
<details ontoggle={link.callback(move |e: Event| {
let details = e.target_unchecked_into::<HtmlDetailsElement>();
if details.open() {
msg.clone()
} else {
Msg::NoOp
}
})}>
<summary><h3 style="display: inline;">{title}</h3></summary>
</details>
}
}
}
impl Output {
fn display(self, ctx: &Context<Model>, model: &Model) -> Html {
html! {
<>
{maybe_show("Errors:", &self.errors)}
{maybe_show("Warnings:", &self.warnings)}
{maybe_show("Output:", &self.output)}
{maybe_show("Bytecode:", &String::from_utf8_lossy(&self.bytecode))}
{maybe_details(ctx, "Runtime trace:", &String::from_utf8_lossy(&self.trace), model.trace, Msg::ToggleTrace)}
{maybe_details(ctx, "Parsed AST:", &self.ast, model.display_ast, Msg::ToggleDisplayAst)}
</>
}
}
}
fn eval(model: &Model) -> Output {
let mut out = Output::default();
if model.code.is_empty() {
return out;
}
let mut eval = tvix_eval::Evaluation::new(&model.code, Some("/nixbolt".into()));
let source = eval.source_map();
let result = {
let mut compiler_observer = DisassemblingObserver::new(source.clone(), &mut out.bytecode);
eval.compiler_observer = Some(&mut compiler_observer);
let mut runtime_observer = TracingObserver::new(&mut out.trace);
if model.trace {
eval.runtime_observer = Some(&mut runtime_observer);
}
eval.evaluate()
};
if model.display_ast {
if let Some(ref expr) = result.expr {
out.ast = tvix_eval::pretty_print_expr(expr);
}
}
out.output = match result.value {
Some(val) => val.to_string(),
None => "".to_string(),
};
for warning in result.warnings {
writeln!(
&mut out.warnings,
"{}\n",
warning.fancy_format_str(&source).trim(),
)
.unwrap();
}
if !result.errors.is_empty() {
for error in &result.errors {
writeln!(
&mut out.errors,
"{}\n",
error.fancy_format_str(&source).trim(),
)
.unwrap();
}
return out;
}
out
}
fn main() {
yew::start_app::<Model>();
}