1use anyhow::Result;
3use std::collections::HashMap;
4
5use crate::language::syntax::ast::{Value, Statement, StatementKind};
6
7use super::AudioInterpreter;
8
9pub fn handle_let(interpreter: &mut AudioInterpreter, name: &str, value: &Value) -> Result<()> {
10 if let Value::Map(map) = value {
12 if map.contains_key("waveform") || map.contains_key("_plugin_ref") {
13 let mut is_plugin = false;
15 let mut plugin_author: Option<String> = None;
16 let mut plugin_name: Option<String> = None;
17 let mut plugin_export: Option<String> = None;
18
19 if let Some(Value::String(plugin_ref)) = map.get("_plugin_ref") {
20 let parts: Vec<&str> = plugin_ref.split('.').collect();
21 if parts.len() == 2 {
22 let (var_name, prop_name) = (parts[0], parts[1]);
23 if let Some(var_value) = interpreter.variables.get(var_name) {
24 if let Value::Map(var_map) = var_value {
25 if let Some(Value::String(resolved_plugin)) = var_map.get(prop_name) {
26 if resolved_plugin.starts_with("plugin:") {
27 let ref_parts: Vec<&str> = resolved_plugin["plugin:".len()..].split(':').collect();
28 if ref_parts.len() == 2 {
29 let full_plugin_name = ref_parts[0];
30 let export_name = ref_parts[1];
31 let plugin_parts: Vec<&str> = full_plugin_name.split('.').collect();
32 if plugin_parts.len() == 2 {
33 plugin_author = Some(plugin_parts[0].to_string());
34 plugin_name = Some(plugin_parts[1].to_string());
35 plugin_export = Some(export_name.to_string());
36 is_plugin = true;
37 }
38 }
39 }
40 } else if let Some(Value::Map(export_map)) = var_map.get(prop_name) {
41 if let (Some(Value::String(author)), Some(Value::String(name)), Some(Value::String(export))) = (
42 export_map.get("_plugin_author"),
43 export_map.get("_plugin_name"),
44 export_map.get("_export_name")
45 ) {
46 plugin_author = Some(author.clone());
47 plugin_name = Some(name.clone());
48 plugin_export = Some(export.clone());
49 is_plugin = true;
50 }
51 }
52 }
53 }
54 }
55 }
56
57 let waveform = crate::engine::audio::events::extract_string(map, "waveform", "sine");
58 let attack = crate::engine::audio::events::extract_number(map, "attack", 0.01);
59 let decay = crate::engine::audio::events::extract_number(map, "decay", 0.1);
60 let sustain = crate::engine::audio::events::extract_number(map, "sustain", 0.7);
61 let release = crate::engine::audio::events::extract_number(map, "release", 0.2);
62
63 let synth_type = if let Some(Value::String(t)) = map.get("type") {
64 let clean = t.trim_matches('"').trim_matches('\'');
65 if clean.is_empty() || clean == "synth" { None } else { Some(clean.to_string()) }
66 } else { None };
67
68 let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") {
69 crate::engine::audio::events::extract_filters(filters_arr)
70 } else { Vec::new() };
71
72 let mut options = std::collections::HashMap::new();
73 let reserved_keys = if is_plugin {
74 vec!["attack", "decay", "sustain", "release", "type", "filters", "_plugin_ref"]
75 } else {
76 vec!["waveform", "attack", "decay", "sustain", "release", "type", "filters", "_plugin_ref"]
77 };
78
79 for (key, val) in map.iter() {
80 if !reserved_keys.contains(&key.as_str()) {
81 match val {
82 Value::Number(n) => { options.insert(key.clone(), *n); }
83 Value::String(s) => {
84 if is_plugin && key == "waveform" {
85 let waveform_id = match s.trim_matches('"').trim_matches('\'').to_lowercase().as_str() {
86 "sine" => 0.0,
87 "saw" => 1.0,
88 "square" => 2.0,
89 "triangle" => 3.0,
90 _ => 1.0,
91 };
92 options.insert(key.clone(), waveform_id);
93 }
94 }
95 _ => {}
96 }
97 }
98 }
99
100 if is_plugin && map.contains_key("decay") {
101 options.insert("decay".to_string(), decay);
102 }
103
104 let final_waveform = if is_plugin { "plugin".to_string() } else { waveform };
105
106 let synth_def = crate::engine::audio::events::SynthDefinition {
107 waveform: final_waveform,
108 attack,
109 decay,
110 sustain,
111 release,
112 synth_type,
113 filters,
114 options,
115 plugin_author,
116 plugin_name,
117 plugin_export,
118 };
119
120 println!("🎹 Synth registered: {} -> plugin_author={:?}, plugin_name={:?}, plugin_export={:?}", name, synth_def.plugin_author, synth_def.plugin_name, synth_def.plugin_export);
122 interpreter.events.add_synth(name.to_string(), synth_def);
123 }
124 }
125
126 interpreter.variables.insert(name.to_string(), value.clone());
127 Ok(())
128}
129
130pub fn handle_call(interpreter: &mut AudioInterpreter, name: &str) -> Result<()> {
131 if let Some(pattern_value) = interpreter.variables.get(name).cloned() {
134 if let Value::Statement(stmt_box) = pattern_value {
135 if let StatementKind::Pattern { target, .. } = &stmt_box.kind {
136 if let Some(tgt) = target.as_ref() {
137 let (pattern_str, options) = interpreter.extract_pattern_data(&stmt_box.value);
138 if let Some(pat) = pattern_str {
139 println!("🎵 Call pattern: {} with {}", name, tgt);
140 interpreter.execute_pattern(tgt.as_str(), &pat, options)?;
141 return Ok(());
142 }
143 }
144 }
145 }
146 }
147
148 if let Some(body) = interpreter.groups.get(name).cloned() {
149 println!("📞 Call group: {}", name);
150 super::collector::collect_events(interpreter, &body)?;
151 } else {
152 println!("⚠️ Warning: Group or pattern '{}' not found", name);
153 }
154
155 Ok(())
156}
157
158pub fn execute_print(interpreter: &AudioInterpreter, value: &Value) -> Result<()> {
159 let message = match value {
160 Value::String(s) => {
161 if s.contains('{') && s.contains('}') { interpreter.interpolate_string(s) } else { s.clone() }
162 }
163 Value::Identifier(id) => {
164 if let Some(v) = interpreter.variables.get(id) {
166 match v {
167 Value::String(s) => s.clone(),
168 Value::Number(n) => n.to_string(),
169 Value::Boolean(b) => b.to_string(),
170 Value::Array(arr) => format!("{:?}", arr),
171 Value::Map(map) => format!("{:?}", map),
172 _ => format!("{:?}", v),
173 }
174 } else {
175 format!("Identifier(\"{}\")", id)
176 }
177 }
178 Value::Number(n) => n.to_string(),
179 Value::Boolean(b) => b.to_string(),
180 Value::Array(arr) => format!("{:?}", arr),
181 Value::Map(map) => format!("{:?}", map),
182 _ => format!("{:?}", value),
183 };
184
185 println!("💬 {}", message);
186 Ok(())
187}
188
189pub fn execute_if(
190 interpreter: &mut AudioInterpreter,
191 condition: &Value,
192 body: &[Statement],
193 else_body: &Option<Vec<Statement>>,
194) -> Result<()> {
195 let condition_result = interpreter.evaluate_condition(condition)?;
196
197 if condition_result {
198 super::collector::collect_events(interpreter, body)?;
199 } else if let Some(else_stmts) = else_body {
200 super::collector::collect_events(interpreter, else_stmts)?;
201 }
202
203 Ok(())
204}
205
206pub fn execute_event_handlers(interpreter: &mut AudioInterpreter, event_name: &str) -> Result<()> {
207 let handlers = interpreter.event_registry.get_handlers_matching(event_name);
208
209 for (index, handler) in handlers.iter().enumerate() {
210 if handler.once && !interpreter.event_registry.should_execute_once(event_name, index) {
211 continue;
212 }
213
214 let body_clone = handler.body.clone();
215 super::collector::collect_events(interpreter, &body_clone)?;
216 }
217
218 Ok(())
219}
220
221pub fn handle_assign(interpreter: &mut AudioInterpreter, target: &str, property: &str, value: &Value) -> Result<()> {
222 if let Some(var) = interpreter.variables.get_mut(target) {
223 if let Value::Map(map) = var {
224 map.insert(property.to_string(), value.clone());
225
226 if interpreter.events.synths.contains_key(target) {
227 let map_clone = map.clone();
228 let updated_def = interpreter.extract_synth_def_from_map(&map_clone)?;
229 interpreter.events.synths.insert(target.to_string(), updated_def);
230 println!("🔧 Updated {}.{} = {:?}", target, property, value);
231 }
232 } else {
233 return Err(anyhow::anyhow!("Cannot assign property '{}' to non-map variable '{}'", property, target));
234 }
235 } else {
236 return Err(anyhow::anyhow!("Variable '{}' not found", target));
237 }
238
239 Ok(())
240}
241
242pub fn extract_synth_def_from_map(_interpreter: &AudioInterpreter, map: &HashMap<String, Value>) -> Result<crate::engine::audio::events::SynthDefinition> {
243 use crate::engine::audio::events::extract_filters;
244
245 let waveform = crate::engine::audio::events::extract_string(map, "waveform", "sine");
246 let attack = crate::engine::audio::events::extract_number(map, "attack", 0.01);
247 let decay = crate::engine::audio::events::extract_number(map, "decay", 0.1);
248 let sustain = crate::engine::audio::events::extract_number(map, "sustain", 0.7);
249 let release = crate::engine::audio::events::extract_number(map, "release", 0.2);
250
251 let synth_type = if let Some(Value::String(t)) = map.get("type") {
252 let clean = t.trim_matches('"').trim_matches('\'');
253 if clean.is_empty() || clean == "synth" { None } else { Some(clean.to_string()) }
254 } else { None };
255
256 let filters = if let Some(Value::Array(filters_arr)) = map.get("filters") { extract_filters(filters_arr) } else { Vec::new() };
257
258 let plugin_author = if let Some(Value::String(s)) = map.get("plugin_author") { Some(s.clone()) } else { None };
259 let plugin_name = if let Some(Value::String(s)) = map.get("plugin_name") { Some(s.clone()) } else { None };
260 let plugin_export = if let Some(Value::String(s)) = map.get("plugin_export") { Some(s.clone()) } else { None };
261
262 let mut options = HashMap::new();
263 for (key, val) in map.iter() {
264 if ![
265 "waveform", "attack", "decay", "sustain", "release", "type", "filters",
266 "plugin_author", "plugin_name", "plugin_export",
267 ].contains(&key.as_str())
268 {
269 if let Value::Number(n) = val {
270 options.insert(key.clone(), *n);
271 } else if let Value::String(s) = val {
272 if key == "waveform" || key.starts_with("_") { continue; }
273 if let Ok(n) = s.parse::<f32>() { options.insert(key.clone(), n); }
274 }
275 }
276 }
277
278 Ok(crate::engine::audio::events::SynthDefinition {
279 waveform,
280 attack,
281 decay,
282 sustain,
283 release,
284 synth_type,
285 filters,
286 options,
287 plugin_author,
288 plugin_name,
289 plugin_export,
290 })
291}
292
293pub fn handle_load(interpreter: &mut AudioInterpreter, source: &str, alias: &str) -> Result<()> {
294 use std::path::Path;
295
296 let path = Path::new(source);
297 if let Some(ext) = path.extension().and_then(|s| s.to_str()).map(|s| s.to_lowercase()) {
299 match ext.as_str() {
300 "mid" | "midi" => {
301 use crate::engine::audio::midi::load_midi_file;
302 let midi_data = load_midi_file(path)?;
303 interpreter.variables.insert(alias.to_string(), midi_data);
304 Ok(())
306 }
307 "wav" | "flac" | "mp3" | "ogg" => {
308 use crate::engine::audio::samples;
310 let registered = samples::register_sample_from_path(path)?;
311 interpreter.variables.insert(alias.to_string(), Value::String(registered.clone()));
313 Ok(())
315 }
316 _ => Err(anyhow::anyhow!("Unsupported file type for @load: {}", ext)),
317 }
318 } else {
319 Err(anyhow::anyhow!("Cannot determine file extension for {}", source))
320 }
321}
322
323pub fn handle_bind(interpreter: &mut AudioInterpreter, source: &str, target: &str, options: &Value) -> Result<()> {
324 let midi_data = interpreter.variables.get(source).ok_or_else(|| anyhow::anyhow!("MIDI source '{}' not found", source))?.clone();
325
326 if let Value::Map(midi_map) = &midi_data {
327 let notes = midi_map.get("notes").ok_or_else(|| anyhow::anyhow!("MIDI data has no notes"))?;
328
329 if let Value::Array(notes_array) = notes {
330 let _synth_def = interpreter.events.synths.get(target).ok_or_else(|| anyhow::anyhow!("Synth '{}' not found", target))?.clone();
331
332 let default_velocity = 100;
333 let mut velocity = default_velocity;
334
335 if let Value::Map(opts) = options {
336 if let Some(Value::Number(v)) = opts.get("velocity") { velocity = *v as u8; }
337 }
338
339 let midi_bpm = crate::engine::audio::events::extract_number(midi_map, "bpm", interpreter.bpm);
342
343 for note_val in notes_array {
344 if let Value::Map(note_map) = note_val {
345 let time = crate::engine::audio::events::extract_number(note_map, "time", 0.0);
346 let note = crate::engine::audio::events::extract_number(note_map, "note", 60.0) as u8;
347 let note_velocity = crate::engine::audio::events::extract_number(note_map, "velocity", velocity as f32) as u8;
348 let duration_ms = crate::engine::audio::events::extract_number(note_map, "duration", 500.0);
350
351 use crate::engine::audio::events::AudioEvent;
352 let synth_def = interpreter.events.get_synth(target).cloned().unwrap_or_default();
353 let interp_bpm = interpreter.bpm;
356 let factor = if interp_bpm > 0.0 { midi_bpm / interp_bpm } else { 1.0 };
357
358 let start_time_s = (time / 1000.0) * factor;
359 let duration_s = (duration_ms / 1000.0) * factor;
360
361 let event = AudioEvent::Note {
362 midi: note,
363 start_time: start_time_s,
364 duration: duration_s,
365 velocity: note_velocity as f32,
366 synth_id: target.to_string(),
367 synth_def,
368 pan: 0.0,
369 detune: 0.0,
370 gain: 1.0,
371 attack: None,
372 release: None,
373 delay_time: None,
374 delay_feedback: None,
375 delay_mix: None,
376 reverb_amount: None,
377 drive_amount: None,
378 drive_color: None,
379 };
380
381 interpreter.events.events.push(event);
384 }
385 }
386
387 }
389 }
390
391 Ok(())
392}
393
394pub fn handle_use_plugin(interpreter: &mut AudioInterpreter, author: &str, name: &str, alias: &str) -> Result<()> {
395 use crate::engine::plugin::loader::load_plugin;
396
397 match load_plugin(author, name) {
398 Ok((info, _wasm_bytes)) => {
399 let mut plugin_map = HashMap::new();
400 plugin_map.insert("_type".to_string(), Value::String("plugin".to_string()));
401 plugin_map.insert("_author".to_string(), Value::String(info.author.clone()));
402 plugin_map.insert("_name".to_string(), Value::String(info.name.clone()));
403
404 if let Some(version) = &info.version { plugin_map.insert("_version".to_string(), Value::String(version.clone())); }
405
406 for export in &info.exports {
407 let mut export_map = HashMap::new();
408 export_map.insert("_plugin_author".to_string(), Value::String(info.author.clone()));
409 export_map.insert("_plugin_name".to_string(), Value::String(info.name.clone()));
410 export_map.insert("_export_name".to_string(), Value::String(export.name.clone()));
411 export_map.insert("_export_kind".to_string(), Value::String(export.kind.clone()));
412
413 plugin_map.insert(export.name.clone(), Value::Map(export_map));
414 }
415
416 interpreter.variables.insert(alias.to_string(), Value::Map(plugin_map));
417 println!("🔌 Plugin loaded: {}.{} as {}", author, name, alias);
418 }
419 Err(e) => {
420 eprintln!("❌ Failed to load plugin {}.{}: {}", author, name, e);
421 return Err(anyhow::anyhow!("Failed to load plugin: {}", e));
422 }
423 }
424
425 Ok(())
426}
427
428pub fn handle_bank(interpreter: &mut AudioInterpreter, name: &str, alias: &Option<String>) -> Result<()> {
429 let target_alias = alias.clone().unwrap_or_else(|| name.split('.').last().unwrap_or(name).to_string());
430
431 if let Some(existing_value) = interpreter.variables.get(name) {
432 interpreter.variables.insert(target_alias.clone(), existing_value.clone());
433 #[cfg(not(feature = "wasm"))]
434 println!("🏦 Bank alias created: {} -> {}", name, target_alias);
435 } else {
436 #[cfg(feature = "wasm")]
437 {
438 use crate::web::registry::banks::REGISTERED_BANKS;
439 REGISTERED_BANKS.with(|banks| {
440 for bank in banks.borrow().iter() {
441 if bank.full_name == *name {
442 if let Some(Value::Map(bank_map)) = interpreter.variables.get(&bank.alias) {
443 interpreter.variables.insert(target_alias.clone(), Value::Map(bank_map.clone()));
444 }
445 }
446 }
447 });
448 }
449
450 #[cfg(not(feature = "wasm"))]
451 {
452 if let Ok(current_dir) = std::env::current_dir() {
453 match interpreter.banks.register_bank(target_alias.clone(), &name, ¤t_dir, ¤t_dir) {
454 Ok(_) => {
455 let mut bank_map = HashMap::new();
456 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
457 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
458 interpreter.variables.insert(target_alias.clone(), Value::Map(bank_map));
459 println!("🏦 Bank registered: {} as {}", name, target_alias);
460 }
461 Err(e) => {
462 eprintln!("⚠️ Failed to register bank '{}': {}", name, e);
463 let mut bank_map = HashMap::new();
464 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
465 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
466 interpreter.variables.insert(target_alias.clone(), Value::Map(bank_map));
467 }
468 }
469 } else {
470 let mut bank_map = HashMap::new();
471 bank_map.insert("_name".to_string(), Value::String(name.to_string()));
472 bank_map.insert("_alias".to_string(), Value::String(target_alias.clone()));
473 interpreter.variables.insert(target_alias.clone(), Value::Map(bank_map));
474 eprintln!("⚠️ Could not determine cwd to register bank '{}', registered minimal alias.", name);
475 }
476 }
477 }
478
479 #[cfg(not(feature = "wasm"))]
480 Ok(())
483}
484
485pub fn handle_trigger(interpreter: &mut AudioInterpreter, entity: &str) -> Result<()> {
486 let resolved_entity = if entity.starts_with('.') { &entity[1..] } else { entity };
487
488 if resolved_entity.contains('.') {
489 let parts: Vec<&str> = resolved_entity.split('.').collect();
490 if parts.len() == 2 {
491 let (var_name, property) = (parts[0], parts[1]);
492
493 if let Some(Value::Map(map)) = interpreter.variables.get(var_name) {
494 if let Some(Value::String(sample_uri)) = map.get(property) {
495 let uri = sample_uri.trim_matches('"').trim_matches('\'');
496 println!("🎵 Trigger: {}.{} -> {} at {:.3}s", var_name, property, uri, interpreter.cursor_time);
497 interpreter.events.add_sample_event(uri, interpreter.cursor_time, 1.0);
498 let beat_duration = interpreter.beat_duration();
499 interpreter.cursor_time += beat_duration;
500 } else {
501 #[cfg(not(feature = "wasm"))]
502 {
503 println!("🔍 Attempting to resolve trigger: {}", resolved_entity);
504 let resolved_uri = interpreter.resolve_sample_uri(resolved_entity);
506 if resolved_uri != resolved_entity {
507 println!("🎵 Resolved via variable: {} -> {}", resolved_entity, resolved_uri);
508 interpreter.events.add_sample_event(&resolved_uri, interpreter.cursor_time, 1.0);
509 let beat_duration = interpreter.beat_duration();
510 interpreter.cursor_time += beat_duration;
511 } else if let Some(pathbuf) = interpreter.banks.resolve_trigger(var_name, property) {
512 if let Some(path_str) = pathbuf.to_str() {
513 println!("🎵 Resolved (file): {}.{} -> {}", var_name, property, path_str);
514 interpreter.events.add_sample_event(path_str, interpreter.cursor_time, 1.0);
515 let beat_duration = interpreter.beat_duration();
516 interpreter.cursor_time += beat_duration;
517 } else {
518 println!("⚠️ Resolution failed for {}.{} (invalid path)", var_name, property);
519 }
520 } else {
521 println!("⚠️ No path found for {} via BankRegistry", resolved_entity);
522 }
523 }
524 }
525 }
526 }
527 } else {
528 if let Some(Value::String(sample_uri)) = interpreter.variables.get(resolved_entity) {
529 let uri = sample_uri.trim_matches('"').trim_matches('\'');
530 println!("🎵 Trigger: {} -> {} at {:.3}s", resolved_entity, uri, interpreter.cursor_time);
531 interpreter.events.add_sample_event(uri, interpreter.cursor_time, 1.0);
532 let beat_duration = interpreter.beat_duration();
533 interpreter.cursor_time += beat_duration;
534 }
535 }
536
537 println!("🔄 Trigger interpreted: {}", entity);
538 #[cfg(not(feature = "wasm"))]
539 {
540 println!("🔍 Déclencheurs disponibles dans BankRegistry:");
541 for (bank_name, bank) in interpreter.banks.list_banks() {
542 println!(" Banque: {}", bank_name);
543 for trigger in bank.list_triggers() {
544 println!(" Déclencheur: {}", trigger);
545 }
546 }
547 }
548
549 Ok(())
553}
554
555pub fn extract_pattern_data(_interpreter: &AudioInterpreter, value: &Value) -> (Option<String>, Option<HashMap<String, f32>>) {
556 match value {
557 Value::String(pattern) => (Some(pattern.clone()), None),
558 Value::Map(map) => {
559 let pattern = map.get("pattern").and_then(|v| {
560 if let Value::String(s) = v { Some(s.clone()) } else { None }
561 });
562
563 let mut options = HashMap::new();
564 for (key, val) in map.iter() {
565 if key != "pattern" {
566 if let Value::Number(num) = val { options.insert(key.clone(), *num); }
567 }
568 }
569
570 let opts = if options.is_empty() { None } else { Some(options) };
571 (pattern, opts)
572 }
573 _ => (None, None),
574 }
575}
576
577pub fn execute_pattern(interpreter: &mut AudioInterpreter, target: &str, pattern: &str, options: Option<HashMap<String, f32>>) -> Result<()> {
578 use crate::engine::audio::events::AudioEvent;
579
580 let swing = options.as_ref().and_then(|o| o.get("swing").copied()).unwrap_or(0.0);
581 let humanize = options.as_ref().and_then(|o| o.get("humanize").copied()).unwrap_or(0.0);
582 let velocity_mult = options.as_ref().and_then(|o| o.get("velocity").copied()).unwrap_or(1.0);
583 let tempo_override = options.as_ref().and_then(|o| o.get("tempo").copied());
584
585 let effective_bpm = tempo_override.unwrap_or(interpreter.bpm);
586
587 let resolved_uri = resolve_sample_uri(interpreter, target);
588
589 let pattern_chars: Vec<char> = pattern.chars().filter(|c| !c.is_whitespace()).collect();
590 let step_count = pattern_chars.len() as f32;
591 if step_count == 0.0 { return Ok(()); }
592
593 let bar_duration = (60.0 / effective_bpm) * 4.0;
594 let step_duration = bar_duration / step_count;
595
596 for (i, &ch) in pattern_chars.iter().enumerate() {
597 if ch == 'x' || ch == 'X' {
598 let mut time = interpreter.cursor_time + (i as f32 * step_duration);
599 if swing > 0.0 && i % 2 == 1 { time += step_duration * swing; }
600
601 #[cfg(any(feature = "cli", feature = "wasm"))]
602 if humanize > 0.0 {
603 use rand::Rng;
604 let mut rng = rand::thread_rng();
605 let offset = rng.gen_range(-humanize..humanize);
606 time += offset;
607 }
608
609 let event = AudioEvent::Sample { uri: resolved_uri.clone(), start_time: time, velocity: 100.0 * velocity_mult };
610 interpreter.events.events.push(event);
611 }
612 }
613
614 interpreter.cursor_time += bar_duration;
615 Ok(())
616}
617
618pub fn resolve_sample_uri(interpreter: &AudioInterpreter, target: &str) -> String {
619 if let Some(dot_pos) = target.find('.') {
620 let bank_alias = &target[..dot_pos];
621 let trigger_name = &target[dot_pos + 1..];
622 if let Some(Value::Map(bank_map)) = interpreter.variables.get(bank_alias) {
623 if let Some(Value::String(bank_name)) = bank_map.get("_name") {
624 return format!("devalang://bank/{}/{}", bank_name, trigger_name);
625 }
626 }
627 }
628 target.to_string()
629}