@devaloop/devalang 0.0.1-alpha.13 → 0.0.1-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.devalang +8 -9
  2. package/Cargo.toml +58 -54
  3. package/README.md +36 -21
  4. package/docs/CHANGELOG.md +40 -2
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +2 -2
  7. package/docs/TODO.md +5 -4
  8. package/examples/bank.deva +2 -4
  9. package/examples/function.deva +15 -0
  10. package/examples/index.deva +25 -11
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +6 -6
  13. package/project-version.json +3 -3
  14. package/rust/cli/bank.rs +2 -1
  15. package/rust/cli/build.rs +69 -30
  16. package/rust/cli/check.rs +45 -5
  17. package/rust/cli/driver.rs +40 -28
  18. package/rust/cli/install.rs +22 -7
  19. package/rust/cli/login.rs +134 -0
  20. package/rust/cli/mod.rs +2 -1
  21. package/rust/cli/play.rs +44 -19
  22. package/rust/common/api.rs +8 -0
  23. package/rust/common/cdn.rs +2 -5
  24. package/rust/common/mod.rs +3 -1
  25. package/rust/common/sso.rs +8 -0
  26. package/rust/config/driver.rs +19 -1
  27. package/rust/config/loader.rs +56 -10
  28. package/rust/core/audio/engine.rs +152 -42
  29. package/rust/core/audio/interpreter/arrow_call.rs +34 -15
  30. package/rust/core/audio/interpreter/call.rs +2 -2
  31. package/rust/core/audio/interpreter/driver.rs +19 -14
  32. package/rust/core/audio/interpreter/spawn.rs +2 -2
  33. package/rust/core/builder/mod.rs +11 -6
  34. package/rust/core/lexer/handler/indent.rs +16 -2
  35. package/rust/core/lexer/token.rs +1 -0
  36. package/rust/core/mod.rs +2 -1
  37. package/rust/core/parser/driver.rs +46 -5
  38. package/rust/core/parser/handler/arrow_call.rs +78 -18
  39. package/rust/core/parser/handler/bank.rs +35 -7
  40. package/rust/core/parser/handler/dot.rs +43 -22
  41. package/rust/core/plugin/loader.rs +48 -0
  42. package/rust/core/plugin/mod.rs +1 -0
  43. package/rust/core/preprocessor/loader.rs +6 -4
  44. package/rust/installer/addon.rs +80 -0
  45. package/rust/installer/bank.rs +24 -14
  46. package/rust/installer/mod.rs +4 -1
  47. package/rust/installer/plugin.rs +55 -0
  48. package/rust/main.rs +32 -9
  49. package/rust/utils/logger.rs +16 -0
  50. package/rust/utils/spinner.rs +2 -4
@@ -1,6 +1,5 @@
1
1
  use std::{ fs, path::Path };
2
- use std::collections::HashMap;
3
- use crate::config::driver::{ BankEntry, Config };
2
+ use crate::config::driver::{ BankEntry, BankMetadata, Config };
4
3
 
5
4
  pub fn load_config(path: Option<&Path>) -> Option<Config> {
6
5
  let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
@@ -56,6 +55,59 @@ pub fn remove_bank_from_config(config: &mut Config, dependency: &str) {
56
55
  }
57
56
  }
58
57
 
58
+ pub fn add_plugin_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
59
+ if config.plugins.is_none() {
60
+ config.plugins = Some(Vec::new());
61
+ }
62
+
63
+ let plugins = config.plugins.as_mut().unwrap();
64
+
65
+ let exists = plugins.iter().any(|p| p.path == dependency);
66
+ if exists {
67
+ println!("Plugin '{}' already in config", dependency);
68
+ return;
69
+ }
70
+
71
+ let metadata_path = Path::new(real_path).join("plugin.toml");
72
+
73
+ if !metadata_path.exists() {
74
+ eprintln!("❌ Plugin metadata file '{}' does not exist", metadata_path.display());
75
+ return;
76
+ }
77
+
78
+ let metadata_content = std::fs
79
+ ::read_to_string(&metadata_path)
80
+ .expect("Failed to read plugin metadata file");
81
+
82
+ let metadata: std::collections::HashMap<String, String> = toml
83
+ ::from_str(&metadata_content)
84
+ .expect("Failed to parse plugin metadata file");
85
+
86
+ let plugin_entry = crate::config::driver::PluginEntry {
87
+ path: dependency.to_string(),
88
+ version: metadata
89
+ .get("version")
90
+ .cloned()
91
+ .unwrap_or_else(|| "0.0.1".to_string()),
92
+ author: metadata
93
+ .get("author")
94
+ .cloned()
95
+ .unwrap_or_else(|| "unknown".to_string()),
96
+ access: metadata
97
+ .get("access")
98
+ .cloned()
99
+ .unwrap_or_else(|| "public".to_string()),
100
+ };
101
+
102
+ plugins.push(plugin_entry);
103
+
104
+ if let Err(e) = config.write(config) {
105
+ eprintln!("❌ Failed to write config: {}", e);
106
+ } else {
107
+ println!("✅ Plugin '{}' added to config", dependency);
108
+ }
109
+ }
110
+
59
111
  pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
