blots_wasm/
lib.rs

1use anyhow::Result;
2use blots_core::{
3    expressions::evaluate_pairs,
4    functions::get_built_in_function_idents,
5    heap::{CONSTANTS, Heap},
6    parser::{Rule, Token, get_pairs, get_tokens},
7    values::SerializableValue,
8};
9use indexmap::IndexMap;
10use serde::{Deserialize, Serialize};
11use std::{
12    cell::RefCell,
13    collections::{HashMap, HashSet},
14    rc::Rc,
15};
16use wasm_bindgen::prelude::*;
17
18#[derive(Debug, Serialize, Deserialize)]
19struct EvaluationResult {
20    values: HashMap<String, SerializableValue>,
21    bindings: HashMap<String, SerializableValue>,
22    outputs: HashSet<String>,
23}
24
25#[wasm_bindgen]
26pub fn evaluate(expr: &str, inputs_js: JsValue) -> Result<JsValue, JsError> {
27    let heap = Rc::new(RefCell::new(Heap::new()));
28    let inputs_given: IndexMap<String, SerializableValue> =
29        serde_wasm_bindgen::from_value(inputs_js)?;
30
31    let inputs = inputs_given
32        .into_iter()
33        .map(|(key, value)| (key, value.to_value(&mut heap.borrow_mut()).unwrap()))
34        .collect();
35
36    let bindings = Rc::new(RefCell::new(HashMap::new()));
37    {
38        bindings.borrow_mut().insert(
39            String::from("inputs"),
40            heap.borrow_mut().insert_record(inputs),
41        );
42    }
43
44    let expr_owned = String::from(expr);
45    let pairs =
46        get_pairs(&expr_owned).map_err(|e| JsError::new(&format!("Parsing error: {}", e)))?;
47
48    let mut outputs = HashSet::new();
49    let mut values = HashMap::new();
50
51    for pair in pairs {
52        match pair.as_rule() {
53            Rule::statement => {
54                if let Some(inner_pair) = pair.into_inner().next() {
55                    let rule = inner_pair.as_rule();
56                    let start_line_col = inner_pair.as_span().start_pos().line_col();
57                    let end_line_col = inner_pair.as_span().end_pos().line_col();
58
59                    let inner_pairs = inner_pair.into_inner();
60
61                    if rule == Rule::output_declaration {
62                        let mut inner_pairs_clone = inner_pairs.clone();
63
64                        let next_token = inner_pairs_clone.next().unwrap(); // Skip the output keyword.
65                        let output_name = match next_token.as_rule() {
66                            Rule::identifier => next_token.as_str().to_string(),
67                            Rule::assignment => {
68                                let next_inner_token = next_token.into_inner().next().unwrap();
69                                match next_inner_token.as_rule() {
70                                    Rule::identifier => next_inner_token.as_str().to_string(),
71                                    _ => {
72                                        unreachable!(
73                                            "unexpected rule: {:?}",
74                                            next_inner_token.as_rule()
75                                        )
76                                    }
77                                }
78                            }
79                            _ => unreachable!("unexpected rule: {:?}", next_token.as_rule()),
80                        };
81
82                        outputs.insert(output_name);
83                    }
84
85                    let col_id = format!(
86                        "{}-{}__{}-{}",
87                        start_line_col.0, start_line_col.1, end_line_col.0, end_line_col.1
88                    );
89
90                    let value =
91                        evaluate_pairs(inner_pairs, Rc::clone(&heap), Rc::clone(&bindings), 0, expr)
92                            .map_err(|error| {
93                                JsError::new(&format!("Evaluation error: {}", error))
94                            })?;
95
96                    values.insert(col_id, value);
97                }
98            }
99            Rule::EOI => {}
100            rule => unreachable!("unexpected rule: {:?}", rule),
101        }
102    }
103
104    let values_serializable = values
105        .iter()
106        .map(|(k, v)| (k.clone(), v.to_serializable_value(&heap.borrow()).unwrap()))
107        .collect();
108
109    let bindings_serializable = bindings
110        .borrow()
111        .iter()
112        .map(|(k, v)| (k.clone(), v.to_serializable_value(&heap.borrow()).unwrap()))
113        .collect();
114
115    // Use json_compatible serializer to ensure Records are serialized as JSON objects
116    // instead of JavaScript Maps
117    let serializer = serde_wasm_bindgen::Serializer::json_compatible();
118    Ok(EvaluationResult {
119        values: values_serializable,
120        bindings: bindings_serializable,
121        outputs,
122    }
123    .serialize(&serializer)?)
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127enum SerialToken {
128    Start { rule: String, pos: usize },
129    End { rule: String, pos: usize },
130}
131
132#[wasm_bindgen]
133pub fn tokenize(input: &str) -> Result<JsValue, JsError> {
134    let input = input.to_string();
135    let tokens = get_tokens(&input)?;
136
137    let tokens: Vec<SerialToken> = tokens
138        .iter()
139        .map(|token| match token {
140            Token::Start { rule, pos } => SerialToken::Start {
141                rule: format!("{:?}", rule),
142                pos: pos.pos(),
143            },
144            Token::End { rule, pos } => SerialToken::End {
145                rule: format!("{:?}", rule),
146                pos: pos.pos(),
147            },
148        })
149        .collect();
150
151    // Use json_compatible serializer for consistency
152    let serializer = serde_wasm_bindgen::Serializer::json_compatible();
153    Ok(tokens.serialize(&serializer)?)
154}
155
156#[wasm_bindgen]
157pub fn get_built_in_function_names() -> Result<JsValue, JsError> {
158    // Use json_compatible serializer for consistency
159    let serializer = serde_wasm_bindgen::Serializer::json_compatible();
160    Ok(get_built_in_function_idents().serialize(&serializer)?)
161}
162
163#[wasm_bindgen]
164pub fn get_constants() -> Result<JsValue, JsError> {
165    let mut map = HashMap::new();
166    let constants = SerializableValue::Record(
167        CONSTANTS
168            .clone()
169            .into_iter()
170            .map(|(k, v)| (k, v.into()))
171            .collect(),
172    );
173
174    map.insert(String::from("constants"), constants);
175
176    // Use json_compatible serializer to ensure Records are serialized as JSON objects
177    let serializer = serde_wasm_bindgen::Serializer::json_compatible();
178    Ok(map.serialize(&serializer)?)
179}
180
181#[derive(Debug, Serialize, Deserialize)]
182#[serde(untagged)]
183enum ExpressionResult {
184    Value { value: SerializableValue },
185    Error { error: String },
186}
187
188#[wasm_bindgen]
189pub fn evaluate_inline_expressions(
190    expressions_js: JsValue,
191    inputs_js: JsValue,
192) -> Result<JsValue, JsError> {
193    let expressions: Vec<String> = serde_wasm_bindgen::from_value(expressions_js)?;
194    let inputs_given: IndexMap<String, SerializableValue> =
195        serde_wasm_bindgen::from_value(inputs_js)?;
196
197    let mut results = Vec::new();
198
199    for expr in expressions {
200        let result = evaluate_single_inline_expression(&expr, &inputs_given);
201        results.push(result);
202    }
203
204    // Use json_compatible serializer to ensure Records are serialized as JSON objects
205    let serializer = serde_wasm_bindgen::Serializer::json_compatible();
206    Ok(results.serialize(&serializer)?)
207}
208
209fn evaluate_single_inline_expression(
210    expr: &str,
211    inputs_given: &IndexMap<String, SerializableValue>,
212) -> ExpressionResult {
213    let heap = Rc::new(RefCell::new(Heap::new()));
214
215    // Convert inputs to Values
216    let inputs: IndexMap<String, _> = inputs_given
217        .iter()
218        .map(|(key, value)| (key.clone(), value.to_value(&mut heap.borrow_mut()).unwrap()))
219        .collect();
220
221    let bindings = Rc::new(RefCell::new(HashMap::new()));
222
223    // Add inputs record
224    {
225        bindings.borrow_mut().insert(
226            String::from("inputs"),
227            heap.borrow_mut().insert_record(inputs.clone()),
228        );
229    }
230
231    // Inject all input values directly into bindings for inline access
232    for (key, value) in inputs {
233        bindings.borrow_mut().insert(key, value);
234    }
235
236    // Parse the expression
237    let expr_string = expr.to_string();
238    let pairs = match get_pairs(&expr_string) {
239        Ok(pairs) => pairs,
240        Err(e) => {
241            return ExpressionResult::Error {
242                error: format!("Parsing error: {}", e),
243            };
244        }
245    };
246
247    // Find and evaluate the first statement/expression
248    for pair in pairs {
249        match pair.as_rule() {
250            Rule::statement => {
251                if let Some(inner_pair) = pair.into_inner().next() {
252                    let inner_pairs = inner_pair.into_inner();
253
254                    match evaluate_pairs(inner_pairs, Rc::clone(&heap), Rc::clone(&bindings), 0, expr) {
255                        Ok(value) => match value.to_serializable_value(&heap.borrow()) {
256                            Ok(serializable) => {
257                                return ExpressionResult::Value {
258                                    value: serializable,
259                                };
260                            }
261                            Err(e) => {
262                                return ExpressionResult::Error {
263                                    error: format!("Serialization error: {}", e),
264                                };
265                            }
266                        },
267                        Err(e) => {
268                            return ExpressionResult::Error {
269                                error: format!("Evaluation error: {}", e),
270                            };
271                        }
272                    }
273                }
274            }
275            Rule::EOI => continue,
276            _ => continue,
277        }
278    }
279
280    ExpressionResult::Error {
281        error: "No expression found".to_string(),
282    }
283}