helix/
lib.rs

1pub mod dna {
2    pub mod atp;
3    pub mod bch;
4    pub mod cmd;
5    pub mod exp;
6    pub mod ffi;
7    pub mod hel;
8    pub mod map;
9    pub mod mds;
10    pub mod ngs;
11    pub mod ops;
12    pub mod out;
13    pub mod tst;
14    pub mod compiler;
15}
16pub use dna::atp;
17pub use dna::bch;
18pub use dna::cmd;
19pub use dna::exp;
20pub use dna::ffi;
21pub use dna::hel;
22pub use dna::map;
23pub use dna::mds;
24pub use dna::ngs;
25pub use dna::ops;
26pub use dna::out;
27pub use dna::tst;
28pub use dna::compiler;
29pub use dna::compiler::Compiler;
30pub use dna::mds::optimizer::OptimizationLevel;
31#[cfg(test)]
32mod tests;
33#[cfg(test)]
34mod bch;
35
36#[cfg(test)]
37#[path = "dna/tst/integration_tests.rs"]
38mod integration_tests;
39
40pub use crate::dna::atp::types::{
41    HelixConfig, ProjectConfig, AgentConfig, WorkflowConfig, MemoryConfig, ContextConfig,
42    CrewConfig, PipelineConfig, RetryConfig, TriggerConfig, StepConfig, Value,
43    load_default_config, DataFormat, TrainingFormat, GenericJSONDataset, TrainingDataset,
44    TrainingSample, AlgorithmFormat,
45};
46pub use crate::dna::out::hlxb_config_format::{
47    HlxbWriter, HlxbReader, HlxbHeader, HLXB_MAGIC, HLXB_VERSION,
48};
49pub use crate::dna::atp::ast::{
50    HelixAst, Declaration, Expression, Statement, AgentDecl, WorkflowDecl, MemoryDecl,
51    ContextDecl, CrewDecl, PipelineDecl,
52};
53pub use crate::dna::atp::lexer::{Token, SourceLocation};
54pub use crate::dna::atp::parser::{Parser, ParseError};
55pub use crate::dna::mds::semantic::{SemanticAnalyzer, SemanticError};
56pub use crate::dna::mds::codegen::{CodeGenerator, HelixIR};
57pub use crate::dna::atp::types::HelixLoader;
58pub use crate::dna::mds::server::{HelixServer, ServerConfig};
59use std::path::Path;
60type ParseResult<T> = Result<T, ParseError>;
61#[cfg(feature = "js")]
62use napi::bindgen_prelude::*;
63#[cfg(feature = "js")]
64use napi_derive::napi;
65#[cfg(feature = "js")]
66#[derive(Debug, Clone)]
67pub struct NapiStringError(pub String);
68#[cfg(feature = "js")]
69impl AsRef<str> for NapiStringError {
70    fn as_ref(&self) -> &str {
71        &self.0
72    }
73}
74#[cfg(feature = "js")]
75impl From<String> for NapiStringError {
76    fn from(s: String) -> Self {
77        NapiStringError(s)
78    }
79}
80#[cfg(feature = "js")]
81impl std::fmt::Display for NapiStringError {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "{}", self.0)
84    }
85}
86#[cfg(feature = "js")]
87impl std::error::Error for NapiStringError {}
88#[cfg(feature = "js")]
89impl From<NapiStringError> for napi::Error<NapiStringError> {
90    fn from(err: NapiStringError) -> Self {
91        napi::Error::new(err, napi::Status::GenericFailure)
92    }
93}
94#[cfg(feature = "js")]
95type ConfigResult<T> = Result<T, NapiStringError>;
96#[cfg(not(feature = "js"))]
97type ConfigResult<T> = Result<T, String>;
98pub fn parse(source: &str) -> std::result::Result<HelixAst, ParseError> {
99    parse_with_locations(source).or_else(|_| parse_legacy(source))
100}
101pub fn parse_with_locations(source: &str) -> std::result::Result<HelixAst, ParseError> {
102    use crate::dna::atp::lexer::{tokenize_with_locations, SourceMap};
103    let tokens_with_loc = match tokenize_with_locations(source) {
104        Ok(tokens) => tokens,
105        Err(e) => {
106            return Err(ParseError {
107                message: format!("Lexer error: {}", e),
108                location: None,
109                token_index: 0,
110                expected: None,
111                found: String::new(),
112                context: String::new(),
113            });
114        }
115    };
116    let source_map = SourceMap {
117        tokens: tokens_with_loc.clone(),
118        source: source.to_string(),
119    };
120    let mut parser = Parser::new_with_source_map(source_map);
121    match parser.parse() {
122        Ok(ast) => Ok(ast),
123        Err(msg) => {
124            Err(ParseError {
125                message: msg,
126                location: None,
127                token_index: 0,
128                expected: None,
129                found: String::new(),
130                context: String::new(),
131            })
132        }
133    }
134}
135fn parse_legacy(source: &str) -> std::result::Result<HelixAst, ParseError> {
136    let tokens = match crate::dna::atp::lexer::tokenize(source) {
137        Ok(tokens) => tokens,
138        Err(e) => {
139            return Err(ParseError {
140                message: format!("Lexer error: {}", e),
141                location: None,
142                token_index: 0,
143                expected: None,
144                found: String::new(),
145                context: String::new(),
146            });
147        }
148    };
149    let mut parser = Parser::new(tokens);
150    match parser.parse() {
151        Ok(ast) => Ok(ast),
152        Err(msg) => {
153            Err(ParseError {
154                message: msg,
155                location: None,
156                token_index: 0,
157                expected: None,
158                found: String::new(),
159                context: String::new(),
160            })
161        }
162    }
163}
164#[cfg(feature = "js")]
165pub fn parse_and_validate(
166    source: &str,
167) -> std::result::Result<HelixConfig, NapiStringError> {
168    let ast = match parse(source) {
169        Ok(ast) => ast,
170        Err(e) => return Err(NapiStringError(e.to_string())),
171    };
172    validate(&ast)?;
173    ast_to_config(ast)
174}
175#[cfg(not(feature = "js"))]
176pub fn parse_and_validate(source: &str) -> std::result::Result<HelixConfig, String> {
177    let ast = match parse(source) {
178        Ok(ast) => ast,
179        Err(e) => return Err(e.to_string()),
180    };
181    validate(&ast)?;
182    ast_to_config(ast)
183}
184#[cfg(feature = "js")]
185pub fn validate(ast: &HelixAst) -> std::result::Result<(), NapiStringError> {
186    let mut analyzer = SemanticAnalyzer::new();
187    match analyzer.analyze(ast) {
188        Ok(()) => Ok(()),
189        Err(errors) => {
190            Err(
191                NapiStringError(
192                    errors
193                        .iter()
194                        .map(|e| format!("{:?}", e))
195                        .collect::<Vec<_>>()
196                        .join("\n"),
197                ),
198            )
199        }
200    }
201}
202#[cfg(not(feature = "js"))]
203pub fn validate(ast: &HelixAst) -> std::result::Result<(), String> {
204    let mut analyzer = SemanticAnalyzer::new();
205    match analyzer.analyze(ast) {
206        Ok(()) => Ok(()),
207        Err(errors) => {
208            Err(errors.iter().map(|e| format!("{:?}", e)).collect::<Vec<_>>().join("\n"))
209        }
210    }
211}
212#[cfg(feature = "js")]
213pub fn ast_to_config(
214    ast: HelixAst,
215) -> std::result::Result<HelixConfig, NapiStringError> {
216    let loader = crate::dna::atp::types::HelixLoader::new();
217    match loader.ast_to_config(ast) {
218        Ok(config) => Ok(config),
219        Err(e) => Err(NapiStringError(e.to_string())),
220    }
221}
222#[cfg(not(feature = "js"))]
223pub fn ast_to_config(ast: HelixAst) -> std::result::Result<HelixConfig, String> {
224    let loader = crate::dna::atp::types::HelixLoader::new();
225    match loader.ast_to_config(ast) {
226        Ok(config) => Ok(config),
227        Err(e) => Err(e.to_string()),
228    }
229}
230#[cfg(feature = "js")]
231pub fn load_file<P: AsRef<Path>>(
232    path: P,
233) -> std::result::Result<HelixConfig, NapiStringError> {
234    let content = match std::fs::read_to_string(path) {
235        Ok(content) => content,
236        Err(e) => return Err(NapiStringError(format!("Failed to read file: {}", e))),
237    };
238    parse_and_validate(&content)
239}
240#[cfg(not(feature = "js"))]
241pub fn load_file<P: AsRef<Path>>(path: P) -> std::result::Result<HelixConfig, String> {
242    let content = match std::fs::read_to_string(path) {
243        Ok(content) => content,
244        Err(e) => return Err(format!("Failed to read file: {}", e)),
245    };
246    parse_and_validate(&content)
247}
248#[cfg(feature = "js")]
249pub fn load_directory<P: AsRef<Path>>(
250    path: P,
251) -> std::result::Result<Vec<HelixConfig>, NapiStringError> {
252    let mut configs = Vec::new();
253    let entries = match std::fs::read_dir(path) {
254        Ok(entries) => entries,
255        Err(e) => return Err(NapiStringError(format!("Failed to read directory: {}", e))),
256    };
257    for entry in entries {
258        let entry = match entry {
259            Ok(entry) => entry,
260            Err(e) => return Err(NapiStringError(format!("Failed to read entry: {}", e))),
261        };
262        let path = entry.path();
263        if path.extension().and_then(|s| s.to_str()) == Some("HELIX") {
264            let config = match load_file(&path) {
265                Ok(config) => config,
266                Err(e) => return Err(e),
267            };
268            configs.push(config);
269        }
270    }
271    Ok(configs)
272}
273#[cfg(not(feature = "js"))]
274pub fn load_directory<P: AsRef<Path>>(
275    path: P,
276) -> std::result::Result<Vec<HelixConfig>, String> {
277    let mut configs = Vec::new();
278    let entries = match std::fs::read_dir(path) {
279        Ok(entries) => entries,
280        Err(e) => return Err(format!("Failed to read directory: {}", e)),
281    };
282    for entry in entries {
283        let entry = match entry {
284            Ok(entry) => entry,
285            Err(e) => return Err(format!("Failed to read entry: {}", e)),
286        };
287        let path = entry.path();
288        if path.extension().and_then(|s| s.to_str()) == Some("HELIX") {
289            let config = match load_file(&path) {
290                Ok(config) => config,
291                Err(e) => return Err(e),
292            };
293            configs.push(config);
294        }
295    }
296    Ok(configs)
297}
298pub fn pretty_print(ast: &HelixAst) -> String {
299    let mut printer = crate::dna::atp::ast::AstPrettyPrinter::new();
300    printer.print(ast)
301}
302#[cfg(feature = "php")]
303use std::ffi::{CStr, CString};
304#[cfg(feature = "php")]
305use std::os::raw::c_char;
306/// Execute Helix code using FFI
307#[cfg(feature = "php")]
308#[no_mangle]
309pub extern "C" fn helix_execute_ffi(code_ptr: *const c_char) -> *mut c_char {
310    if code_ptr.is_null() {
311        return std::ptr::null_mut();
312    }
313    let code = unsafe { CStr::from_ptr(code_ptr) };
314    let code_str = match code.to_str() {
315        Ok(s) => s,
316        Err(_) => {
317            let error_str = "Error: Invalid UTF-8 in code string";
318            if let Ok(cstring) = CString::new(error_str) {
319                return cstring.into_raw();
320            }
321            return std::ptr::null_mut();
322        }
323    };
324    let runtime = match tokio::runtime::Runtime::new() {
325        Ok(rt) => rt,
326        Err(e) => {
327            let error_str = format!("Error: Failed to create runtime: {}", e);
328            if let Ok(cstring) = CString::new(error_str) {
329                return cstring.into_raw();
330            }
331            return std::ptr::null_mut();
332        }
333    };
334    let result = runtime
335        .block_on(async {
336            let mut hlx = match crate::dna_hlx::Hlx::new().await {
337                Ok(h) => h,
338                Err(e) => return Err(format!("Failed to initialize Helix: {}", e)),
339            };
340            hlx.execute(code_str).await.map_err(|e| format!("Execution error: {}", e))
341        });
342    match result {
343        Ok(value) => {
344            let result_str = format!("{}", value);
345            match CString::new(result_str) {
346                Ok(cstring) => cstring.into_raw(),
347                Err(e) => {
348                    let error_str = format!(
349                        "Error: Failed to create result string: {}", e
350                    );
351                    if let Ok(cstring) = CString::new(error_str) {
352                        cstring.into_raw()
353                    } else {
354                        std::ptr::null_mut()
355                    }
356                }
357            }
358        }
359        Err(e) => {
360            match CString::new(e) {
361                Ok(cstring) => cstring.into_raw(),
362                Err(_) => {
363                    let error_str = "Error: Failed to create error string";
364                    if let Ok(cstring) = CString::new(error_str) {
365                        cstring.into_raw()
366                    } else {
367                        std::ptr::null_mut()
368                    }
369                }
370            }
371        }
372    }
373}
374/// Parse Helix code using FFI
375#[cfg(feature = "php")]
376#[no_mangle]
377pub extern "C" fn helix_parse_ffi(code_ptr: *const c_char) -> *mut c_char {
378    if code_ptr.is_null() {
379        return std::ptr::null_mut();
380    }
381    let code = unsafe { CStr::from_ptr(code_ptr) };
382    let code_str = match code.to_str() {
383        Ok(s) => s,
384        Err(_) => {
385            let error_str = "Error: Invalid UTF-8 in code string";
386            if let Ok(cstring) = CString::new(error_str) {
387                return cstring.into_raw();
388            }
389            return std::ptr::null_mut();
390        }
391    };
392    let dispatcher = crate::dispatch::HelixDispatcher::new();
393    let result = dispatcher.parse_only(code_str);
394    match result {
395        Ok(ast) => {
396            let ast_json = match serde_json::to_string_pretty(&ast) {
397                Ok(json) => json,
398                Err(_) => format!("{:?}", ast),
399            };
400            let result_str = format!("Parsed AST:\n{}", ast_json);
401            match CString::new(result_str) {
402                Ok(cstring) => cstring.into_raw(),
403                Err(e) => {
404                    let error_str = format!("Error: Failed to create AST string: {}", e);
405                    if let Ok(cstring) = CString::new(error_str) {
406                        cstring.into_raw()
407                    } else {
408                        std::ptr::null_mut()
409                    }
410                }
411            }
412        }
413        Err(e) => {
414            let error_str = format!("Parse Error: {}", e);
415            match CString::new(error_str) {
416                Ok(cstring) => cstring.into_raw(),
417                Err(_) => {
418                    let error_str = "Error: Failed to create parse error string";
419                    if let Ok(cstring) = CString::new(error_str) {
420                        cstring.into_raw()
421                    } else {
422                        std::ptr::null_mut()
423                    }
424                }
425            }
426        }
427    }
428}
429/// Load and execute a Helix file using FFI
430#[cfg(feature = "php")]
431#[no_mangle]
432pub extern "C" fn helix_load_file_ffi(file_path_ptr: *const c_char) -> *mut c_char {
433    if file_path_ptr.is_null() {
434        return std::ptr::null_mut();
435    }
436    let file_path = unsafe { CStr::from_ptr(file_path_ptr) };
437    let file_path_str = match file_path.to_str() {
438        Ok(s) => s,
439        Err(_) => {
440            let error_str = "Error: Invalid UTF-8 in file path";
441            if let Ok(cstring) = CString::new(error_str) {
442                return cstring.into_raw();
443            }
444            return std::ptr::null_mut();
445        }
446    };
447    let runtime = match tokio::runtime::Runtime::new() {
448        Ok(rt) => rt,
449        Err(e) => {
450            let error_str = format!("Error: Failed to create runtime: {}", e);
451            if let Ok(cstring) = CString::new(error_str) {
452                return cstring.into_raw();
453            }
454            return std::ptr::null_mut();
455        }
456    };
457    let result = runtime
458        .block_on(async {
459            let content = std::fs::read_to_string(file_path_str)
460                .map_err(|e| format!("Failed to read file '{}': {}", file_path_str, e))?;
461            let mut hlx = crate::dna_hlx::Hlx::new()
462                .await
463                .map_err(|e| format!("Failed to initialize Helix: {}", e))?;
464            hlx.execute(&content).await.map_err(|e| format!("Execution error: {}", e))
465        });
466    match result {
467        Ok(value) => {
468            let result_str = format!(
469                "File '{}' executed successfully:\n{}", file_path_str, value
470            );
471            match CString::new(result_str) {
472                Ok(cstring) => cstring.into_raw(),
473                Err(e) => {
474                    let error_str = format!(
475                        "Error: Failed to create result string: {}", e
476                    );
477                    if let Ok(cstring) = CString::new(error_str) {
478                        cstring.into_raw()
479                    } else {
480                        std::ptr::null_mut()
481                    }
482                }
483            }
484        }
485        Err(e) => {
486            match CString::new(e) {
487                Ok(cstring) => cstring.into_raw(),
488                Err(_) => {
489                    let error_str = "Error: Failed to create error string";
490                    if let Ok(cstring) = CString::new(error_str) {
491                        cstring.into_raw()
492                    } else {
493                        std::ptr::null_mut()
494                    }
495                }
496            }
497        }
498    }
499}
500/// Free a C string allocated by the FFI functions
501#[cfg(feature = "php")]
502#[no_mangle]
503pub extern "C" fn helix_free_string(ptr: *mut c_char) {
504    if !ptr.is_null() {
505        unsafe {
506            let _ = CString::from_raw(ptr);
507        }
508    }
509}
510/// Get version information
511#[cfg(feature = "php")]
512#[no_mangle]
513pub extern "C" fn helix_version() -> *mut c_char {
514    let version = env!("CARGO_PKG_VERSION");
515    match CString::new(version) {
516        Ok(cstring) => cstring.into_raw(),
517        Err(_) => std::ptr::null_mut(),
518    }
519}
520/// Test function to verify FFI is working
521#[cfg(feature = "php")]
522#[no_mangle]
523pub extern "C" fn helix_test_ffi() -> *mut c_char {
524    match CString::new("Hello from Helix PHP SDK FFI!") {
525        Ok(cstring) => cstring.into_raw(),
526        Err(_) => std::ptr::null_mut(),
527    }
528}
529/// Initialize the PHP SDK
530#[cfg(feature = "php")]
531#[no_mangle]
532pub extern "C" fn helix_init() {}
533#[cfg(feature = "js")]
534/// JavaScript wrapper for Helix values
535#[napi(js_name = "Value")]
536#[derive(Clone)]
537pub struct JsValue {
538    inner: HlxValue,
539}
540#[cfg(feature = "js")]
541#[napi]
542impl JsValue {
543    #[napi(constructor)]
544    pub fn new(value: String) -> Result<Self> {
545        Ok(JsValue {
546            inner: HlxValue::String(value),
547        })
548    }
549    #[napi(getter)]
550    pub fn type_name(&self) -> &str {
551        match &self.inner {
552            HlxValue::String(_) => "string",
553            HlxValue::Number(_) => "number",
554            HlxValue::Bool(_) => "boolean",
555            HlxValue::Array(_) => "array",
556            HlxValue::Object(_) => "object",
557            HlxValue::Null => "null",
558        }
559    }
560    #[napi(getter)]
561    pub fn is_string(&self) -> bool {
562        matches!(& self.inner, HlxValue::String(_))
563    }
564    #[napi(getter)]
565    pub fn is_number(&self) -> bool {
566        matches!(& self.inner, HlxValue::Number(_))
567    }
568    #[napi(getter)]
569    pub fn is_boolean(&self) -> bool {
570        matches!(& self.inner, HlxValue::Bool(_))
571    }
572    #[napi(getter)]
573    pub fn is_array(&self) -> bool {
574        matches!(& self.inner, HlxValue::Array(_))
575    }
576    #[napi(getter)]
577    pub fn is_object(&self) -> bool {
578        matches!(& self.inner, HlxValue::Object(_))
579    }
580    #[napi(getter)]
581    pub fn is_null(&self) -> bool {
582        matches!(& self.inner, HlxValue::Null)
583    }
584    #[napi]
585    pub fn as_string(&self) -> Option<String> {
586        match &self.inner {
587            HlxValue::String(s) => Some(s.clone()),
588            _ => None,
589        }
590    }
591    #[napi]
592    pub fn as_number(&self) -> Option<f64> {
593        match &self.inner {
594            HlxValue::Number(n) => Some(*n),
595            _ => None,
596        }
597    }
598    #[napi]
599    pub fn as_boolean(&self) -> Option<bool> {
600        match &self.inner {
601            HlxValue::Bool(b) => Some(*b),
602            _ => None,
603        }
604    }
605    #[napi]
606    pub fn as_array(&self) -> Option<Vec<JsValue>> {
607        match &self.inner {
608            HlxValue::Array(arr) => {
609                Some(arr.iter().map(|v| JsValue { inner: v.clone() }).collect())
610            }
611            _ => None,
612        }
613    }
614    #[napi]
615    pub fn as_object(&self) -> Option<HashMap<String, JsValue>> {
616        match &self.inner {
617            HlxValue::Object(obj) => {
618                let mut result = HashMap::new();
619                for (k, v) in obj {
620                    result.insert(k.clone(), JsValue { inner: v.clone() });
621                }
622                Some(result)
623            }
624            _ => None,
625        }
626    }
627    #[napi]
628    pub fn to_string(&self) -> String {
629        match &self.inner {
630            HlxValue::String(s) => s.clone(),
631            HlxValue::Number(n) => n.to_string(),
632            HlxValue::Bool(b) => b.to_string(),
633            HlxValue::Array(arr) => format!("[{}]", arr.len()),
634            HlxValue::Object(obj) => format!("{{{}}}", obj.len()),
635            HlxValue::Null => "null".to_string(),
636        }
637    }
638    #[napi]
639    pub fn to_json(&self) -> String {
640        serde_json::to_string(&self.inner).unwrap_or_else(|_| "null".to_string())
641    }
642}
643#[cfg(feature = "js")]
644/// JavaScript wrapper for HelixConfig
645#[napi(js_name = "HelixConfig")]
646pub struct JsHelixConfig {
647    config: HelixConfig,
648}
649#[cfg(feature = "js")]
650#[napi]
651impl JsHelixConfig {
652    #[napi(constructor)]
653    pub fn new() -> Self {
654        JsHelixConfig {
655            config: HelixConfig::default(),
656        }
657    }
658    #[napi]
659    pub fn get(&self, key: String) -> Option<JsValue> {
660        Some(JsValue {
661            inner: HlxValue::String(key),
662        })
663    }
664    #[napi]
665    pub fn set(&mut self, key: String, value: String) -> Result<()> {
666        Ok(())
667    }
668    #[napi]
669    pub fn keys(&self) -> Vec<String> {
670        let mut keys = Vec::new();
671        if !self.config.agents.is_empty() {
672            keys.push("agents".to_string());
673        }
674        if !self.config.workflows.is_empty() {
675            keys.push("workflows".to_string());
676        }
677        if !self.config.contexts.is_empty() {
678            keys.push("contexts".to_string());
679        }
680        if self.config.memory.is_some() {
681            keys.push("memory".to_string());
682        }
683        keys
684    }
685    #[napi]
686    pub fn has(&self, key: String) -> bool {
687        match key.as_str() {
688            "agents" => !self.config.agents.is_empty(),
689            "workflows" => !self.config.workflows.is_empty(),
690            "contexts" => !self.config.contexts.is_empty(),
691            "memory" => self.config.memory.is_some(),
692            "crews" => !self.config.crews.is_empty(),
693            _ => false,
694        }
695    }
696    #[napi]
697    pub fn size(&self) -> u32 {
698        self.keys().len() as u32
699    }
700    #[napi]
701    pub fn items(&self) -> HashMap<String, String> {
702        let mut result = HashMap::new();
703        for key in self.keys() {
704            let value = self
705                .get(key.clone())
706                .map(|v| v.to_string())
707                .unwrap_or_else(|| "null".to_string());
708            result.insert(key, value);
709        }
710        result
711    }
712    #[napi]
713    pub fn delete(&mut self, key: String) -> bool {
714        false
715    }
716    #[napi]
717    pub fn clear(&mut self) -> Result<()> {
718        self.config = HelixConfig::default();
719        Ok(())
720    }
721    #[napi]
722    pub fn to_object(&self) -> HashMap<String, JsValue> {
723        let mut result = HashMap::new();
724        result
725            .insert(
726                "agents_count".to_string(),
727                JsValue {
728                    inner: HlxValue::Number(self.config.agents.len() as f64),
729                },
730            );
731        result
732            .insert(
733                "workflows_count".to_string(),
734                JsValue {
735                    inner: HlxValue::Number(self.config.workflows.len() as f64),
736                },
737            );
738        result
739            .insert(
740                "contexts_count".to_string(),
741                JsValue {
742                    inner: HlxValue::Number(self.config.contexts.len() as f64),
743                },
744            );
745        result
746    }
747}
748#[cfg(feature = "js")]
749#[napi]
750pub fn parse_helix_config(source: String) -> Result<JsHelixConfig> {
751    match crate::parse_and_validate(&source) {
752        Ok(config) => Ok(JsHelixConfig { config }),
753        Err(err) => Err(Error::from_reason(format!("Parse error: {}", err))),
754    }
755}
756#[cfg(feature = "js")]
757#[napi]
758pub async fn execute(
759    expression: String,
760    context: Option<HashMap<String, String>>,
761) -> Result<JsValue> {
762    let rt = tokio::runtime::Runtime::new()
763        .map_err(|e| Error::from_reason(format!("Runtime error: {}", e)))?;
764    let result = rt
765        .block_on(async {
766            let mut interpreter = crate::interpreter::HelixInterpreter::new()
767                .await
768                .map_err(|e| Error::from_reason(
769                    format!("Interpreter initialization error: {}", e),
770                ))?;
771            match crate::parse(&expression) {
772                Ok(ast) => {
773                    match interpreter.execute_ast(&ast).await {
774                        Ok(value) => Ok(JsValue { inner: value }),
775                        Err(e) => {
776                            Err(Error::from_reason(format!("Execution error: {}", e)))
777                        }
778                    }
779                }
780                Err(parse_err) => {
781                    let result_value = HlxValue::String(
782                        format!("Expression result: {}", expression),
783                    );
784                    Ok(JsValue { inner: result_value })
785                }
786            }
787        });
788    result
789}
790#[cfg(feature = "js")]
791#[napi]
792pub fn load_helix_config(file_path: String) -> Result<JsHelixConfig> {
793    match crate::load_file(&file_path) {
794        Ok(config) => Ok(JsHelixConfig { config }),
795        Err(err) => Err(napi::Error::from_reason(format!("File load error: {}", err))),
796    }
797}
798#[cfg(feature = "js")]
799#[napi(js_name = "ExecutionContext")]
800#[derive(Clone)]
801pub struct JsExecutionContext {
802    request: Option<HashMap<String, String>>,
803    session: HashMap<String, String>,
804    cookies: HashMap<String, String>,
805    params: HashMap<String, String>,
806    query: HashMap<String, String>,
807}
808#[cfg(feature = "js")]
809#[napi]
810impl JsExecutionContext {
811    #[napi(constructor)]
812    pub fn new(
813        request: Option<HashMap<String, String>>,
814        session: Option<HashMap<String, String>>,
815        cookies: Option<HashMap<String, String>>,
816        params: Option<HashMap<String, String>>,
817        query: Option<HashMap<String, String>>,
818    ) -> Self {
819        JsExecutionContext {
820            request,
821            session: session.unwrap_or_default(),
822            cookies: cookies.unwrap_or_default(),
823            params: params.unwrap_or_default(),
824            query: query.unwrap_or_default(),
825        }
826    }
827    #[napi(getter)]
828    pub fn request(&self) -> Option<HashMap<String, String>> {
829        self.request.clone()
830    }
831    #[napi(getter)]
832    pub fn session(&self) -> HashMap<String, String> {
833        self.session.clone()
834    }
835    #[napi(getter)]
836    pub fn cookies(&self) -> HashMap<String, String> {
837        self.cookies.clone()
838    }
839    #[napi(getter)]
840    pub fn params(&self) -> HashMap<String, String> {
841        self.params.clone()
842    }
843    #[napi(getter)]
844    pub fn query(&self) -> HashMap<String, String> {
845        self.query.clone()
846    }
847}
848#[cfg(feature = "js")]
849#[napi(js_name = "OperatorRegistry")]
850pub struct JsOperatorRegistry {
851    context: Option<JsExecutionContext>,
852}
853#[cfg(feature = "js")]
854#[napi]
855impl JsOperatorRegistry {
856    #[napi(constructor)]
857    pub fn new(context: Option<&JsExecutionContext>) -> Self {
858        JsOperatorRegistry {
859            context: context.map(|c| c.clone()),
860        }
861    }
862    #[napi(getter)]
863    pub fn context(&self) -> Option<JsExecutionContext> {
864        self.context.clone()
865    }
866    #[napi]
867    pub async fn execute(&self, operator: String, params: String) -> Result<JsValue> {
868        let result = HlxValue::String(
869            format!("Operator '{}' executed with params: {}", operator, params),
870        );
871        Ok(JsValue { inner: result })
872    }
873}
874#[cfg(feature = "js")]
875#[napi(js_name = "HelixInterpreter")]
876pub struct JsHelixInterpreter {
877    interpreter: Option<crate::interpreter::HelixInterpreter>,
878}
879#[cfg(feature = "js")]
880#[napi]
881impl JsHelixInterpreter {
882    #[napi(constructor)]
883    pub fn new() -> Result<Self> {
884        Ok(JsHelixInterpreter {
885            interpreter: None,
886        })
887    }
888    #[napi]
889    pub async unsafe fn execute(&mut self, expression: String) -> Result<JsValue> {
890        if self.interpreter.is_none() {
891            let interpreter = crate::interpreter::HelixInterpreter::new()
892                .await
893                .map_err(|e| Error::from_reason(
894                    format!("Interpreter initialization error: {}", e),
895                ))?;
896            self.interpreter = Some(interpreter);
897        }
898        let interpreter = self.interpreter.as_mut().unwrap();
899        match crate::parse(&expression) {
900            Ok(ast) => {
901                match interpreter.execute_ast(&ast).await {
902                    Ok(value) => Ok(JsValue { inner: value }),
903                    Err(e) => Err(Error::from_reason(format!("Execution error: {}", e))),
904                }
905            }
906            Err(parse_err) => {
907                let result_value = HlxValue::String(
908                    format!("Expression result: {}", expression),
909                );
910                Ok(JsValue { inner: result_value })
911            }
912        }
913    }
914    #[napi]
915    pub async unsafe fn set_variable(
916        &mut self,
917        name: String,
918        value: String,
919    ) -> Result<()> {
920        if self.interpreter.is_none() {
921            let interpreter = crate::interpreter::HelixInterpreter::new()
922                .await
923                .map_err(|e| Error::from_reason(
924                    format!("Interpreter initialization error: {}", e),
925                ))?;
926            self.interpreter = Some(interpreter);
927        }
928        if let Some(interpreter) = &mut self.interpreter {
929            interpreter.set_variable(name, HlxValue::String(value));
930        }
931        Ok(())
932    }
933    #[napi]
934    pub fn get_variable(&self, name: String) -> Option<JsValue> {
935        self.interpreter
936            .as_ref()?
937            .get_variable(&name)
938            .map(|v| JsValue { inner: v.clone() })
939    }
940}