website_playground/
lib.rs

1mod utils;
2use std::cell::RefCell;
3use std::collections::HashMap;
4use std::task::{Context, Poll};
5use std::thread_local;
6
7use dfir_datalog_core::gen_dfir_graph;
8use dfir_lang::diagnostic::{Diagnostic, Level};
9use dfir_lang::graph::{WriteConfig, build_hfcode, partition_graph};
10use dfir_rs::datalog;
11use dfir_rs::scheduled::graph::Dfir;
12use proc_macro2::{LineColumn, Span};
13use quote::quote;
14use serde::{Deserialize, Serialize};
15use wasm_bindgen::prelude::*;
16
17#[wasm_bindgen]
18extern "C" {
19    fn alert(s: &str);
20    #[wasm_bindgen(js_namespace = console)]
21    fn log(s: &str);
22}
23
24#[wasm_bindgen]
25pub fn init() {
26    utils::set_panic_hook();
27}
28
29#[derive(Serialize, Deserialize)]
30pub struct JsLineColumn {
31    pub line: usize,
32    pub column: usize,
33}
34
35impl From<LineColumn> for JsLineColumn {
36    fn from(lc: LineColumn) -> Self {
37        JsLineColumn {
38            line: lc.line,
39            column: lc.column,
40        }
41    }
42}
43
44#[derive(Serialize, Deserialize)]
45pub struct JsSpan {
46    pub start: JsLineColumn,
47    pub end: Option<JsLineColumn>,
48}
49
50impl From<Span> for JsSpan {
51    fn from(span: Span) -> Self {
52        #[cfg(procmacro2_semver_exempt)]
53        let is_call_site = span.eq(&Span::call_site());
54
55        #[cfg(not(procmacro2_semver_exempt))]
56        let is_call_site = true;
57
58        if is_call_site {
59            JsSpan {
60                start: JsLineColumn { line: 0, column: 0 },
61                end: None,
62            }
63        } else {
64            JsSpan {
65                start: span.start().into(),
66                end: Some(span.end().into()),
67            }
68        }
69    }
70}
71
72#[derive(Serialize, Deserialize)]
73pub struct JsDiagnostic {
74    pub span: JsSpan,
75    pub message: String,
76    pub is_error: bool,
77}
78
79impl From<Diagnostic> for JsDiagnostic {
80    fn from(diag: Diagnostic) -> Self {
81        JsDiagnostic {
82            span: diag.span.into(),
83            message: diag.message,
84            is_error: diag.level == Level::Error,
85        }
86    }
87}
88
89#[derive(Serialize, Deserialize)]
90pub struct DfirResult {
91    pub output: Option<DfirOutput>,
92    pub diagnostics: Vec<JsDiagnostic>,
93}
94#[derive(Serialize, Deserialize)]
95pub struct DfirOutput {
96    pub compiled: String,
97    pub mermaid: String,
98}
99
100#[wasm_bindgen]
101pub fn compile_dfir(
102    program: String,
103    no_subgraphs: bool,
104    no_varnames: bool,
105    no_pull_push: bool,
106    no_handoffs: bool,
107    no_references: bool,
108    op_short_text: bool,
109) -> JsValue {
110    let write_config = WriteConfig {
111        no_subgraphs,
112        no_varnames,
113        no_pull_push,
114        no_handoffs,
115        no_references,
116        op_short_text,
117        op_text_no_imports: false,
118    };
119
120    let out = match syn::parse_str(&program) {
121        Ok(input) => {
122            let (graph_code_opt, diagnostics) = build_hfcode(input, &quote!(dfir_rs));
123            let output = graph_code_opt.map(|(graph, code)| {
124                let mermaid = graph.to_mermaid(&write_config);
125                let file = syn::parse_quote! {
126                    fn main() {
127                        let mut df = #code;
128                        df.run_available();
129                    }
130                };
131                let compiled = prettyplease::unparse(&file);
132                DfirOutput { mermaid, compiled }
133            });
134            DfirResult {
135                output,
136                diagnostics: diagnostics.into_iter().map(Into::into).collect(),
137            }
138        }
139        Err(errors) => DfirResult {
140            output: None,
141            diagnostics: errors
142                .into_iter()
143                .map(|e| JsDiagnostic {
144                    span: e.span().into(),
145                    message: e.to_string(),
146                    is_error: true,
147                })
148                .collect(),
149        },
150    };
151
152    serde_wasm_bindgen::to_value(&out).unwrap()
153}
154
155#[wasm_bindgen]
156pub fn compile_datalog(
157    program: String,
158    no_subgraphs: bool,
159    no_varnames: bool,
160    no_pull_push: bool,
161    no_handoffs: bool,
162    no_references: bool,
163    op_short_text: bool,
164) -> JsValue {
165    let write_config = WriteConfig {
166        no_subgraphs,
167        no_varnames,
168        no_pull_push,
169        no_handoffs,
170        no_references,
171        op_short_text,
172        op_text_no_imports: false,
173    };
174
175    let wrapped = format!("r#\"{}\"#", program);
176    let out = match syn::parse_str(&wrapped) {
177        Ok(input) => match gen_dfir_graph(input) {
178            Ok(flat_graph) => {
179                let mut diagnostics = Vec::new();
180                let output = match partition_graph(flat_graph) {
181                    Ok(part_graph) => {
182                        let out =
183                            part_graph.as_code(&quote!(dfir_rs), true, quote!(), &mut diagnostics);
184                        let file: syn::File = syn::parse_quote! {
185                            fn main() {
186                                #out
187                            }
188                        };
189
190                        Some(DfirOutput {
191                            compiled: prettyplease::unparse(&file),
192                            mermaid: part_graph.to_mermaid(&write_config),
193                        })
194                    }
195                    Err(diagnostic) => {
196                        diagnostics.push(diagnostic);
197                        None
198                    }
199                };
200                DfirResult {
201                    output,
202                    diagnostics: diagnostics.into_iter().map(Into::into).collect(),
203                }
204            }
205            Err(diagnostics) => DfirResult {
206                output: None,
207                diagnostics: diagnostics.into_iter().map(Into::into).collect(),
208            },
209        },
210        Err(err) => DfirResult {
211            output: None,
212            diagnostics: vec![
213                Diagnostic {
214                    span: Span::call_site(),
215                    level: Level::Error,
216                    message: format!("Error: Could not parse input: {}", err),
217                }
218                .into(),
219            ],
220        },
221    };
222
223    serde_wasm_bindgen::to_value(&out).unwrap()
224}
225
226struct DfirInstance<'a, In, Out> {
227    dfir: Dfir<'a>,
228    input: tokio::sync::mpsc::UnboundedSender<In>,
229    output: tokio::sync::mpsc::UnboundedReceiver<Out>,
230}
231
232type DatalogBooleanDemoInstance = DfirInstance<'static, (i32,), (i32,)>;
233
234thread_local! {
235    static DATALOG_BOOLEAN_DEMO_INSTANCES: RefCell<HashMap<String, DatalogBooleanDemoInstance>> =
236        RefCell::new(HashMap::new());
237}
238
239#[wasm_bindgen]
240pub fn init_datalog_boolean_demo(instance_name: &str) {
241    DATALOG_BOOLEAN_DEMO_INSTANCES.with(|map| {
242        let (in_send, input) = dfir_rs::util::unbounded_channel::<(i32,)>();
243        let (out, out_recv) = dfir_rs::util::unbounded_channel::<(i32,)>();
244        let dfir = datalog!(
245            r#"
246              .input ints `source_stream(input)`
247              .output result `for_each(|v| out.send(v).unwrap())`
248
249              result(a) :- ints(a), ( a >= 0 ).
250            "#
251        );
252
253        map.borrow_mut().insert(
254            instance_name.into(),
255            DatalogBooleanDemoInstance {
256                dfir,
257                input: in_send,
258                output: out_recv.into_inner(),
259            },
260        );
261    })
262}
263
264#[wasm_bindgen]
265pub fn send_datalog_boolean_demo(instance_name: &str, input: i32) -> Option<i32> {
266    DATALOG_BOOLEAN_DEMO_INSTANCES.with(|map| -> Option<i32> {
267        let mut map = map.borrow_mut();
268        let instance = map.get_mut(instance_name)?;
269        instance.input.send((input,)).unwrap();
270        instance.dfir.run_tick();
271        match instance
272            .output
273            .poll_recv(&mut Context::from_waker(futures::task::noop_waker_ref()))
274        {
275            Poll::Pending => None,
276            Poll::Ready(opt) => Some(opt?.0),
277        }
278    })
279}