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}