helix/dna/hel/
dna_hlx.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use crate::hel::error::HlxError;
4use crate::dna::atp::value::Value;
5use crate::hel::dispatch::{HelixDispatcher, DispatchResult};
6use crate::HelixConfig;
7use crate::ops::engine::OperatorEngine;
8
9/// Trait for converting Rust types into DnaValue
10pub trait IntoValue {
11    fn into_value(self) -> Value;
12}
13
14impl IntoValue for Value {
15    fn into_value(self) -> Value {
16        self
17    }
18}
19
20impl IntoValue for &str {
21    fn into_value(self) -> Value {
22        Value::String(self.to_string())
23    }
24}
25
26impl IntoValue for String {
27    fn into_value(self) -> Value {
28        Value::String(self)
29    }
30}
31
32impl IntoValue for &String {
33    fn into_value(self) -> Value {
34        Value::String(self.clone())
35    }
36}
37
38impl IntoValue for bool {
39    fn into_value(self) -> Value {
40        Value::Bool(self)
41    }
42}
43
44impl IntoValue for i8 {
45    fn into_value(self) -> Value {
46        Value::Number(self as f64)
47    }
48}
49
50impl IntoValue for i16 {
51    fn into_value(self) -> Value {
52        Value::Number(self as f64)
53    }
54}
55
56impl IntoValue for i32 {
57    fn into_value(self) -> Value {
58        Value::Number(self as f64)
59    }
60}
61
62impl IntoValue for i64 {
63    fn into_value(self) -> Value {
64        Value::Number(self as f64)
65    }
66}
67
68impl IntoValue for u8 {
69    fn into_value(self) -> Value {
70        Value::Number(self as f64)
71    }
72}
73
74impl IntoValue for u16 {
75    fn into_value(self) -> Value {
76        Value::Number(self as f64)
77    }
78}
79
80impl IntoValue for u32 {
81    fn into_value(self) -> Value {
82        Value::Number(self as f64)
83    }
84}
85
86impl IntoValue for u64 {
87    fn into_value(self) -> Value {
88        Value::Number(self as f64)
89    }
90}
91
92impl IntoValue for f32 {
93    fn into_value(self) -> Value {
94        Value::Number(self as f64)
95    }
96}
97
98impl IntoValue for f64 {
99    fn into_value(self) -> Value {
100        Value::Number(self)
101    }
102}
103
104// Array support for Vec of IntoValue types
105impl<T: IntoValue> IntoValue for Vec<T> {
106    fn into_value(self) -> Value {
107        Value::Array(self.into_iter().map(|v| v.into_value()).collect())
108    }
109}
110
111pub struct Hlx {
112    pub config: Option<HelixConfig>,
113    pub data: HashMap<String, HashMap<String, Value>>,
114    pub file_path: Option<PathBuf>,
115    pub dispatcher: HelixDispatcher,
116    pub operator_engine: OperatorEngine,
117}
118impl Hlx {
119    pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self, HlxError> {
120        let path = path.as_ref().to_path_buf();
121        let mut hlx = Self {
122            config: None,
123            data: HashMap::new(),
124            file_path: Some(path.clone()),
125            dispatcher: HelixDispatcher::new(),
126            operator_engine: OperatorEngine::new().await?,
127        };
128        hlx.dispatcher.initialize().await?;
129        if path.extension().and_then(|s| s.to_str()) == Some("hlxb") {
130            #[cfg(feature = "compiler")]
131            {
132                let loader = crate::mds::loader::BinaryLoader::new();
133                let binary = loader
134                    .load_file(&path)
135                    .map_err(|e| HlxError::compilation_error(
136                        format!("Failed to load binary: {:?}", e),
137                        "Ensure file is a valid HLXB file",
138                    ))?;
139                // Convert binary to config if needed
140                hlx.config = Some(crate::HelixConfig::default());
141            }
142            #[cfg(not(feature = "compiler"))]
143            {
144                return Err(
145                    HlxError::compilation_error(
146                        "Binary file support not available",
147                        "Compile with 'compiler' feature enabled",
148                    ),
149                );
150            }
151        } else {
152            let content = std::fs::read_to_string(&path)
153                .map_err(|e| HlxError::io_error(
154                    format!("Failed to read file: {}", e),
155                    "Ensure file exists and is readable",
156                ))?;
157            match hlx.dispatcher.parse_and_execute(&content).await? {
158                DispatchResult::Executed(value) => {
159                    if let Value::Object(obj) = value {
160                        for (section, section_data) in obj {
161                            if let Value::Object(section_obj) = section_data {
162                                let mut section_map = HashMap::new();
163                                for (key, val) in section_obj {
164                                    section_map.insert(key, val);
165                                }
166                                hlx.data.insert(section, section_map);
167                            }
168                        }
169                    }
170                }
171                DispatchResult::Parsed(ast) => {
172                    hlx.config = Some(
173                        crate::ast_to_config(ast)
174                            .map_err(|e| HlxError::config_conversion(
175                                "conversion".to_string(),
176                                e,
177                            ))?,
178                    );
179                }
180                _ => {}
181            }
182        }
183        Ok(hlx)
184    }
185    pub async fn new() -> Result<Self, HlxError> {
186        Ok(Self {
187            config: None,
188            data: HashMap::new(),
189            file_path: None,
190            dispatcher: HelixDispatcher::new(),
191            operator_engine: OperatorEngine::new().await?,
192        })
193    }
194    pub fn get(&self, section: &str, key: &str) -> Option<&Value> {
195        self.data.get(section)?.get(key)
196    }
197    /// Set a value in a section - automatically converts Rust types to DnaValue
198    /// 
199    /// # Examples
200    /// ```
201    /// // String values
202    /// hlx.set("project", "name", "MyProject");
203    /// hlx.set("project", "version", String::from("1.0.0"));
204    /// 
205    /// // Numeric values
206    /// hlx.set("config", "port", 8080);
207    /// hlx.set("config", "timeout", 30.5);
208    /// 
209    /// // Boolean values
210    /// hlx.set("features", "debug", true);
211    /// 
212    /// // Explicit DnaValue for complex types
213    /// hlx.set("data", "items", DnaValue::Array(vec![
214    ///     DnaValue::String("item1".to_string()),
215    ///     DnaValue::String("item2".to_string()),
216    /// ]));
217    /// ```
218    pub fn set<T: IntoValue>(&mut self, section: &str, key: &str, value: T) {
219        self.data
220            .entry(section.to_string())
221            .or_insert_with(HashMap::new)
222            .insert(key.to_string(), value.into_value());
223    }
224    
225    // Keep old method names for backward compatibility (delegates to new set)
226    pub fn set_str(&mut self, section: &str, key: &str, value: &str) {
227        self.set(section, key, value);
228    }
229    
230    pub fn set_num(&mut self, section: &str, key: &str, value: f64) {
231        self.set(section, key, value);
232    }
233    
234    pub fn set_bool(&mut self, section: &str, key: &str, value: bool) {
235        self.set(section, key, value);
236    }
237    
238    /// Increase a numeric value by the specified amount
239    /// If the key doesn't exist, it will be initialized to 0 + amount
240    /// If the value is not a number, it will be converted to 0 + amount
241    pub fn increase(&mut self, section: &str, key: &str, amount: f64) -> Result<f64, HlxError> {
242        let current_value = self.get(section, key)
243            .and_then(|v| v.as_number())
244            .unwrap_or(0.0);
245        
246        let new_value = current_value + amount;
247        
248        self.set(section, key, Value::Number(new_value));
249        Ok(new_value)
250    }
251    pub fn index(&self, section: &str) -> Option<&HashMap<String, Value>> {
252        self.data.get(section)
253    }
254    pub fn index_mut(&mut self, section: &str) -> Option<&mut HashMap<String, Value>> {
255        self.data.get_mut(section)
256    }
257    pub async fn server(&mut self) -> Result<(), HlxError> {
258        if self.dispatcher.is_ready() {
259            Ok(())
260        } else {
261            self.dispatcher.initialize().await
262        }
263    }
264    pub async fn watch(&mut self) -> Result<(), HlxError> {
265        #[cfg(feature = "compiler")]
266        {
267            if let Some(path) = &self.file_path {
268                println!("Watching {} for changes...", path.display());
269                Ok(())
270            } else {
271                Err(
272                    HlxError::invalid_input(
273                        "No file loaded for watching",
274                        "Load a file first with Hlx::load()",
275                    ),
276                )
277            }
278        }
279        #[cfg(not(feature = "compiler"))]
280        {
281            Err(
282                HlxError::compilation_error(
283                    "Watch mode not available",
284                    "Compile with 'compiler' feature enabled",
285                ),
286            )
287        }
288    }
289    pub async fn process(&mut self) -> Result<(), HlxError> {
290        if let Some(path) = &self.file_path {
291            let content = std::fs::read_to_string(path)
292                .map_err(|e| HlxError::io_error(
293                    format!("Failed to read file: {}", e),
294                    "Ensure file exists and is readable",
295                ))?;
296            match self.dispatcher.parse_and_execute(&content).await? {
297                DispatchResult::Executed(value) => {
298                    println!("Processed successfully: {:?}", value);
299                    Ok(())
300                }
301                _ => Ok(()),
302            }
303        } else {
304            Err(
305                HlxError::invalid_input(
306                    "No file loaded for processing",
307                    "Load a file first with Hlx::load()",
308                ),
309            )
310        }
311    }
312    pub async fn compile(&mut self) -> Result<(), HlxError> {
313        #[cfg(feature = "compiler")]
314        {
315            if let Some(path) = &self.file_path {
316                use crate::dna::compiler::{Compiler, OptimizationLevel};
317                let compiler = Compiler::builder()
318                    .optimization_level(OptimizationLevel::Two)
319                    .compression(true)
320                    .cache(true)
321                    .verbose(false)
322                    .build();
323                let binary = compiler
324                    .compile_file(path)
325                    .map_err(|e| HlxError::compilation_error(
326                        format!("Compilation failed: {}", e),
327                        "Check file syntax and try again",
328                    ))?;
329                let binary_path = path.with_extension("hlxb");
330                let serializer = crate::mds::serializer::BinarySerializer::new(true);
331                serializer
332                    .write_to_file(&binary, &binary_path)
333                    .map_err(|e| HlxError::io_error(
334                        format!("Failed to write binary file: {}", e),
335                        "Ensure output directory is writable",
336                    ))?;
337                println!(
338                    "✅ Successfully compiled {} to {}", path.display(), binary_path
339                    .display()
340                );
341                Ok(())
342            } else {
343                Err(
344                    HlxError::invalid_input(
345                        "No file loaded for compilation",
346                        "Load a file first with Hlx::load()",
347                    ),
348                )
349            }
350        }
351        #[cfg(not(feature = "compiler"))]
352        {
353            Err(
354                HlxError::compilation_error(
355                    "Compilation not available",
356                    "Compile with 'compiler' feature enabled",
357                ),
358            )
359        }
360    }
361    pub async fn execute(&mut self, code: &str) -> Result<Value, HlxError> {
362        if !self.dispatcher.is_ready() {
363            self.dispatcher.initialize().await?;
364        }
365        match self.dispatcher.parse_and_execute(code).await {
366            Ok(DispatchResult::Executed(value)) => Ok(value),
367            Ok(DispatchResult::ParseError(err)) => {
368                Err(
369                    HlxError::invalid_input(
370                        format!("Parse error: {}", err),
371                        "Check syntax",
372                    ),
373                )
374            }
375            Ok(DispatchResult::ExecutionError(err)) => Err(err),
376            Ok(DispatchResult::Parsed(_)) => {
377                Err(
378                    HlxError::execution_error(
379                        "Parsed but not executed",
380                        "Use process() for file processing",
381                    ),
382                )
383            }
384            Err(e) => Err(e),
385        }
386    }
387    pub async fn execute_operator(
388        &self,
389        operator: &str,
390        params: &str,
391    ) -> Result<Value, HlxError> {
392        self.operator_engine.execute_operator(operator, params).await
393    }
394    pub fn sections(&self) -> Vec<&String> {
395        self.data.keys().collect()
396    }
397    pub fn keys(&self, section: &str) -> Option<Vec<&String>> {
398        self.data.get(section).map(|s| s.keys().collect())
399    }
400    pub fn save(&self) -> Result<(), HlxError> {
401        if let Some(path) = &self.file_path {
402            let mut content = String::new();
403            
404            // Generate proper HLX format with colon/semicolon syntax
405            for (section, keys) in &self.data {
406                // Use section name directly with colon syntax
407                content.push_str(&format!("{} :\n", section));
408                
409                for (key, value) in keys {
410                    // Format value appropriately
411                    let formatted_value = match value {
412                        Value::String(s) => format!("\"{}\"", s),
413                        Value::Number(n) => n.to_string(),
414                        Value::Bool(b) => b.to_string(),
415                        Value::Array(arr) => {
416                            let items: Vec<String> = arr.iter().map(|v| {
417                                match v {
418                                    Value::String(s) => format!("\"{}\"", s),
419                                    Value::Number(n) => n.to_string(),
420                                    Value::Bool(b) => b.to_string(),
421                                    _ => format!("{}", v),
422                                }
423                            }).collect();
424                            format!("[{}]", items.join(", "))
425                        },
426                        Value::Object(obj) => {
427                            let pairs: Vec<String> = obj.iter().map(|(k, v)| {
428                                format!("{} = {}", k, v)
429                            }).collect();
430                            format!("{{\n        {}\n    }}", pairs.join("\n        "))
431                        },
432                        _ => format!("{}", value),
433                    };
434                    
435                    content.push_str(&format!("    {} = {}\n", key, formatted_value));
436                }
437                
438                content.push_str(";\n\n");
439            }
440            
441            std::fs::write(path, content)
442                .map_err(|e| HlxError::io_error(
443                    format!("Failed to save file: {}", e),
444                    "Ensure write permissions",
445                ))
446        } else {
447            Err(
448                HlxError::invalid_input(
449                    "No file path set",
450                    "Load a file first or set file_path manually",
451                ),
452            )
453        }
454    }
455
456    /// Generate HLX content as a string without writing to file
457    /// 
458    /// # Example
459    /// ```
460    /// let mut hlx = Hlx::new().await?;
461    /// hlx.set("project", "name", Value::String("MyProject".to_string()));
462    /// hlx.set("project", "version", Value::String("1.0.0".to_string()));
463    /// let content = hlx.make()?;
464    /// println!("Generated HLX content:\n{}", content);
465    /// ```
466    pub fn make(&self) -> Result<String, HlxError> {
467        let mut content = String::new();
468        
469        // Generate proper HLX format with colon/semicolon syntax
470        for (section, keys) in &self.data {
471            // Use section name directly with colon syntax
472            content.push_str(&format!("{} :\n", section));
473            
474            for (key, value) in keys {
475                // Format value appropriately
476                let formatted_value = match value {
477                    Value::String(s) => format!("\"{}\"", s),
478                    Value::Number(n) => n.to_string(),
479                    Value::Bool(b) => b.to_string(),
480                    Value::Array(arr) => {
481                        let items: Vec<String> = arr.iter().map(|v| {
482                            match v {
483                                Value::String(s) => format!("\"{}\"", s),
484                                Value::Number(n) => n.to_string(),
485                                Value::Bool(b) => b.to_string(),
486                                _ => format!("{}", v),
487                            }
488                        }).collect();
489                        format!("[{}]", items.join(", "))
490                    },
491                    Value::Object(obj) => {
492                        let pairs: Vec<String> = obj.iter().map(|(k, v)| {
493                            format!("{} = {}", k, v)
494                        }).collect();
495                        format!("{{\n        {}\n    }}", pairs.join("\n        "))
496                    },
497                    _ => format!("{}", value),
498                };
499                
500                content.push_str(&format!("    {} = {}\n", key, formatted_value));
501            }
502            
503            content.push_str(";\n\n");
504        }
505        
506        Ok(content)
507    }
508}
509impl std::ops::Index<&str> for Hlx {
510    type Output = HashMap<String, Value>;
511    fn index(&self, section: &str) -> &Self::Output {
512        self.data
513            .get(section)
514            .unwrap_or_else(|| panic!("Section '{}' not found", section))
515    }
516}
517impl std::ops::IndexMut<&str> for Hlx {
518    fn index_mut(&mut self, section: &str) -> &mut Self::Output {
519        self.data.entry(section.to_string()).or_insert_with(HashMap::new)
520    }
521}
522pub mod test_operators {
523    use super::*;
524    pub async fn test_fundamental_operators() -> Result<(), HlxError> {
525        let mut hlx = Hlx::new().await?;
526        println!("Testing fundamental operators...");
527        let result = hlx.execute(r#"@var(name="test_var", value="hello")"#).await?;
528        println!("@var result: {:?}", result);
529        let result = hlx.execute(r#"@env(key="HOME")"#).await?;
530        println!("@env result: {:?}", result);
531        let result = hlx.execute(r#"@date("Y-m-d")"#).await?;
532        println!("@date result: {:?}", result);
533        let result = hlx.execute(r#"@time("H:i:s")"#).await?;
534        println!("@time result: {:?}", result);
535        let result = hlx.execute("@uuid()").await?;
536        println!("@uuid result: {:?}", result);
537        let result = hlx.execute(r#"@string("hello world", "upper")"#).await?;
538        println!("@string result: {:?}", result);
539        let result = hlx.execute(r#"@math("5 + 3")"#).await?;
540        println!("@math result: {:?}", result);
541        let result = hlx.execute(r#"@calc("a = 10; b = 5; a + b")"#).await?;
542        println!("@calc result: {:?}", result);
543        let result = hlx
544            .execute(r#"@if(condition="true", then="yes", else="no")"#)
545            .await?;
546        println!("@if result: {:?}", result);
547        let result = hlx
548            .execute(r#"@array(values="[1,2,3]", operation="length")"#)
549            .await?;
550        println!("@array result: {:?}", result);
551        let result = hlx.execute(r#"@json('{"name":"test"}', "parse")"#).await?;
552        println!("@json result: {:?}", result);
553        let result = hlx.execute(r#"@base64("hello", "encode")"#).await?;
554        println!("@base64 result: {:?}", result);
555        let result = hlx.execute(r#"@hash("password", "sha256")"#).await?;
556        println!("@hash result: {:?}", result);
557        println!("All fundamental operators tested successfully!");
558        Ok(())
559    }
560    pub async fn test_conditional_operators() -> Result<(), HlxError> {
561        let mut hlx = Hlx::new().await?;
562        println!("Testing conditional operators...");
563        let result = hlx
564            .execute(r#"@if(condition="@math('5 > 3')", then="greater", else="less")"#)
565            .await?;
566        println!("@if with expression: {:?}", result);
567        let result = hlx
568            .execute(
569                r#"@switch(value="2", cases="{'1':'one','2':'two','3':'three'}", default="unknown")"#,
570            )
571            .await?;
572        println!("@switch result: {:?}", result);
573        let result = hlx
574            .execute(r#"@filter(array="[1,2,3,4,5]", condition="@math('value > 3')")"#)
575            .await?;
576        println!("@filter result: {:?}", result);
577        let result = hlx
578            .execute(r#"@map(array="[1,2,3]", transform="@math('value * 2')")"#)
579            .await?;
580        println!("@map result: {:?}", result);
581        let result = hlx
582            .execute(
583                r#"@reduce(array="[1,2,3,4]", initial="0", operation="@math('acc + value')")"#,
584            )
585            .await?;
586        println!("@reduce result: {:?}", result);
587        println!("All conditional operators tested successfully!");
588        Ok(())
589    }
590}
591#[cfg(test)]
592mod tests {
593    use super::*;
594    #[tokio::test]
595    async fn test_hlx_interface() {
596        let mut hlx = Hlx::new().await.unwrap();
597        hlx.data.insert("database".to_string(), HashMap::new());
598        hlx.index_mut("database")
599            .unwrap()
600            .insert(
601                "host".to_string(),
602                crate::dna::atp::value::Value::String("localhost".to_string()),
603            );
604        hlx.index_mut("database")
605            .unwrap()
606            .insert("port".to_string(), crate::dna::atp::value::Value::Number(5432.0));
607        assert_eq!(
608            hlx.get("database", "host"), Some(& crate::dna::atp::value::Value::String("localhost"
609            .to_string()))
610        );
611        assert_eq!(hlx.get("database", "port"), Some(& Value::Number(5432.0)));
612        let sections = hlx.sections();
613        assert!(sections.iter().any(| s | * s == "database"));
614        let keys = hlx.keys("database").unwrap();
615        assert!(keys.iter().any(| k | * k == "host"));
616    }
617    #[tokio::test]
618    async fn test_operator_execution() {
619        let hlx = Hlx::new().await.unwrap();
620        let result = hlx.execute_operator("date", "{\"format\":\"Y-m-d\"}").await;
621        println!("Direct operator execution result: {:?}", result);
622        assert!(result.is_ok());
623        let result = hlx.execute_operator("uuid", "").await;
624        println!("UUID operator execution result: {:?}", result);
625        assert!(result.is_ok());
626        let result = hlx.execute_operator("nonexistent", "{}").await;
627        println!("Invalid operator result: {:?}", result);
628        assert!(result.is_err());
629    }
630    #[tokio::test]
631    async fn test_operator_integration() {
632        use crate::ops::OperatorParser;
633        let mut ops_parser = OperatorParser::new().await;
634        let result = ops_parser.parse_value("@date(\"Y-m-d\")").await.unwrap();
635        match result {
636            crate::dna::atp::value::Value::String(date_str) => {
637                assert!(! date_str.is_empty());
638                println!("✅ @date operator working: {}", date_str);
639            }
640            _ => panic!("Expected string result from @date"),
641        }
642        let result = ops_parser.parse_value("@uuid()").await.unwrap();
643        match result {
644            crate::dna::atp::value::Value::String(uuid_str) => {
645                assert!(! uuid_str.is_empty());
646                println!(
647                    "✅ @uuid operator working: {} (length: {})", uuid_str, uuid_str
648                    .len()
649                );
650            }
651            _ => panic!("Expected string result from @uuid"),
652        }
653        use dna::ops::OperatorEngine;
654        let operator_engine = OperatorEngine::new().await.unwrap();
655        let result = operator_engine
656            .execute_operator("date", "{\"format\":\"%Y-%m-%d\"}")
657            .await
658            .unwrap();
659        match result {
660            crate::dna::atp::value::Value::String(date_str) => {
661                assert!(! date_str.is_empty());
662                println!("✅ Direct date operator working: {}", date_str);
663            }
664            _ => panic!("Expected string result from direct date operator"),
665        }
666        let result = operator_engine.execute_operator("uuid", "").await.unwrap();
667        match result {
668            crate::dna::atp::value::Value::String(uuid_str) => {
669                assert!(! uuid_str.is_empty());
670                println!(
671                    "✅ Direct uuid operator working: {} (length: {})", uuid_str,
672                    uuid_str.len()
673                );
674            }
675            _ => panic!("Expected string result from direct uuid operator"),
676        }
677        println!("✅ ops.rs and operators/ integration fully working!");
678    }
679    #[tokio::test]
680    async fn test_comprehensive_operator_testing() {
681        assert!(true);
682    }
683}