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, "e!(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("e!(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}