60
112
  if config.banks.is_none() {
61
113
  config.banks = Some(Vec::new());
@@ -80,24 +132,18 @@ pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &st
80
132
  ::read_to_string(&metadata_path)
81
133
  .expect("Failed to read bank metadata file");
82
134
 
83
- let metadata: HashMap<String, String> = toml
135
+ let metadata: BankMetadata = toml
84
136
  ::from_str(&metadata_content)
85
137
  .expect("Failed to parse bank metadata file");
86
138
 
87
139
  let bank_to_insert = BankEntry {
88
140
  path: dependency.to_string(),
89
141
  version: Some(
90
- metadata
142
+ metadata.bank
91
143
  .get("version")
92
144
  .cloned()
93
145
  .unwrap_or_else(|| "0.0.1".to_string())
94
146
  ),
95
- author: Some(
96
- metadata
97
- .get("author")
98
- .cloned()
99
- .unwrap_or_else(|| "unknown".to_string())
100
- ),
101
147
  };
102
148
 
103
149
  banks.push(bank_to_insert);
@@ -100,30 +100,141 @@ impl AudioEngine {
100
100
  freq: f32,
101
101
  amp: f32,
102
102
  start_time_ms: f32,
103
- duration_ms: f32
103
+ duration_ms: f32,
104
+ synth_params: HashMap<String, Value>,
105
+ note_params: HashMap<String, Value>
104
106
  ) {
107
+ let valid_synth_params = vec!["attack", "decay", "sustain", "release"];
108
+ let valid_note_params = vec![
109
+ "duration",
110
+ "velocity",
111
+ "glide",
112
+ "slide",
113
+ "amp",
114
+ "target_freq",
115
+ "target_amp",
116
+ "modulation",
117
+ "expression"
118
+ ];
119
+
120
+ // Synth params validation
121
+ for key in synth_params.keys() {
122
+ if !valid_synth_params.contains(&key.as_str()) {
123
+ eprintln!("⚠️ Unknown synth parameter: '{}'", key);
124
+ }
125
+ }
126
+
127
+ // Note params validation
128
+ for key in note_params.keys() {
129
+ if !valid_note_params.contains(&key.as_str()) {
130
+ eprintln!("⚠️ Unknown note parameter: '{}'", key);
131
+ }
132
+ }
133
+
134
+ // Synth parameters
135
+ let attack = self.extract_f32(&synth_params, "attack").unwrap_or(0.0);
136
+ let decay = self.extract_f32(&synth_params, "decay").unwrap_or(0.0);
137
+ let sustain = self.extract_f32(&synth_params, "sustain").unwrap_or(0.0);
138
+ let release = self.extract_f32(&synth_params, "release").unwrap_or(0.0);
139
+ let attack_s = if attack > 10.0 { attack / 1000.0 } else { attack };
140
+ let decay_s = if decay > 10.0 { decay / 1000.0 } else { decay };
141
+ let release_s = if release > 10.0 { release / 1000.0 } else { release };
142
+ let sustain_level = if sustain > 1.0 { (sustain / 100.0).clamp(0.0, 1.0) } else { sustain.clamp(0.0, 1.0) };
143
+
144
+ // Note parameters
145
+ let duration_ms = self.extract_f32(&note_params, "duration").unwrap_or(duration_ms);
146
+ let velocity = self.extract_f32(&note_params, "velocity").unwrap_or(1.0);
147
+ let glide = self.extract_boolean(&note_params, "glide").unwrap_or(false);
148
+ let slide = self.extract_boolean(&note_params, "slide").unwrap_or(false);
149
+
150
+ let _amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0) * velocity.clamp(0.0, 1.0);
151
+
152
+ // Logic for glide and slide
153
+ let freq_start = freq;
154
+ let mut freq_end = freq;
155
+ let amp_start = amp * velocity.clamp(0.0, 1.0);
156
+ let mut amp_end = amp_start;
157
+
158
+ if glide {
159
+ if let Some(Value::Number(target_freq)) = note_params.get("target_freq") {
160
+ freq_end = *target_freq;
161
+ } else {
162
+ freq_end = freq * 1.5; // Par défaut, glide vers une quinte
163
+ }
164
+ }
165
+
166
+ if slide {
167
+ if let Some(Value::Number(target_amp)) = note_params.get("target_amp") {
168
+ amp_end = *target_amp * velocity.clamp(0.0, 1.0);
169
+ } else {
170
+ amp_end = amp_start * 0.5; // Par défaut, slide vers la moitié
171
+ }
172
+ }
173
+
105
174
  let sample_rate = SAMPLE_RATE as f32;
106
175
  let channels = CHANNELS as usize;
107
176
 
108
177
  let total_samples = ((duration_ms / 1000.0) * sample_rate) as usize;
109
178
  let start_sample = ((start_time_ms / 1000.0) * sample_rate) as usize;
110
- let amplitude = (i16::MAX as f32) * amp.clamp(0.0, 1.0);
111
179
 
112
180
  let mut samples = Vec::with_capacity(total_samples);
113
- let fade_len = (sample_rate * 0.01) as usize; // 10 ms fade
181
+ let fade_len = (sample_rate * 0.01) as usize; // 10 ms fade
182
+
183
+ let attack_samples = (attack_s * sample_rate) as usize;
184
+ let decay_samples = (decay_s * sample_rate) as usize;
185
+ let release_samples = (release_s * sample_rate) as usize;
186
+ let sustain_samples = if total_samples > attack_samples + decay_samples + release_samples {
187
+ total_samples - attack_samples - decay_samples - release_samples
188
+ } else {
189
+ 0
190
+ };
114
191
 
115
192
  for i in 0..total_samples {
116
193
  let t = ((start_sample + i) as f32) / sample_rate;
117
- let phase = 2.0 * std::f32::consts::PI * freq * t;
194
+
195
+ // Glide
196
+ let current_freq = if glide {
197
+ freq_start + ((freq_end - freq_start) * (i as f32)) / (total_samples as f32)
198
+ } else {
199
+ freq
200
+ };
201
+
202
+ // Slide
203
+ let current_amp = if slide {
204
+ amp_start + ((amp_end - amp_start) * (i as f32)) / (total_samples as f32)
205
+ } else {
206
+ amp_start
207
+ };
208
+
209
+ let phase = 2.0 * std::f32::consts::PI * current_freq * t;
118
210
 
119
211
  let mut value = match waveform.as_str() {
120
212
  "sine" => phase.sin(),
121
- "square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 }
122
- "saw" => 2.0 * (freq * t - (freq * t + 0.5).floor()),
123
- "triangle" => (2.0 * (2.0 * (freq * t).fract() - 1.0)).abs() * 2.0 - 1.0,
213
+ "square" => if phase.sin() >= 0.0 { 1.0 } else { -1.0 },
214
+ "saw" => 2.0 * (current_freq * t - (current_freq * t + 0.5).floor()),
215
+ "triangle" => (2.0 * (2.0 * (current_freq * t).fract() - 1.0)).abs() * 2.0 - 1.0,
124
216
  _ => 0.0,
125
217
  };
126
218
 
219
+ // ADSR envelope
220
+ let envelope = if i < attack_samples {
221
+ (i as f32) / (attack_samples as f32)
222
+ } else if i < attack_samples + decay_samples {
223
+ 1.0 -
224
+ (1.0 - sustain_level) * (((i - attack_samples) as f32) / (decay_samples as f32))
225
+ } else if i < attack_samples + decay_samples + sustain_samples {
226
+ sustain_level
227
+ } else {
228
+ if release_samples > 0 {
229
+ sustain_level *
230
+ (1.0 -
231
+ ((i - attack_samples - decay_samples - sustain_samples) as f32) /
232
+ (release_samples as f32))
233
+ } else {
234
+ 0.0
235
+ }
236
+ };
237
+
127
238
  // Fade in/out
128
239
  if i < fade_len {
129
240
  value *= (i as f32) / (fade_len as f32);
@@ -131,7 +242,9 @@ impl AudioEngine {
131
242
  value *= ((total_samples - i) as f32) / (fade_len as f32);
132
243
  }
133
244
 
134
- samples.push((value * amplitude) as i16);
245
+ value *= envelope;
246
+ // Application de l'amplitude dynamique (slide + velocity)
247
+ samples.push((value * (i16::MAX as f32) * current_amp) as i16);
135
248
  }
136
249
 
137
250
  // Convert to stereo
@@ -148,8 +261,9 @@ impl AudioEngine {
148
261
  }
149
262
 
150
263
  for (i, sample) in stereo_samples.iter().enumerate() {
151
- if *sample != 0 {
152
- }
264
+ // Debug: note si on rencontre des samples non nuls
265
+ // (pour traquer les buffers silencieux)
266
+ // if i == 0 { eprintln!("[debug] first stereo sample: {}", sample); }
153
267
  self.buffer[offset + i] = self.buffer[offset + i].saturating_add(*sample);
154
268
  }
155
269
  }
@@ -168,8 +282,8 @@ impl AudioEngine {
168
282
  }
169
283
 
170
284
  let root = Path::new(env!("CARGO_MANIFEST_DIR"));
171
- let module_root = Path::new(&self.module_name);
172
- let mut resolved_path = String::new();
285
+ let module_root = Path::new(&self.module_name);
286
+ let resolved_path: String;
173
287
 
174
288
  // Get the variable path from the variable table
175
289
  let mut var_path = filepath.to_string();
@@ -179,41 +293,14 @@ impl AudioEngine {
179
293
  var_path = sample_path.clone();
180
294
  }
181
295
 
182
- // If it's a namespace
183
- if var_path.contains(".") {
184
- let parts: Vec<&str> = var_path.trim_start_matches('.').split('.').collect();
185
- if parts.len() == 2 {
186
- let bank_name = parts[0];
187
- let entity_name = parts[1];
188
-
189
- // Verifies if the bank is declared
190
- if !variable_table.variables.contains_key(bank_name) {
191
- eprintln!(
192
- "❌ Bank '{}' not declared. Please declare it first using : 'bank {}'",
193
- bank_name,
194
- bank_name
195
- );
196
- return;
197
- }
198
-
199
- resolved_path = root
200
- .join(".deva")
201
- .join("bank")
202
- .join(bank_name)
203
- .join(format!("{}.wav", entity_name))
204
- .to_string_lossy()
205
- .to_string();
206
- } else {
207
- eprintln!("❌ Invalid namespace format: {}", var_path);
208
- return;
209
- }
210
- } else if var_path.starts_with("devalang://") {
296
+ // Handle devalang:// protocol
297
+ if var_path.starts_with("devalang://") {
211
298
  let path_after_protocol = var_path.replace("devalang://", "");
212
299
  let parts: Vec<&str> = path_after_protocol.split('/').collect();
213
300
 
214
301
  if parts.len() < 3 {
215
302
  eprintln!(
216
- "❌ Invalid devalang:// path format. Expected devalang://<type>/<bank>/<entity>"
303
+ "❌ Invalid devalang:// path format. Expected devalang://<type>/<author>.<bank>/<entity>"
217
304
  );
218
305
  return;
219
306
  }
@@ -405,4 +492,27 @@ impl AudioEngine {
405
492
  }
406
493
  }
407
494
  }
495
+
496
+ fn extract_f32(&self, map: &HashMap<String, Value>, key: &str) -> Option<f32> {
497
+ match map.get(key) {
498
+ Some(Value::Number(n)) => Some(*n),
499
+ Some(Value::String(s)) => s.parse::<f32>().ok(),
500
+ Some(Value::Boolean(b)) => Some(if *b { 1.0 } else { 0.0 }),
501
+ _ => None,
502
+ }
503
+ }
504
+
505
+ fn extract_boolean(&self, map: &HashMap<String, Value>, key: &str) -> Option<bool> {
506
+ match map.get(key) {
507
+ Some(Value::Boolean(b)) => Some(*b),
508
+ Some(Value::Number(n)) => Some(*n != 0.0),
509
+ Some(Value::Identifier(s)) => {
510
+ if s == "true" { Some(true) } else if s == "false" { Some(false) } else { None }
511
+ }
512
+ Some(Value::String(s)) => {
513
+ if s == "true" { Some(true) } else if s == "false" { Some(false) } else { None }
514
+ }
515
+ _ => None,
516
+ }
517
+ }
408
518
  }
