about summary refs log tree commit diff
path: root/corp/tvixbolt/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'corp/tvixbolt/src/main.rs')
-rw-r--r--corp/tvixbolt/src/main.rs187
1 files changed, 187 insertions, 0 deletions
diff --git a/corp/tvixbolt/src/main.rs b/corp/tvixbolt/src/main.rs
new file mode 100644
index 000000000000..9ed79aa0da82
--- /dev/null
+++ b/corp/tvixbolt/src/main.rs
@@ -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>();
+}