helix_core/compiler/
mod.rs

1pub mod binary;
2pub mod optimizer;
3pub mod serializer;
4pub mod loader;
5pub mod bundle;
6pub mod modules;
7pub mod runtime;
8pub mod workflow;
9#[cfg(feature = "cli")]
10pub mod cli;
11pub mod tools;
12pub use binary::{HelixBinary, BinaryFlags, BinaryMetadata, DataSection, SectionType};
13pub use optimizer::{Optimizer, OptimizationLevel};
14pub use serializer::BinarySerializer;
15pub use loader::BinaryLoader;
16pub use bundle::Bundler;
17pub use modules::{ModuleSystem, DependencyBundler, ModuleResolver};
18pub use runtime::{HelixVM, VMExecutor, VMConfig};
19use crate::{parse, validate, ast::HelixAst, codegen::{CodeGenerator, HelixIR}};
20use std::path::{Path, PathBuf};
21use std::fs;
22use std::time::Instant;
23#[derive(Clone)]
24pub struct Compiler {
25    optimization_level: OptimizationLevel,
26    enable_compression: bool,
27    enable_cache: bool,
28    verbose: bool,
29    cache_dir: Option<PathBuf>,
30}
31impl Compiler {
32    pub fn new(optimization_level: OptimizationLevel) -> Self {
33        Self {
34            optimization_level,
35            enable_compression: true,
36            enable_cache: true,
37            verbose: false,
38            cache_dir: None,
39        }
40    }
41    pub fn builder() -> CompilerBuilder {
42        CompilerBuilder::default()
43    }
44    pub fn compile_file<P: AsRef<Path>>(
45        &self,
46        input: P,
47    ) -> Result<HelixBinary, CompileError> {
48        let start = Instant::now();
49        let path = input.as_ref();
50        if self.verbose {
51            println!("Compiling: {}", path.display());
52        }
53        if self.enable_cache {
54            if let Some(cached) = self.check_cache(path)? {
55                if self.verbose {
56                    println!("  Using cached version");
57                }
58                return Ok(cached);
59            }
60        }
61        let source = fs::read_to_string(path)
62            .map_err(|e| CompileError::IoError(format!("Failed to read file: {}", e)))?;
63        let binary = self.compile_source(&source, Some(path))?;
64        if self.enable_cache {
65            self.cache_binary(path, &binary)?;
66        }
67        if self.verbose {
68            let elapsed = start.elapsed();
69            println!("  Compiled in {:?}", elapsed);
70        }
71        Ok(binary)
72    }
73    pub fn compile_source(
74        &self,
75        source: &str,
76        source_path: Option<&Path>,
77    ) -> Result<HelixBinary, CompileError> {
78        let ast = parse(source).map_err(|e| CompileError::ParseError(e.to_string()))?;
79        validate(&ast).map_err(|e| CompileError::ValidationError(e))?;
80        let mut generator = CodeGenerator::new();
81        let ir = generator.generate(&ast);
82        let optimized_ir = self.optimize_ir(ir);
83        let binary = self.ir_to_binary(optimized_ir, source_path)?;
84        Ok(binary)
85    }
86    pub fn compile_bundle<P: AsRef<Path>>(
87        &self,
88        directory: P,
89    ) -> Result<HelixBinary, CompileError> {
90        let bundler = Bundler::new();
91        bundler.bundle_directory(directory, self.optimization_level)
92    }
93    pub fn decompile(&self, binary: &HelixBinary) -> Result<String, CompileError> {
94        let ast = self.binary_to_ast(binary)?;
95        Ok(crate::pretty_print(&ast))
96    }
97    fn optimize_ir(&self, mut ir: HelixIR) -> HelixIR {
98        let mut optimizer = Optimizer::new(self.optimization_level);
99        optimizer.optimize(&mut ir);
100        ir
101    }
102    fn ir_to_binary(
103        &self,
104        ir: HelixIR,
105        source_path: Option<&Path>,
106    ) -> Result<HelixBinary, CompileError> {
107        let serializer = BinarySerializer::new(self.enable_compression)
108            .with_compression_method(
109                if self.enable_compression {
110                    binary::CompressionMethod::Lz4
111                } else {
112                    binary::CompressionMethod::None
113                },
114            );
115        let binary = serializer
116            .serialize(ir, source_path)
117            .map_err(|e| CompileError::SerializationError(e.to_string()))?;
118        Ok(binary)
119    }
120    fn binary_to_ast(&self, binary: &HelixBinary) -> Result<HelixAst, CompileError> {
121        let deserializer = BinarySerializer::new(false);
122        let ir = deserializer
123            .deserialize_to_ir(binary)
124            .map_err(|e| CompileError::DeserializationError(e.to_string()))?;
125        ir_to_ast(ir)
126    }
127    fn check_cache(
128        &self,
129        source_path: &Path,
130    ) -> Result<Option<HelixBinary>, CompileError> {
131        if let Some(cache_dir) = &self.cache_dir {
132            let cache_path = cache_path_for(cache_dir, source_path);
133            if cache_path.exists() {
134                let source_modified = fs::metadata(source_path)
135                    .and_then(|m| m.modified())
136                    .map_err(|e| CompileError::IoError(e.to_string()))?;
137                let cache_modified = fs::metadata(&cache_path)
138                    .and_then(|m| m.modified())
139                    .map_err(|e| CompileError::IoError(e.to_string()))?;
140                if cache_modified > source_modified {
141                    let serializer = BinarySerializer::new(false);
142                    return serializer
143                        .read_from_file(&cache_path)
144                        .map(Some)
145                        .map_err(|e| CompileError::CacheError(e.to_string()));
146                }
147            }
148        }
149        Ok(None)
150    }
151    fn cache_binary(
152        &self,
153        source_path: &Path,
154        binary: &HelixBinary,
155    ) -> Result<(), CompileError> {
156        if let Some(cache_dir) = &self.cache_dir {
157            let cache_path = cache_path_for(cache_dir, source_path);
158            if let Some(parent) = cache_path.parent() {
159                fs::create_dir_all(parent)
160                    .map_err(|e| CompileError::IoError(e.to_string()))?;
161            }
162            let serializer = BinarySerializer::new(self.enable_compression);
163            serializer
164                .write_to_file(binary, &cache_path)
165                .map_err(|e| CompileError::CacheError(e.to_string()))?;
166        }
167        Ok(())
168    }
169}
170#[derive(Default)]
171pub struct CompilerBuilder {
172    optimization_level: OptimizationLevel,
173    enable_compression: bool,
174    enable_cache: bool,
175    verbose: bool,
176    cache_dir: Option<PathBuf>,
177}
178impl CompilerBuilder {
179    pub fn optimization_level(mut self, level: OptimizationLevel) -> Self {
180        self.optimization_level = level;
181        self
182    }
183    pub fn compression(mut self, enable: bool) -> Self {
184        self.enable_compression = enable;
185        self
186    }
187    pub fn cache(mut self, enable: bool) -> Self {
188        self.enable_cache = enable;
189        self
190    }
191    pub fn cache_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
192        self.cache_dir = Some(dir.into());
193        self
194    }
195    pub fn verbose(mut self, enable: bool) -> Self {
196        self.verbose = enable;
197        self
198    }
199    pub fn build(self) -> Compiler {
200        Compiler {
201            optimization_level: self.optimization_level,
202            enable_compression: self.enable_compression,
203            enable_cache: self.enable_cache,
204            verbose: self.verbose,
205            cache_dir: self.cache_dir,
206        }
207    }
208}
209#[derive(Debug)]
210pub enum CompileError {
211    IoError(String),
212    ParseError(String),
213    ValidationError(String),
214    OptimizationError(String),
215    SerializationError(String),
216    DeserializationError(String),
217    CacheError(String),
218}
219impl std::fmt::Display for CompileError {
220    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
221        match self {
222            Self::IoError(e) => write!(f, "I/O error: {}", e),
223            Self::ParseError(e) => write!(f, "Parse error: {}", e),
224            Self::ValidationError(e) => write!(f, "Validation error: {}", e),
225            Self::OptimizationError(e) => write!(f, "Optimization error: {}", e),
226            Self::SerializationError(e) => write!(f, "Serialization error: {}", e),
227            Self::DeserializationError(e) => write!(f, "Deserialization error: {}", e),
228            Self::CacheError(e) => write!(f, "Cache error: {}", e),
229        }
230    }
231}
232impl std::error::Error for CompileError {}
233fn ir_to_ast(ir: HelixIR) -> Result<HelixAst, CompileError> {
234    let mut declarations = Vec::new();
235    for (_id, agent) in ir.symbol_table.agents {
236        let name = ir
237            .string_pool
238            .get(agent.name_idx)
239            .unwrap_or(&format!("agent_{}", agent.id))
240            .clone();
241        declarations
242            .push(
243                crate::ast::Declaration::Agent(crate::ast::AgentDecl {
244                    name,
245                    properties: std::collections::HashMap::new(),
246                    capabilities: None,
247                    backstory: None,
248                    tools: None,
249                }),
250            );
251    }
252    Ok(HelixAst { declarations })
253}
254fn cache_path_for(cache_dir: &Path, source_path: &Path) -> PathBuf {
255    let mut hasher = std::collections::hash_map::DefaultHasher::new();
256    use std::hash::{Hash, Hasher};
257    source_path.hash(&mut hasher);
258    let hash = hasher.finish();
259    cache_dir.join(format!("{:x}.hlxb", hash))
260}
261#[cfg(test)]
262mod tests {
263    use super::*;
264    #[test]
265    fn test_compiler_builder() {
266        let compiler = Compiler::builder()
267            .optimization_level(OptimizationLevel::Two)
268            .compression(true)
269            .cache(false)
270            .verbose(true)
271            .build();
272        assert_eq!(compiler.optimization_level, OptimizationLevel::Two);
273        assert!(compiler.enable_compression);
274        assert!(! compiler.enable_cache);
275        assert!(compiler.verbose);
276    }
277    #[test]
278    fn test_compile_simple() {
279        let source = r#"
280            agent "test" {
281                model = "gpt-4"
282                temperature = 0.7
283            }
284        "#;
285        let compiler = Compiler::new(OptimizationLevel::Zero);
286        let result = compiler.compile_source(source, None);
287        assert!(result.is_ok());
288    }
289}