helix/dna/hel/
dispatch.rs

1use crate::dna::atp::lexer::{tokenize_with_locations, SourceMap};
2use crate::dna::atp::parser::{Parser, ParseError};
3use crate::dna::atp::ast::HelixAst;
4use crate::dna::atp::value::Value;
5use crate::dna::atp::interpreter::HelixInterpreter;
6use crate::hel::error::HlxError;
7#[derive(Debug)]
8pub enum DispatchResult {
9    Parsed(HelixAst),
10    Executed(Value),
11    #[cfg(feature = "js")]
12    ParseError(napi::Error<ParseError>),
13    #[cfg(not(feature = "js"))]
14    ParseError(ParseError),
15    ExecutionError(HlxError),
16}
17pub struct HelixDispatcher {
18    interpreter: Option<HelixInterpreter>,
19}
20impl HelixDispatcher {
21    pub fn new() -> Self {
22        Self { interpreter: None }
23    }
24    pub async fn initialize(&mut self) -> Result<(), HlxError> {
25        self.interpreter = Some(HelixInterpreter::new().await?);
26        Ok(())
27    }
28    #[cfg(feature = "js")]
29    pub fn parse_only(&self, source: &str) -> Result<HelixAst, napi::Error<ParseError>> {
30        crate::parse(source).map_err(|e| e.into())
31    }
32    #[cfg(not(feature = "js"))]
33    pub fn parse_only(&self, source: &str) -> Result<HelixAst, ParseError> {
34        crate::parse(source)
35    }
36    pub async fn parse_and_execute(
37        &mut self,
38        source: &str,
39    ) -> Result<DispatchResult, HlxError> {
40        if self.interpreter.is_none() {
41            self.initialize().await?;
42        }
43        let ast = match crate::parse(source) {
44            Ok(ast) => ast,
45            Err(parse_err) => return Ok(DispatchResult::ParseError(parse_err)),
46        };
47        if let Some(ref mut interpreter) = self.interpreter {
48            match interpreter.execute_ast(&ast).await {
49                Ok(value) => Ok(DispatchResult::Executed(value.into())),
50                Err(exec_err) => Ok(DispatchResult::ExecutionError(exec_err)),
51            }
52        } else {
53            Err(
54                HlxError::execution_error(
55                    "Interpreter not initialized",
56                    "Call initialize() before executing code",
57                ),
58            )
59        }
60    }
61    pub async fn parse_dsl(&mut self, source: &str) -> Result<DispatchResult, HlxError> {
62        let tokens_with_loc = tokenize_with_locations(source)
63            .map_err(|e| {
64                HlxError::invalid_input(
65                    format!("Lexer error: {}", e),
66                    "Check syntax and try again",
67                )
68            })?;
69        let source_map = SourceMap {
70            tokens: tokens_with_loc.clone(),
71            source: source.to_string(),
72        };
73        let mut parser = Parser::new_with_source_map(source_map);
74        match parser.parse() {
75            Ok(ast) => Ok(DispatchResult::Parsed(ast)),
76            Err(parse_err) => {
77                let parse_error = ParseError {
78                    message: parse_err,
79                    location: None,
80                    token_index: 0,
81                    expected: None,
82                    found: String::new(),
83                    context: String::new(),
84                };
85                Ok(DispatchResult::ParseError(parse_error.into()))
86            }
87        }
88    }
89    pub fn interpreter(&self) -> Option<&HelixInterpreter> {
90        self.interpreter.as_ref()
91    }
92    pub fn interpreter_mut(&mut self) -> Option<&mut HelixInterpreter> {
93        self.interpreter.as_mut()
94    }
95    pub fn is_ready(&self) -> bool {
96        self.interpreter.is_some()
97    }
98}
99#[cfg(feature = "js")]
100pub fn parse_helix(source: &str) -> Result<HelixAst, napi::Error<ParseError>> {
101    crate::parse(source).map_err(|e| e.into())
102}
103#[cfg(not(feature = "js"))]
104pub fn parse_helix(source: &str) -> Result<HelixAst, ParseError> {
105    crate::parse(source)
106}
107#[cfg(feature = "js")]
108pub fn parse_and_validate(
109    source: &str,
110) -> Result<crate::atp::types::HelixConfig, napi::Error<String>> {
111    crate::parse_and_validate(source).map_err(|e| e.into())
112}
113#[cfg(not(feature = "js"))]
114pub fn parse_and_validate(source: &str) -> Result<crate::atp::types::HelixConfig, String> {
115    crate::parse_and_validate(source)
116}
117pub async fn execute_helix(source: &str) -> Result<Value, HlxError> {
118    let mut dispatcher = HelixDispatcher::new();
119    dispatcher.initialize().await?;
120    match dispatcher.parse_and_execute(source).await? {
121        DispatchResult::Executed(value) => Ok(value),
122        DispatchResult::ParseError(err) => {
123            Err(
124                HlxError::invalid_input(
125                    format!("Parse error: {}", err),
126                    "Check syntax and try again",
127                ),
128            )
129        }
130        DispatchResult::ExecutionError(err) => Err(err),
131        DispatchResult::Parsed(_) => {
132            Err(
133                HlxError::execution_error(
134                    "Parsed AST but no execution result",
135                    "Use parse_helix for parsing only",
136                ),
137            )
138        }
139    }
140}
141#[cfg(feature = "js")]
142pub async fn parse_helix_dsl(source: &str) -> Result<HelixAst, napi::Error<ParseError>> {
143    let mut dispatcher = HelixDispatcher::new();
144    match dispatcher.parse_dsl(source).await {
145        Ok(DispatchResult::Parsed(ast)) => Ok(ast),
146        Ok(DispatchResult::ParseError(err)) => Err(err),
147        _ => {
148            let parse_error = ParseError {
149                message: "Unexpected dispatch result".to_string(),
150                location: None,
151                token_index: 0,
152                expected: None,
153                found: String::new(),
154                context: String::new(),
155            };
156            Err(parse_error.into())
157        }
158    }
159}
160#[cfg(not(feature = "js"))]
161pub async fn parse_helix_dsl(source: &str) -> Result<HelixAst, ParseError> {
162    let mut dispatcher = HelixDispatcher::new();
163    match dispatcher.parse_dsl(source).await {
164        Ok(DispatchResult::Parsed(ast)) => Ok(ast),
165        Ok(DispatchResult::ParseError(err)) => Err(err),
166        _ => {
167            let parse_error = ParseError {
168                message: "Unexpected dispatch result".to_string(),
169                location: None,
170                token_index: 0,
171                expected: None,
172                found: String::new(),
173                context: String::new(),
174            };
175            Err(parse_error)
176        }
177    }
178}
179#[cfg(test)]
180mod tests {
181    use super::*;
182    #[tokio::test]
183    async fn test_parse_only() {
184        let source = r#"
185        agent "test_agent" {
186            capabilities ["coding", "analysis"]
187            backstory {
188                "A helpful AI assistant"
189            }
190        }
191        "#;
192        let dispatcher = HelixDispatcher::new();
193        let result = dispatcher.parse_only(source);
194        if let Err(ref e) = result {
195            println!("Parse error: {:?}", e);
196        }
197        assert!(result.is_ok());
198    }
199    #[tokio::test]
200    async fn test_parse_and_execute() {
201        let source = r#"
202        @date("Y-m-d")
203        "#;
204        let mut dispatcher = HelixDispatcher::new();
205        let result = dispatcher.parse_and_execute(source).await;
206        assert!(result.is_ok());
207        if let Ok(DispatchResult::Executed(value)) = result {
208            match value {
209                Value::String(date_str) => assert!(! date_str.is_empty()),
210                _ => panic!("Expected string value"),
211            }
212        }
213    }
214    #[tokio::test]
215    async fn test_execute_expression() {
216        let source = "@date(\"Y-m-d\")";
217        let mut dispatcher = HelixDispatcher::new();
218        dispatcher.initialize().await.unwrap();
219        let result = dispatcher.parse_and_execute(source).await;
220        assert!(result.is_ok());
221        if let Ok(DispatchResult::Executed(value)) = result {
222            match value {
223                Value::String(date_str) => assert!(! date_str.is_empty()),
224                _ => panic!("Expected string value"),
225            }
226        }
227    }
228}