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::{function::FunctionTable, global::GlobalStore, variable::VariableTable},
9 utils::path::normalize_path,
10};
11use devalang_types::Value;
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]
175pub fn unregister_playhead_callback() {
176 #[cfg(target_arch = "wasm32")]
177 {
178 crate::core::audio::interpreter::driver::unregister_playhead_callback();
179 }
180}
181
182fn parse_internal_from_string(virtual_path: &str, source: &str) -> Result<ParseResult, String> {
183 let entry_path = normalize_path(virtual_path);
184 let output_path = normalize_path("./temp");
185
186 let mut global_store = GlobalStore::new();
187 let loader =
188 ModuleLoader::from_raw_source(&entry_path, &output_path, source, &mut global_store);
189
190 let module = loader
191 .load_single_module(&mut global_store)
192 .map_err(|e| format!("Error loading module: {}", e))?;
193
194 let raw_ast = ast_to_string(module.statements.clone());
195
196 let found_errors = collect_errors_recursively(&module.statements);
197
198 let result = ParseResult {
199 ok: true,
200 ast: raw_ast,
201 errors: found_errors,
202 };
203
204 Ok(result)
205}
206
207fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
208 let mut errors: Vec<ErrorResult> = Vec::new();
209
210 for stmt in statements {
211 match &stmt.kind {
212 StatementKind::Unknown => {
213 errors.push(ErrorResult {
214 message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
215 line: stmt.line,
216 column: stmt.column,
217 });
218 }
219 StatementKind::Error { message } => {
220 errors.push(ErrorResult {
221 message: message.clone(),
222 line: stmt.line,
223 column: stmt.column,
224 });
225 }
226 StatementKind::Loop => {
227 if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
228 errors.extend(collect_errors_recursively(body_statements));
229 }
230 }
231 _ => {}
232 }
233 }
234
235 errors
236}
237
238fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
239 if let Value::Map(map) = value {
240 if let Some(Value::Block(statements)) = map.get("body") {
241 return Some(statements);
242 }
243 }
244 None
245}
246
247fn ast_to_string(statements: Vec<Statement>) -> String {
248 serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
249}