devalang_core/core/preprocessor/resolver/
value.rs

1use std::collections::HashMap;
2use toml::Value as TomlValue;
3
4use crate::core::{
5    preprocessor::{module::Module, resolver::driver::resolve_statement},
6    store::global::GlobalStore,
7};
8
9use devalang_types::Value;
10
11fn find_export_value(name: &str, global_store: &GlobalStore) -> Option<Value> {
12    for module in global_store.modules.values() {
13        if let Some(val) = module.export_table.get_export(name) {
14            return Some(val.clone());
15        }
16    }
17
18    None
19}
20
21pub fn resolve_value(value: &Value, module: &Module, global_store: &mut GlobalStore) -> Value {
22    match value {
23        Value::String(s) => {
24            // Keep raw strings as-is; they may be runtime-evaluated (e.g., expressions)
25            Value::String(s.clone())
26        }
27
28        Value::Identifier(name) => {
29            if let Some(original_val) = module.variable_table.get(name) {
30                return resolve_value(original_val, module, global_store);
31            }
32
33            if let Some(export_val) = find_export_value(name, global_store) {
34                return resolve_value(&export_val, module, global_store);
35            }
36
37            // Leave unresolved identifiers as-is; may be runtime-bound (e.g., foreach variable)
38            Value::Identifier(name.clone())
39        }
40
41        Value::Map(map) => {
42            if let Some(Value::Identifier(entity)) = map.get("entity") {
43                // SECTION Synth
44                if entity == "synth" {
45                    if let Some(Value::Map(synth_data)) = map.get("value") {
46                        let resolved_waveform = synth_data
47                            .get("waveform")
48                            .map(|wf| resolve_value(wf, module, global_store))
49                            .unwrap_or(Value::Null);
50
51                        let resolved_params = synth_data
52                            .get("parameters")
53                            .map(|p| resolve_value(p, module, global_store))
54                            .unwrap_or(Value::Map(HashMap::new()));
55
56                        // If waveform refers to a plugin synth (e.g., alias.synth),
57                        // merge plugin-exported defaults (dynamic) into parameters and
58                        // allow 'waveform' override from parameters map.
59                        let mut final_waveform = resolved_waveform.clone();
60                        let mut params_map = match resolved_params.clone() {
61                            Value::Map(m) => m,
62                            _ => HashMap::new(),
63                        };
64
65                        // Helper: convert TomlValue into runtime Value
66                        fn toml_to_value(tv: &TomlValue) -> Value {
67                            match tv {
68                                TomlValue::String(s) => Value::String(s.clone()),
69                                TomlValue::Integer(i) => Value::Number(*i as f32),
70                                TomlValue::Float(f) => Value::Number(*f as f32),
71                                TomlValue::Boolean(b) => Value::Boolean(*b),
72                                TomlValue::Array(arr) => {
73                                    Value::Array(arr.iter().map(toml_to_value).collect())
74                                }
75                                TomlValue::Table(t) => {
76                                    let mut m = HashMap::new();
77                                    for (k, v) in t.iter() {
78                                        m.insert(k.clone(), toml_to_value(v));
79                                    }
80                                    Value::Map(m)
81                                }
82                                _ => Value::Null,
83                            }
84                        }
85
86                        // Detect plugin alias from waveform string like "alias.synth" OR just "alias"
87                        let (alias_opt, explicit_synth_export) = match &final_waveform {
88                            Value::String(s) | Value::Identifier(s) => {
89                                let parts: Vec<&str> = s.split('.').collect();
90                                if parts.len() >= 2 && parts[1] == "synth" {
91                                    (Some(parts[0].to_string()), true)
92                                } else if parts.len() == 1 {
93                                    (Some(parts[0].to_string()), false)
94                                } else {
95                                    (None, false)
96                                }
97                            }
98                            _ => (None, false),
99                        };
100
101                        if let Some(alias) = alias_opt {
102                            // Resolve alias -> plugin uri -> plugin info
103                            if let Some(Value::String(uri)) = module.variable_table.get(&alias) {
104                                if let Some(id) = uri.strip_prefix("devalang://plugin/") {
105                                    let mut parts = id.split('.');
106                                    let author = parts.next().unwrap_or("");
107                                    let pname = parts.next().unwrap_or("");
108                                    let key = format!("{}:{}", author, pname);
109                                    if let Some((plugin_info, _wasm)) =
110                                        global_store.plugins.get(&key)
111                                    {
112                                        // Merge defaults dynamically from exports
113                                        for exp in &plugin_info.exports {
114                                            // Skip entry named 'synth' which is used as the flag
115                                            if exp.name == "synth" {
116                                                continue;
117                                            }
118                                            if let Some(def) = &exp.default {
119                                                let val = toml_to_value(def);
120                                                // only apply if not overridden by user params
121                                                params_map.entry(exp.name.clone()).or_insert(val);
122                                            }
123                                        }
124
125                                        // If 'waveform' is provided in params (by user or default), use it
126                                        if let Some(wf_val) = params_map.remove("waveform") {
127                                            final_waveform =
128                                                resolve_value(&wf_val, module, global_store);
129                                        } else if let Some(wf_default) = plugin_info
130                                            .exports
131                                            .iter()
132                                            .find(|e| e.name == "waveform")
133                                            .and_then(|e| e.default.as_ref())
134                                        {
135                                            final_waveform = toml_to_value(wf_default);
136                                        } else if explicit_synth_export {
137                                            // keep as alias.synth if no default waveform
138                                        } else {
139                                            // If no explicit .synth in waveform, but alias is a plugin,
140                                            // treat it as alias.synth by default to enable plugin synth usage
141                                            final_waveform =
142                                                Value::String(format!("{}.synth", alias));
143                                        }
144                                    }
145                                }
146                            }
147                        }
148
149                        let mut result = HashMap::new();
150                        result.insert("waveform".to_string(), final_waveform);
151                        result.insert("parameters".to_string(), Value::Map(params_map));
152
153                        return Value::Map(result);
154                    }
155                }
156            }
157
158            let mut resolved = HashMap::new();
159            for (k, v) in map {
160                resolved.insert(k.clone(), resolve_value(v, module, global_store));
161            }
162
163            Value::Map(resolved)
164        }
165
166        Value::Block(stmts) => {
167            let resolved_stmts = stmts
168                .iter()
169                .map(|stmt| resolve_statement(stmt, module, &module.path, global_store))
170                .collect();
171            Value::Block(resolved_stmts)
172        }
173
174        other => other.clone(),
175    }
176}