2022-09-04 16:57:14 +02:00
|
|
|
|
use std::fmt::Write;
|
|
|
|
|
|
2022-09-18 18:23:22 +02:00
|
|
|
|
use serde::{Deserialize, Serialize};
|
2022-12-09 11:18:40 +01:00
|
|
|
|
use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
|
2022-10-13 18:20:39 +02:00
|
|
|
|
use web_sys::HtmlDetailsElement;
|
2022-09-04 14:16:27 +02:00
|
|
|
|
use web_sys::HtmlTextAreaElement;
|
|
|
|
|
use yew::prelude::*;
|
|
|
|
|
use yew::TargetCast;
|
2022-09-18 18:23:22 +02:00
|
|
|
|
use yew_router::{prelude::*, AnyRoute};
|
2022-09-04 14:16:27 +02:00
|
|
|
|
|
2022-10-13 18:20:39 +02:00
|
|
|
|
#[derive(Clone)]
|
2022-09-04 14:16:27 +02:00
|
|
|
|
enum Msg {
|
|
|
|
|
CodeChange(String),
|
2022-09-04 20:39:21 +02:00
|
|
|
|
ToggleTrace(bool),
|
2022-10-13 17:22:36 +02:00
|
|
|
|
ToggleDisplayAst(bool),
|
2022-10-13 18:20:39 +02:00
|
|
|
|
|
|
|
|
|
// Required because browsers are stupid and it's easy to get into
|
|
|
|
|
// infinite loops with `ontoggle` events.
|
|
|
|
|
NoOp,
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-18 18:23:22 +02:00
|
|
|
|
#[derive(Clone, Serialize, Deserialize)]
|
2022-09-04 14:16:27 +02:00
|
|
|
|
struct Model {
|
|
|
|
|
code: String,
|
2022-10-13 18:20:39 +02:00
|
|
|
|
|
|
|
|
|
// #[serde(skip_serializing)]
|
2022-09-04 20:39:21 +02:00
|
|
|
|
trace: bool,
|
2022-10-13 18:20:39 +02:00
|
|
|
|
|
|
|
|
|
// #[serde(skip_serializing)]
|
2022-10-13 17:22:36 +02:00
|
|
|
|
display_ast: bool,
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-04 19:59:19 +02:00
|
|
|
|
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>
|
2023-02-02 18:35:36 +01:00
|
|
|
|
<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>
|
2022-09-04 19:59:19 +02:00
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-13 13:43:28 +02:00
|
|
|
|
/// 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>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-04 19:59:19 +02:00
|
|
|
|
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>
|
2022-09-13 13:43:28 +02:00
|
|
|
|
{ad()}
|
2022-09-04 19:59:19 +02:00
|
|
|
|
</footer>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-04 14:16:27 +02:00
|
|
|
|
impl Component for Model {
|
|
|
|
|
type Message = Msg;
|
|
|
|
|
type Properties = ();
|
|
|
|
|
|
2022-09-18 18:23:22 +02:00
|
|
|
|
fn create(_: &Context<Self>) -> Self {
|
|
|
|
|
BrowserHistory::new()
|
|
|
|
|
.location()
|
|
|
|
|
.query::<Self>()
|
|
|
|
|
.unwrap_or_else(|_| Self {
|
|
|
|
|
code: String::new(),
|
|
|
|
|
trace: false,
|
2022-10-13 17:22:36 +02:00
|
|
|
|
display_ast: false,
|
2022-09-18 18:23:22 +02:00
|
|
|
|
})
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-09-18 18:23:22 +02:00
|
|
|
|
fn update(&mut self, _: &Context<Self>, msg: Self::Message) -> bool {
|
2022-09-04 14:16:27 +02:00
|
|
|
|
match msg {
|
2022-09-04 20:39:21 +02:00
|
|
|
|
Msg::ToggleTrace(trace) => {
|
|
|
|
|
self.trace = trace;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-13 17:22:36 +02:00
|
|
|
|
Msg::ToggleDisplayAst(display_ast) => {
|
|
|
|
|
self.display_ast = display_ast;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-04 14:16:27 +02:00
|
|
|
|
Msg::CodeChange(new_code) => {
|
|
|
|
|
self.code = new_code;
|
|
|
|
|
}
|
2022-10-13 18:20:39 +02:00
|
|
|
|
|
|
|
|
|
Msg::NoOp => {}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
2022-09-18 18:23:22 +02:00
|
|
|
|
|
|
|
|
|
let _ = BrowserHistory::new().replace_with_query(AnyRoute::new("/"), self.clone());
|
|
|
|
|
|
|
|
|
|
true
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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! {
|
2022-09-04 19:59:19 +02:00
|
|
|
|
<>
|
2022-09-04 14:16:27 +02:00
|
|
|
|
<div class="container">
|
2022-09-04 19:59:19 +02:00
|
|
|
|
<h1>{"tvixbolt 0.1-alpha"}</h1>
|
|
|
|
|
{tvixbolt_overview()}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
<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)
|
|
|
|
|
|
|
|
|
|
})}
|
2022-09-18 18:23:22 +02:00
|
|
|
|
id="code" cols="30" rows="10" value={self.code.clone()}>
|
|
|
|
|
</textarea>
|
2022-09-04 14:16:27 +02:00
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
</form>
|
|
|
|
|
<hr />
|
2022-10-13 18:20:39 +02:00
|
|
|
|
{self.run(ctx)}
|
2022-09-04 19:59:19 +02:00
|
|
|
|
{footer()}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
</div>
|
2022-09-04 19:59:19 +02:00
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Model {
|
2022-10-13 18:20:39 +02:00
|
|
|
|
fn run(&self, ctx: &Context<Self>) -> Html {
|
2022-09-04 19:59:19 +02:00
|
|
|
|
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>
|
2022-10-13 18:20:39 +02:00
|
|
|
|
{eval(self).display(ctx, self)}
|
2022-09-04 19:59:19 +02:00
|
|
|
|
</>
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
struct Output {
|
2022-12-09 11:18:40 +01:00
|
|
|
|
errors: String,
|
2022-09-04 14:16:27 +02:00
|
|
|
|
warnings: String,
|
|
|
|
|
output: String,
|
|
|
|
|
bytecode: Vec<u8>,
|
2022-09-04 20:39:21 +02:00
|
|
|
|
trace: Vec<u8>,
|
2022-10-13 17:22:36 +02:00
|
|
|
|
ast: String,
|
2022-09-04 14:16:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn maybe_show(title: &str, s: &str) -> Html {
|
|
|
|
|
if s.is_empty() {
|
|
|
|
|
html! {}
|
|
|
|
|
} else {
|
|
|
|
|
html! {
|
|
|
|
|
<>
|
|
|
|
|
<h3>{title}</h3>
|
|
|
|
|
<pre>{s}</pre>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-13 18:20:39 +02:00
|
|
|
|
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>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-04 14:16:27 +02:00
|
|
|
|
impl Output {
|
2022-10-13 18:20:39 +02:00
|
|
|
|
fn display(self, ctx: &Context<Model>, model: &Model) -> Html {
|
2022-09-04 14:16:27 +02:00
|
|
|
|
html! {
|
|
|
|
|
<>
|
2022-12-09 11:18:40 +01:00
|
|
|
|
{maybe_show("Errors:", &self.errors)}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
{maybe_show("Warnings:", &self.warnings)}
|
2022-09-04 23:02:21 +02:00
|
|
|
|
{maybe_show("Output:", &self.output)}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
{maybe_show("Bytecode:", &String::from_utf8_lossy(&self.bytecode))}
|
2022-10-13 18:20:39 +02:00
|
|
|
|
{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)}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-13 18:20:39 +02:00
|
|
|
|
fn eval(model: &Model) -> Output {
|
2022-09-04 14:16:27 +02:00
|
|
|
|
let mut out = Output::default();
|
|
|
|
|
|
2022-10-13 18:20:39 +02:00
|
|
|
|
if model.code.is_empty() {
|
2022-09-04 14:16:27 +02:00
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 11:18:40 +01:00
|
|
|
|
let mut eval = tvix_eval::Evaluation::new(&model.code, Some("/nixbolt".into()));
|
|
|
|
|
let source = eval.source_map();
|
2022-09-04 14:16:27 +02:00
|
|
|
|
|
2022-12-09 11:18:40 +01:00
|
|
|
|
let result = {
|
|
|
|
|
let mut compiler_observer = DisassemblingObserver::new(source.clone(), &mut out.bytecode);
|
|
|
|
|
eval.compiler_observer = Some(&mut compiler_observer);
|
2022-09-04 14:16:27 +02:00
|
|
|
|
|
2022-12-09 11:18:40 +01:00
|
|
|
|
let mut runtime_observer = TracingObserver::new(&mut out.trace);
|
|
|
|
|
if model.trace {
|
|
|
|
|
eval.runtime_observer = Some(&mut runtime_observer);
|
|
|
|
|
}
|
2022-09-04 14:16:27 +02:00
|
|
|
|
|
2022-12-09 11:18:40 +01:00
|
|
|
|
eval.evaluate()
|
|
|
|
|
};
|
2022-09-04 14:16:27 +02:00
|
|
|
|
|
2022-10-13 18:20:39 +02:00
|
|
|
|
if model.display_ast {
|
2022-12-09 11:18:40 +01:00
|
|
|
|
if let Some(ref expr) = result.expr {
|
|
|
|
|
out.ast = tvix_eval::pretty_print_expr(expr);
|
|
|
|
|
}
|
2022-10-13 17:22:36 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-22 10:25:29 +01:00
|
|
|
|
out.output = match result.value {
|
|
|
|
|
Some(val) => val.to_string(),
|
|
|
|
|
None => "".to_string(),
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-04 14:16:27 +02:00
|
|
|
|
for warning in result.warnings {
|
|
|
|
|
writeln!(
|
|
|
|
|
&mut out.warnings,
|
2022-09-12 14:24:51 +02:00
|
|
|
|
"{}\n",
|
2022-10-04 16:05:34 +02:00
|
|
|
|
warning.fancy_format_str(&source).trim(),
|
2022-09-04 14:16:27 +02:00
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !result.errors.is_empty() {
|
|
|
|
|
for error in &result.errors {
|
|
|
|
|
writeln!(
|
2022-12-09 11:18:40 +01:00
|
|
|
|
&mut out.errors,
|
2022-09-12 15:18:26 +02:00
|
|
|
|
"{}\n",
|
2022-10-04 16:05:34 +02:00
|
|
|
|
error.fancy_format_str(&source).trim(),
|
2022-09-04 14:16:27 +02:00
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
yew::start_app::<Model>();
|
|
|
|
|
}
|