1pub mod config;
2pub mod core;
3
4use crate::core::{
5 audio::{engine::AudioEngine, interpreter::driver::run_audio_program},
6 parser::statement::{Statement, StatementKind},
7 preprocessor::loader::ModuleLoader,
8 store::global::GlobalStore,
9};
10use devalang_types::{FunctionTable, Value, VariableTable};
11use devalang_utils::path::normalize_path;
12use serde::{Deserialize, Serialize};
13use serde_wasm_bindgen::to_value;
14use wasm_bindgen::prelude::*;
15
16#[derive(Serialize, Deserialize)]
17struct ParseResult {
18 ok: bool,
19 ast: String,
20 errors: Vec<ErrorResult>,
21}
22
23#[derive(Serialize, Deserialize)]
24struct ErrorResult {
25 message: String,
26 line: usize,
27 column: usize,
28}
29
30#[wasm_bindgen]
31pub fn parse(entry_path: &str, source: &str) -> Result<JsValue, JsValue> {
32 let statements = parse_internal_from_string(entry_path, source);
33
34 match statements {
35 Ok(value) => {
36 let ast_string = value;
37 to_value(&ast_string)
38 .map_err(|e| JsValue::from_str(&format!("Error converting AST to JS value: {}", e)))
39 }
40 Err(e) => Err(JsValue::from_str(&format!("Error: {}", e))),
41 }
42}
43
44#[wasm_bindgen]
45pub fn debug_render(user_code: &str) -> Result<JsValue, JsValue> {
46 console_error_panic_hook::set_once();
47
48 let entry_path = normalize_path("playground.deva");
49 let output_path = normalize_path("./temp");
50
51 let mut global_store = GlobalStore::new();
52
53 let loader =
54 ModuleLoader::from_raw_source(&entry_path, &output_path, user_code, &mut global_store);
55
56 loader
57 .load_wasm_module(&mut global_store)
58 .map_err(|e| JsValue::from_str(&format!("Module loading error: {}", e)))?;
59
60 let all_statements_map = loader.extract_statements_map(&global_store);
61
62 let main_statements = all_statements_map
63 .get(&entry_path)
64 .ok_or(JsValue::from_str("No statements found for entry module"))?
65 .clone();
66
67 let mut audio_engine = AudioEngine::new("wasm_output".to_string());
68
69 let _ = run_audio_program(
70 &main_statements,
71 &mut audio_engine,
72 "playground".to_string(),
73 "wasm_output".to_string(),
74 VariableTable::new(),
75 FunctionTable::new(),
76 &mut global_store,
77 );
78
79 let samples = audio_engine.get_normalized_buffer();
83 let any_nonzero = samples.iter().any(|&s| s != 0.0);
84
85 let ast_res = parse_internal_from_string("playground.deva", user_code);
87 let ast_str = match ast_res {
88 Ok(p) => p.ast,
89 Err(_) => "".to_string(),
90 };
91
92 #[derive(Serialize)]
93 struct DebugResult {
94 samples_len: usize,
95 any_nonzero: bool,
96 ast: String,
97 note_count: usize,
98 global_vars: Vec<String>,
99 statements_count: usize,
100 }
101
102 let out = DebugResult {
103 samples_len: samples.len(),
104 any_nonzero,
105 ast: ast_str,
106 note_count: audio_engine.note_count,
107 global_vars: global_store.variables.variables.keys().cloned().collect(),
108 statements_count: main_statements.len(),
109 };
110
111 to_value(&out).map_err(|e| JsValue::from_str(&format!("Error converting debug result: {}", e)))
112}
113
114#[wasm_bindgen]
115pub fn render_audio(user_code: &str) -> Result<js_sys::Float32Array, JsValue> {
116 console_error_panic_hook::set_once();
117
118 let entry_path = normalize_path("playground.deva");
119 let output_path = normalize_path("./temp");
120
121 let mut global_store = GlobalStore::new();
122
123 let loader =
124 ModuleLoader::from_raw_source(&entry_path, &output_path, user_code, &mut global_store);
125
126 loader
127 .load_wasm_module(&mut global_store)
128 .map_err(|e| JsValue::from_str(&format!("Module loading error: {}", e)))?;
129
130 let all_statements_map = loader.extract_statements_map(&global_store);
131
132 let main_statements = all_statements_map
133 .get(&entry_path)
134 .ok_or(JsValue::from_str("No statements found for entry module"))?
135 .clone();
136
137 let mut audio_engine = AudioEngine::new("wasm_output".to_string());
138
139 let _ = run_audio_program(
140 &main_statements,
141 &mut audio_engine,
142 "playground".to_string(),
143 "wasm_output".to_string(),
144 VariableTable::new(),
145 FunctionTable::new(),
146 &mut global_store,
147 );
148
149 let samples = audio_engine.get_normalized_buffer();
150
151 if samples.is_empty() {
152 let silent = vec![0.0f32; 1024];
155 return Ok(js_sys::Float32Array::from(silent.as_slice()));
156 }
157
158 Ok(js_sys::Float32Array::from(samples.as_slice()))
159}
160
161#[wasm_bindgen]
162#[allow(unused_variables)]
163pub fn register_playhead_callback(cb: &js_sys::Function) {
164 #[cfg(target_arch = "wasm32")]
169 {
170 crate::core::audio::interpreter::driver::register_playhead_callback(cb.clone());
171 }
172}
173
174#[wasm_bindgen]
175#[allow(unused_variables)]
176pub fn collect_playhead_events() -> Result<JsValue, JsValue> {
177 #[cfg(target_arch = "wasm32")]
178 {
179 let events = crate::core::audio::interpreter::driver::collect_playhead_events();
180 to_value(&events).map_err(|e| JsValue::from_str(&format!("Error converting events: {}", e)))
181 }
182 #[cfg(not(target_arch = "wasm32"))]
183 {
184 to_value(&Vec::<String>::new()).map_err(|e| JsValue::from_str(&format!("Error: {}", e)))
186 }
187}
188
189#[wasm_bindgen]
190pub fn unregister_playhead_callback() {
191 #[cfg(target_arch = "wasm32")]
192 {
193 crate::core::audio::interpreter::driver::unregister_playhead_callback();
194 }
195}
196
197fn parse_internal_from_string(virtual_path: &str, source: &str) -> Result<ParseResult, String> {
198 let entry_path = normalize_path(virtual_path);
199 let output_path = normalize_path("./temp");
200
201 let mut global_store = GlobalStore::new();
202 let loader =
203 ModuleLoader::from_raw_source(&entry_path, &output_path, source, &mut global_store);
204
205 let module = loader
206 .load_single_module(&mut global_store)
207 .map_err(|e| format!("Error loading module: {}", e))?;
208
209 let raw_ast = ast_to_string(module.statements.clone());
210
211 let found_errors = collect_errors_recursively(&module.statements);
212
213 let result = ParseResult {
214 ok: true,
215 ast: raw_ast,
216 errors: found_errors,
217 };
218
219 Ok(result)
220}
221
222fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
223 let mut errors: Vec<ErrorResult> = Vec::new();
224
225 for stmt in statements {
226 match &stmt.kind {
227 StatementKind::Unknown => {
228 errors.push(ErrorResult {
229 message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
230 line: stmt.line,
231 column: stmt.column,
232 });
233 }
234 StatementKind::Error { message } => {
235 errors.push(ErrorResult {
236 message: message.clone(),
237 line: stmt.line,
238 column: stmt.column,
239 });
240 }
241 StatementKind::Loop => {
242 if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
243 errors.extend(collect_errors_recursively(body_statements));
244 }
245 }
246 _ => {}
247 }
248 }
249
250 errors
251}
252
253fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
254 if let Value::Map(map) = value {
255 if let Some(Value::Block(statements)) = map.get("body") {
256 return Some(statements);
257 }
258 }
259 None
260}
261
262fn ast_to_string(statements: Vec<Statement>) -> String {
263 serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use devalang_types::{Statement, StatementKind, Value};
270
271 #[test]
272 fn test_extract_loop_body_statements_none() {
273 let v = Value::Map(std::collections::HashMap::new());
274 assert!(extract_loop_body_statements(&v).is_none());
275 }
276
277 #[test]
278 fn test_extract_loop_body_statements_some() {
279 let stmt = Statement::unknown();
280 let mut map = std::collections::HashMap::new();
281 map.insert("body".to_string(), Value::Block(vec![stmt.clone(), stmt]));
282
283 let v = Value::Map(map);
284 let res = extract_loop_body_statements(&v);
285 assert!(res.is_some());
286 let slice = res.unwrap();
287 assert_eq!(slice.len(), 2);
288 }
289
290 #[test]
291 fn test_collect_errors_recursively_detection() {
292 let mut statements: Vec<Statement> = Vec::new();
293
294 let s1 = Statement::unknown_with_pos(0, 10, 2);
296 statements.push(s1.clone());
297
298 let s2 = Statement::error_with_pos(0, 20, 4, "boom".to_string());
300 statements.push(s2.clone());
301
302 let body_stmt = Statement::unknown_with_pos(1, 30, 5);
304 let mut loop_map = std::collections::HashMap::new();
305 loop_map.insert("body".to_string(), Value::Block(vec![body_stmt.clone()]));
306
307 let loop_stmt = Statement {
308 kind: StatementKind::Loop,
309 value: Value::Map(loop_map),
310 indent: 0,
311 line: 15,
312 column: 1,
313 };
314 statements.push(loop_stmt);
315
316 let errors = collect_errors_recursively(&statements);
317 assert_eq!(errors.len(), 3);
319 assert!(errors.iter().any(|e| e.line == 10));
320 assert!(errors.iter().any(|e| e.line == 20));
321 assert!(errors.iter().any(|e| e.line == 30));
322 }
323}