feat(corp/tvixbolt): check in initial tvixbolt version
This is the code backing the small site currently deployed at https://tazj.in/blobs/nixbolt/index.html This relies on a newer version of Tvix than is available in depot and a bunch of other stuff that isn't public yet, so for now no build file is provided as this is heavily work-in-progress. Change-Id: I7a8e4dbf4e11d1c70175f929e65f40ff69acbbd9 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6315 Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
f88d248f48
commit
8655440ae3
6 changed files with 862 additions and 0 deletions
187
corp/tvixbolt/src/main.rs
Normal file
187
corp/tvixbolt/src/main.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use std::{fmt::Write, rc::Rc};
|
||||
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 == "" {
|
||||
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 mut result = tvix_eval::compiler::compile(
|
||||
root_expr,
|
||||
Some("/nixbolt".into()),
|
||||
&file,
|
||||
tvix_eval::builtins::global_builtins(),
|
||||
Rc::new(codemap),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let lambda = Rc::new(result.lambda);
|
||||
|
||||
tvix_eval::disassembler::disassemble_lambda(&mut out.bytecode, lambda.clone());
|
||||
|
||||
out.bytecode.append(&mut result.output);
|
||||
|
||||
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::vm::run_lambda(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>();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue