on master help sign in
wock/wock-web/src/lib.rs
215 lines 7.0 kB history raw
1#![feature(iter_collect_into)]
2mod utils;
4use core::panic;
5use std::{borrow::BorrowMut, cell::RefCell, rc::Rc};
7use dodrio::{
8 bumpalo::{self, Bump},
9 Attribute, Listener, Node, NodeKey, Render, RenderContext, RootRender, VdomWeak,
10};
11use gloo::net::{self, http::Request};
12use nock::{
13 cue::cue_bytes,
14 interpreter::{generate_interpreter_context, ram_ttanks, slam, InterpreterContext},
15 noun::{cell, Noun},
16};
17use wasm_bindgen::prelude::*;
18use web_sys::{
19 console::{error_1, log_1},
20 Element,
21};
23#[wasm_bindgen]
24extern "C" {
25 fn alert(s: &str);
27 #[wasm_bindgen(js_namespace = console)]
28 fn log(s: &str);
30 #[wasm_bindgen(js_namespace = console)]
31 fn error(s: &str);
34#[derive(Clone)]
35struct WockApp {
36 nock: Rc<Noun>,
37 ctx: RefCell<InterpreterContext>,
38 model: Rc<Noun>,
41impl<'a> Render<'a> for WockApp {
42 fn render(&self, cx: &mut dodrio::RenderContext<'a>) -> dodrio::Node<'a> {
43 match slam(
44 &mut *self.ctx.borrow_mut(),
45 &self.nock,
46 &cell(&Rc::new(Noun::from_bytes(b"view")), &self.model),
47 ) {
48 Ok(sail) => render_sail(cx, sail),
49 Err(tanks) => {
50 let str = ram_ttanks(&mut (*self).ctx.borrow_mut(), tanks);
51 error_1(&JsValue::from_str(&str));
53 panic!("Could not render");
54 }
55 }
56 }
59fn render_sail<'a>(ctx: &mut RenderContext<'a>, manx: Rc<Noun>) -> Node<'a> {
60 let (marx, marl) = manx.as_cell().unwrap();
61 let (mane, mart) = marx.as_cell().unwrap();
63 if mane.is_sig() {
64 let contents = mart
65 .list_iter()
66 .map(|attr| attr.as_cell().unwrap())
67 .find(|(key, _)| key.is_sig())
68 .map(|(_, value)| tape_to_bump_str(ctx.bump, value))
69 .unwrap_or("");
70 Node::text(contents)
71 } else {
72 let mut children = bumpalo::collections::Vec::new_in(&ctx.bump);
73 marl.list_iter()
74 .map(|child| render_sail(ctx, child.clone()))
75 .collect_into(&mut children);
77 let mut attributes = bumpalo::collections::Vec::new_in(ctx.bump);
78 let mut listeners = bumpalo::collections::Vec::new_in(ctx.bump);
80 mart.list_iter().for_each(|attr| {
81 let (name, value) = attr.as_cell().unwrap();
82 match &*name.as_bytes().unwrap() {
83 b"on-click" => listeners.push(Listener {
84 event: "click",
85 callback: {
86 let value = value.clone();
87 ctx.bump.alloc(
88 move |root: &mut dyn RootRender,
89 vdom: VdomWeak,
90 _event: web_sys::Event| {
91 let app = root.unwrap_mut::<WockApp>();
93 let binding = app.ctx.clone();
94 let mut ctx = binding.borrow_mut();
95 let sig = ctx.nouns.sig.clone();
97 let step: Rc<Noun> = Rc::new((value.clone(), sig).into());
99 match slam(
100 &mut ctx,
101 &app.nock,
102 &Rc::new(
103 (
104 Noun::from_bytes(b"move").rc(),
105 app.model.clone(),
106 step.clone(),
107 )
108 .into(),
109 ),
110 ) {
111 Ok(new_model) => {
112 app.model = new_model;
113 vdom.schedule_render();
114 }
115 Err(tanks) => {
116 let mut ctx = ctx.borrow_mut().clone();
118 let str = ram_ttanks(&mut ctx, tanks);
119 error_1(&JsValue::from_str(&str));
121 error(&*format!(
122 "Application failed to process event: {}",
123 step
124 ))
125 }
126 };
127 },
128 )
129 },
130 }),
131 _ => attributes.push(Attribute {
132 name: cord_to_bump_str(ctx.bump, name),
133 value: tape_to_bump_str(ctx.bump, value),
134 }),
135 };
136 });
138 Node::element(
139 &ctx.bump,
140 NodeKey::NONE,
141 cord_to_bump_str(ctx.bump, mane),
142 listeners.into_bump_slice(),
143 attributes.into_bump_slice(),
144 children.into_bump_slice(),
145 None,
146 )
147 }
150fn cord_to_bump_str<'a>(bump: &'a Bump, non: &Rc<Noun>) -> &'a str {
151 let bytes =
152 bumpalo::collections::Vec::from_iter_in(non.as_bytes().unwrap().iter().cloned(), bump);
153 &*bumpalo::collections::String::from_utf8(bytes)
154 .unwrap()
155 .into_bump_str()
158fn tape_to_bump_str<'a>(bump: &'a Bump, non: &Rc<Noun>) -> &'a str {
159 let bytes = bumpalo::collections::Vec::from_iter_in(
160 non.list_iter().map(|char| char.as_u32().unwrap() as u8),
161 bump,
162 );
163 &*bumpalo::collections::String::from_utf8(bytes)
164 .unwrap()
165 .into_bump_str()
168#[wasm_bindgen]
169pub async fn main(path: &str) {
170 let nock = load_nock(path).await.unwrap_or_else(|err| {
171 error(&*err.to_string());
172 panic!("Could not load nock.")
173 });
175 let document = gloo::utils::document();
176 let output_el: Element = document.get_element_by_id("output").unwrap();
178 let mut ctx = InterpreterContext {
179 slog: |str: &String| log_1(&JsValue::from_str(&*str)),
180 ..generate_interpreter_context()
181 };
183 let model = slam(
184 &mut ctx,
185 &nock,
186 &cell(&Rc::new(Noun::from_bytes(b"init")), &Rc::new(Noun::SIG)),
187 );
188 let model = match model {
189 Ok(model) => model,
190 Err(tanks) => {
191 let str = ram_ttanks(&mut ctx, tanks);
192 error_1(&JsValue::from_str(&str));
193 panic!("Could not load initial model.");
194 }
195 };
197 dodrio::Vdom::new(
198 &output_el,
199 WockApp {
200 nock,
201 model,
202 ctx: RefCell::new(ctx),
203 },
204 )
205 .forget();
208pub async fn load_nock(path: &str) -> Result<Rc<Noun>, net::Error> {
209 let blob = Request::get(path).send().await?.binary().await?;
211 let nock = cue_bytes(&*blob);
212 let (_typ, nok) = nock.as_cell().unwrap();
214 Ok(nok.clone())