@@ -23,11 +23,16 @@ pub fn interprete_call_arrow_statement(
23
23
  .unwrap_or(0.0);
24
24
 
25
25
  if let StatementKind::ArrowCall { target, method, args } = &stmt.kind {
26
- let Some(Value::Map(synth_map)) = variable_table.get(target) else {
26
+ let Some(Value::Statement(synth_stmt)) = variable_table.get(target) else {
27
27
  println!("❌ Synth '{}' not found in variable table", target);
28
28
  return (*max_end_time, cursor_copy);
29
29
  };
30
30
 
31
+ let Value::Map(synth_map) = &synth_stmt.value else {
32
+ println!("❌ Invalid synth statement for '{}', expected a map.", target);
33
+ return (*max_end_time, cursor_copy);
34
+ };
35
+
31
36
  let Some(Value::String(entity)) = synth_map.get("entity") else {
32
37
  println!("❌ Missing 'entity' key in synth '{}'.", target);
33
38
  return (*max_end_time, cursor_copy);
@@ -53,37 +58,51 @@ pub fn interprete_call_arrow_statement(
53
58
  return (*max_end_time, cursor_copy);
54
59
  };
55
60
 
56
- let freq = extract_f32(params, "freq", base_bpm).unwrap_or(440.0);
57
- let amp = extract_f32(params, "amp", base_bpm).unwrap_or(1.0);
61
+ // Synth parameters
62
+ let synth_params = params.clone();
63
+ let amp = extract_f32(&synth_params, "amp", base_bpm).unwrap_or(1.0);
58
64
 
59
65
  if method == "note" {
60
- let Some(Value::Identifier(note_name)) = args.get(0) else {
66
+ let filtered_args: Vec<_> = args
67
+ .iter()
68
+ .filter(|arg| !matches!(arg, Value::Unknown))
69
+ .collect();
70
+
71
+ let Some(Value::Identifier(note_name)) = filtered_args.get(0).map(|v| (*v).clone()) else {
61
72
  println!("❌ Invalid or missing argument for 'note' method on '{}'.", target);
62
73
  return (*max_end_time, cursor_copy);
63
74
  };
64
75
 
65
- let mut final_note_params = HashMap::new();
66
- if let Some(Value::Map(note_params)) = args.get(1) {
67
- for (key, value) in note_params {
68
- final_note_params.insert(key.clone(), value.clone());
76
+ let mut note_params = HashMap::new();
77
+ if let Some(arg1) = filtered_args.get(1) {
78
+ match (*arg1).clone() {
79
+ Value::Map(map) => {
80
+ for (key, value) in map {
81
+ note_params.insert(key, value);
82
+ }
83
+ }
84
+ _ => {}
69
85
  }
70
86
  }
71
87
 
72
- let duration_ms = extract_f32(&final_note_params, "duration", base_bpm).unwrap_or(
73
- base_duration
74
- );
88
+ // Note parameters and calculations
89
+ let amp_note = extract_f32(&note_params, "amp", base_bpm).unwrap_or(amp);
90
+ let duration_ms = extract_f32(&note_params, "duration", base_bpm)
91
+ .unwrap_or(base_duration * 1000.0);
92
+
75
93
  let duration_secs = duration_ms / 1000.0;
76
-
77
- let final_freq = note_to_freq(note_name);
94
+ let final_freq = note_to_freq(&note_name);
78
95
  let start_time = cursor_copy;
79
96
  let end_time = start_time + duration_secs;
80
97
 
81
98
  audio_engine.insert_note(
82
99
  waveform.clone(),
83
100
  final_freq,
84
- amp,
101
+ amp_note,
85
102
  start_time * 1000.0,
86
- duration_ms
103
+ duration_ms,
104
+ synth_params,
105
+ note_params
87
106
  );
88
107
 
89
108
  *max_end_time = (*max_end_time).max(end_time);
@@ -18,7 +18,7 @@ pub fn interprete_call_statement(
18
18
  ) -> (f32, f32) {
19
19
  match &stmt.kind {
20
20
  StatementKind::Call { name, args } => {
21
- // 1. Cas : fonction classique
21
+ // Classic function call case
22
22
  if let Some(func) = functions.functions.get(name) {
23
23
  if func.parameters.len() != args.len() {
24
24
  eprintln!(
@@ -48,7 +48,7 @@ pub fn interprete_call_statement(
48
48
  );
49
49
  }
50
50
 
51
- // 2. Cas : group dans le scope local OU global
51
+ // Group case
52
52
  if let Some(group_stmt) = find_group(name, variable_table, global_store) {
53
53
  if let Value::Map(map) = &group_stmt.value {
54
54
  if let Some(Value::Block(body)) = map.get("body") {
@@ -52,8 +52,8 @@ pub fn run_audio_program(
52
52
  pub fn execute_audio_block(
53
53
  audio_engine: &mut AudioEngine,
54
54
  global_store: &GlobalStore,
55
- variable_table: VariableTable,
56
- functions_table: FunctionTable,
55
+ mut variable_table: VariableTable,
56
+ mut functions_table: FunctionTable,
57
57
  statements: Vec<Statement>,
58
58
  mut base_bpm: f32,
59
59
  mut base_duration: f32,
@@ -68,20 +68,21 @@ pub fn execute_audio_block(
68
68
  for stmt in others {
69
69
  match &stmt.kind {
70
70
  StatementKind::Load { .. } => {
71
- if
72
- let Some(new_table) = interprete_load_statement(
73
- &stmt,
74
- &mut variable_table.clone()
75
- )
76
- {
77
- // Extend the variable_table if necessary
71
+ if let Some(new_table) = interprete_load_statement(&stmt, &mut variable_table) {
72
+ variable_table = new_table;
78
73
  }
79
74
  }
80
75
  StatementKind::Let { .. } => {
81
- interprete_let_statement(&stmt, &mut variable_table.clone());
76
+ if let Some(new_table) = interprete_let_statement(&stmt, &mut variable_table) {
77
+ variable_table = new_table;
78
+ }
82
79
  }
83
80
  StatementKind::Function { .. } => {
84
- interprete_function_statement(&stmt, &mut functions_table.clone());
81
+ if let Some(new_functions) =
82
+ interprete_function_statement(&stmt, &mut functions_table)
83
+ {
84
+ functions_table = new_functions;
85
+ }
85
86
  }
86
87
  StatementKind::Tempo => {
87
88
  if let Some((new_bpm, new_duration)) = interprete_tempo_statement(&stmt) {
@@ -129,7 +130,7 @@ pub fn execute_audio_block(
129
130
  max_end_time = new_max;
130
131
  }
131
132
  StatementKind::Call { .. } => {
132
- let (new_max, new_cursor) = interprete_call_statement(
133
+ let (new_max, _) = interprete_call_statement(
133
134
  &stmt,
134
135
  audio_engine,
135
136
  &variable_table,
@@ -140,7 +141,7 @@ pub fn execute_audio_block(
140
141
  max_end_time,
141
142
  cursor_time,
142
143
  );
143
- cursor_time = new_cursor;
144
+ cursor_time = new_max;
144
145
  max_end_time = new_max;
145
146
  }
146
147
  StatementKind::ArrowCall { .. } => {
@@ -154,8 +155,12 @@ pub fn execute_audio_block(
154
155
  Some(&mut cursor_time),
155
156
  true
156
157
  );
158
+
157
159
  cursor_time = new_cursor;
158
- max_end_time = new_max;
160
+
161
+ if new_max > max_end_time {
162
+ max_end_time = new_max;
163
+ }
159
164
  }
160
165
  _ => {}
161
166
  }
@@ -20,7 +20,7 @@ pub fn interprete_spawn_statement(
20
20
  StatementKind::Spawn { name, args } => {
21
21
  let mut local_engine = AudioEngine::new(audio_engine.module_name.clone());
22
22
 
23
- // 1. Cas : fonction
23
+ // Function case
24
24
  if let Some(func) = functions.functions.get(name) {
25
25
  if func.parameters.len() != args.len() {
26
26
  eprintln!(
@@ -53,7 +53,7 @@ pub fn interprete_spawn_statement(
53
53
  return (spawn_max.max(max_end_time), cursor_time);
54
54
  }
55
55
 
56
- // 2. Cas : group dans variable_table ou global_store
56
+ // Group case
57
57
  if let Some(group_stmt) = find_group(name, variable_table, global_store) {
58
58
  if let Value::Map(map) = &group_stmt.value {
59
59
  if let Some(Value::Block(body)) = map.get("body") {
@@ -3,7 +3,6 @@ use crate::core::parser::statement::Statement;
3
3
  use crate::core::store::global::GlobalStore;
4
4
  use std::{ collections::HashMap, fs::create_dir_all };
5
5
  use std::io::Write;
6
-
7
6
  use crate::utils::logger::Logger;
8
7
 
9
8
  pub struct Builder {}
@@ -13,7 +12,12 @@ impl Builder {
13
12
  Builder {}
14
13
  }
15
14
 
16
- pub fn build_ast(&self, modules: &HashMap<String, Vec<Statement>>, out_dir: &str) {
15
+ pub fn build_ast(
16
+ &self,
17
+ modules: &HashMap<String, Vec<Statement>>,
18
+ out_dir: &str,
19
+ compress: bool
20
+ ) {
17
21
  for (name, statements) in modules {
18
22
  let formatted_name = name.split("/").last().unwrap_or(name);
19
23
  let formatted_name = formatted_name.replace(".deva", "");
@@ -22,10 +26,11 @@ impl Builder {
22
26
 
23
27
  let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
24
28
  let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
25
-
26
- let content = serde_json
27
- ::to_string_pretty(&statements)
28
- .expect("Failed to serialize AST");
29
+ let content = if compress {
30
+ serde_json::to_string(&statements).expect("Failed to serialize AST")
31
+ } else {
32
+ serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
33
+ };
29
34
 
30
35
  file.write_all(content.as_bytes()).expect("Failed to write AST to file");
31
36
  }
@@ -16,10 +16,24 @@ pub fn handle_indent_lexer(
16
16
  *current_indent += 1;
17
17
  chars.next();
18
18
  col += 1;
19
+ tokens.push(Token {
20
+ kind: TokenKind::Whitespace,
21
+ lexeme: " ".to_string(),
22
+ line: *line,
23
+ column: col,
24
+ indent: *current_indent,
25
+ });
19
26
  } else if c == '\t' {
20
27
  *current_indent += 4;
21
28
  chars.next();
22
29
  col += 4;
30
+ tokens.push(Token {
31
+ kind: TokenKind::Whitespace,
32
+ lexeme: "\t".to_string(),
33
+ line: *line,
34
+ column: col,
35
+ indent: *current_indent,
36
+ });
23
37
  } else {
24
38
  break;
25
39
  }
@@ -32,7 +46,7 @@ pub fn handle_indent_lexer(
32
46
  indent_stack.push(*current_indent);
33
47
  tokens.push(Token {
34
48
  kind: TokenKind::Indent,
35
- lexeme: String::new(),
49
+ lexeme: String::from("<INDENT>"),
36
50
  line: *line,
37
51
  column: *column,
38
52
  indent: *current_indent,
@@ -42,7 +56,7 @@ pub fn handle_indent_lexer(
42
56
  indent_stack.pop();
43
57
  tokens.push(Token {
44
58
  kind: TokenKind::Dedent,
45
- lexeme: String::new(),
59
+ lexeme: String::from("<DEDENT>"),
46
60
  line: *line,
47
61
  column: *column,
48
62
  indent: *current_indent,
@@ -84,6 +84,7 @@ pub enum TokenKind {
84
84
  ElseIf,
85
85
 
86
86
  // ───── Special / Internal ─────
87
+ Whitespace,
87
88
  Unknown,
88
89
  Error(String),
89
90
  EOF,
package/rust/core/mod.rs CHANGED
@@ -7,4 +7,5 @@ pub mod debugger;
7
7
  pub mod utils;
8
8
  pub mod builder;
9
9
  pub mod error;
10
- pub mod audio;
10
+ pub mod audio;
11
+ pub mod plugin;