1use crate::codegen::HelixIR;
2use crate::compiler::binary::{
3 HelixBinary, BinaryFlags, BinaryMetadata, DataSection, SectionType, SymbolTable,
4 Instruction, Value, CompressionMethod,
5};
6use std::path::Path;
7use std::fs::File;
8use std::io::{Write, Read};
9use bincode;
10pub struct BinarySerializer {
11 enable_compression: bool,
12 compression_method: CompressionMethod,
13}
14impl BinarySerializer {
15 pub fn new(enable_compression: bool) -> Self {
16 Self {
17 enable_compression,
18 compression_method: CompressionMethod::Lz4,
19 }
20 }
21 pub fn with_compression_method(mut self, method: CompressionMethod) -> Self {
22 self.compression_method = method;
23 self
24 }
25 pub fn serialize(
26 &self,
27 ir: HelixIR,
28 source_path: Option<&Path>,
29 ) -> Result<HelixBinary, SerializationError> {
30 let mut binary = HelixBinary::new();
31 binary.metadata = BinaryMetadata {
32 created_at: std::time::SystemTime::now()
33 .duration_since(std::time::UNIX_EPOCH)
34 .unwrap()
35 .as_secs(),
36 compiler_version: env!("CARGO_PKG_VERSION").to_string(),
37 source_hash: self.calculate_source_hash(&ir),
38 optimization_level: 2,
39 platform: format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH),
40 source_path: source_path.map(|p| p.display().to_string()),
41 extra: Default::default(),
42 };
43 binary.flags = BinaryFlags {
44 compressed: self.enable_compression,
45 optimized: true,
46 encrypted: false,
47 signed: false,
48 custom: 0,
49 };
50 binary.symbol_table = self.convert_symbol_table(&ir);
51 binary.data_sections = self.create_data_sections(&ir)?;
52 if self.enable_compression {
53 for section in &mut binary.data_sections {
54 section.compress(self.compression_method.clone())?;
55 }
56 }
57 binary.checksum = binary.calculate_checksum();
58 Ok(binary)
59 }
60 pub fn write_to_file(
61 &self,
62 binary: &HelixBinary,
63 path: &Path,
64 ) -> Result<(), SerializationError> {
65 let data = bincode::serialize(binary)
66 .map_err(|e| SerializationError::BincodeError(e.to_string()))?;
67 let mut file = File::create(path)
68 .map_err(|e| SerializationError::IoError(e.to_string()))?;
69 file.write_all(&data).map_err(|e| SerializationError::IoError(e.to_string()))?;
70 Ok(())
71 }
72 pub fn read_from_file(
73 &self,
74 path: &Path,
75 ) -> Result<HelixBinary, SerializationError> {
76 let mut file = File::open(path)
77 .map_err(|e| SerializationError::IoError(e.to_string()))?;
78 let mut data = Vec::new();
79 file.read_to_end(&mut data)
80 .map_err(|e| SerializationError::IoError(e.to_string()))?;
81 let binary: HelixBinary = bincode::deserialize(&data)
82 .map_err(|e| SerializationError::BincodeError(e.to_string()))?;
83 binary.validate().map_err(|e| SerializationError::ValidationError(e))?;
84 Ok(binary)
85 }
86 pub fn deserialize_to_ir(
87 &self,
88 binary: &HelixBinary,
89 ) -> Result<HelixIR, SerializationError> {
90 let mut ir = HelixIR {
91 version: binary.version,
92 metadata: self.convert_metadata(&binary.metadata),
93 symbol_table: self.convert_symbol_table_to_ir(&binary.symbol_table),
94 instructions: Vec::new(),
95 string_pool: crate::codegen::StringPool {
96 strings: binary.symbol_table.strings.clone(),
97 index: binary.symbol_table.string_map.clone(),
98 },
99 constants: crate::codegen::ConstantPool::new(),
100 };
101 for section in &binary.data_sections {
102 let mut section_clone = section.clone();
103 if section.compression.is_some() {
104 section_clone
105 .decompress()
106 .map_err(|e| SerializationError::DecompressionError(e))?;
107 }
108 match section.section_type {
109 SectionType::Instructions => {
110 ir.instructions = self
111 .deserialize_instructions(§ion_clone.data)?;
112 }
113 _ => {}
114 }
115 }
116 Ok(ir)
117 }
118 fn convert_symbol_table(&self, ir: &HelixIR) -> SymbolTable {
119 let mut table = SymbolTable::default();
120 table.strings = ir.string_pool.strings.clone();
121 for (i, s) in table.strings.iter().enumerate() {
122 table.string_map.insert(s.clone(), i as u32);
123 }
124 for (id, agent) in &ir.symbol_table.agents {
125 if let Some(name) = ir.string_pool.get(agent.name_idx) {
126 table.agents.insert(name.clone(), *id);
127 }
128 }
129 for (id, workflow) in &ir.symbol_table.workflows {
130 if let Some(name) = ir.string_pool.get(workflow.name_idx) {
131 table.workflows.insert(name.clone(), *id);
132 }
133 }
134 for (id, context) in &ir.symbol_table.contexts {
135 if let Some(name) = ir.string_pool.get(context.name_idx) {
136 table.contexts.insert(name.clone(), *id);
137 }
138 }
139 for (id, crew) in &ir.symbol_table.crews {
140 if let Some(name) = ir.string_pool.get(crew.name_idx) {
141 table.crews.insert(name.clone(), *id);
142 }
143 }
144 table
145 }
146 fn convert_symbol_table_to_ir(
147 &self,
148 table: &SymbolTable,
149 ) -> crate::codegen::SymbolTable {
150 use crate::codegen::{AgentSymbol, WorkflowSymbol, ContextSymbol, CrewSymbol};
151 use std::collections::HashMap;
152 let mut symbol_table = crate::codegen::SymbolTable::default();
153 for (name, id) in &table.agents {
154 let name_idx = table.string_map.get(name).copied().unwrap_or(0);
155 symbol_table
156 .agents
157 .insert(
158 *id,
159 AgentSymbol {
160 id: *id,
161 name_idx,
162 model_idx: 0,
163 role_idx: 0,
164 temperature: None,
165 max_tokens: None,
166 capabilities: Vec::new(),
167 backstory_idx: None,
168 },
169 );
170 }
171 for (name, id) in &table.workflows {
172 let name_idx = table.string_map.get(name).copied().unwrap_or(0);
173 symbol_table
174 .workflows
175 .insert(
176 *id,
177 WorkflowSymbol {
178 id: *id,
179 name_idx,
180 trigger_type: crate::codegen::TriggerType::Manual,
181 steps: Vec::new(),
182 pipeline: None,
183 },
184 );
185 }
186 for (name, id) in &table.contexts {
187 let name_idx = table.string_map.get(name).copied().unwrap_or(0);
188 symbol_table
189 .contexts
190 .insert(
191 *id,
192 ContextSymbol {
193 id: *id,
194 name_idx,
195 environment_idx: 0,
196 debug: false,
197 max_tokens: None,
198 secrets: HashMap::new(),
199 },
200 );
201 }
202 for (name, id) in &table.crews {
203 let name_idx = table.string_map.get(name).copied().unwrap_or(0);
204 symbol_table
205 .crews
206 .insert(
207 *id,
208 CrewSymbol {
209 id: *id,
210 name_idx,
211 agent_ids: Vec::new(),
212 process_type: crate::codegen::ProcessTypeIR::Sequential,
213 manager_id: None,
214 },
215 );
216 }
217 symbol_table
218 }
219 fn convert_metadata(&self, metadata: &BinaryMetadata) -> crate::codegen::Metadata {
220 crate::codegen::Metadata {
221 source_file: metadata.source_path.clone(),
222 compile_time: metadata.created_at,
223 compiler_version: metadata.compiler_version.clone(),
224 checksum: None,
225 }
226 }
227 fn create_data_sections(
228 &self,
229 ir: &HelixIR,
230 ) -> Result<Vec<DataSection>, SerializationError> {
231 let mut sections = Vec::new();
232 if !ir.instructions.is_empty() {
233 let instruction_data = self.serialize_instructions(&ir.instructions)?;
234 sections.push(DataSection::new(SectionType::Instructions, instruction_data));
235 }
236 if !ir.symbol_table.agents.is_empty() {
237 let agent_data = bincode::serialize(&ir.symbol_table.agents)
238 .map_err(|e| SerializationError::BincodeError(e.to_string()))?;
239 sections.push(DataSection::new(SectionType::Agents, agent_data));
240 }
241 if !ir.symbol_table.workflows.is_empty() {
242 let workflow_data = bincode::serialize(&ir.symbol_table.workflows)
243 .map_err(|e| SerializationError::BincodeError(e.to_string()))?;
244 sections.push(DataSection::new(SectionType::Workflows, workflow_data));
245 }
246 Ok(sections)
247 }
248 fn serialize_instructions(
249 &self,
250 instructions: &[crate::codegen::Instruction],
251 ) -> Result<Vec<u8>, SerializationError> {
252 let binary_instructions: Vec<Instruction> = instructions
253 .iter()
254 .map(|inst| self.convert_instruction(inst))
255 .collect();
256 bincode::serialize(&binary_instructions)
257 .map_err(|e| SerializationError::BincodeError(e.to_string()))
258 }
259 fn deserialize_instructions(
260 &self,
261 data: &[u8],
262 ) -> Result<Vec<crate::codegen::Instruction>, SerializationError> {
263 let binary_instructions: Vec<Instruction> = bincode::deserialize(data)
264 .map_err(|e| SerializationError::BincodeError(e.to_string()))?;
265 Ok(
266 binary_instructions
267 .iter()
268 .map(|inst| self.convert_instruction_to_ir(inst))
269 .collect(),
270 )
271 }
272 fn convert_instruction(&self, inst: &crate::codegen::Instruction) -> Instruction {
273 match inst {
274 crate::codegen::Instruction::DeclareAgent(id) => {
275 Instruction::InvokeAgent(*id)
276 }
277 crate::codegen::Instruction::DeclareWorkflow(_id) => Instruction::Nop,
278 crate::codegen::Instruction::DeclareContext(_id) => Instruction::Nop,
279 crate::codegen::Instruction::DeclareCrew(id) => Instruction::InvokeCrew(*id),
280 crate::codegen::Instruction::SetProperty { .. } => Instruction::Nop,
281 crate::codegen::Instruction::SetCapability { .. } => Instruction::Nop,
282 crate::codegen::Instruction::SetSecret { .. } => Instruction::Nop,
283 crate::codegen::Instruction::DefineStep { .. } => Instruction::Nop,
284 crate::codegen::Instruction::DefinePipeline { workflow, .. } => {
285 Instruction::Pipeline(*workflow)
286 }
287 crate::codegen::Instruction::ResolveReference { .. } => Instruction::Nop,
288 crate::codegen::Instruction::SetMetadata { .. } => Instruction::Nop,
289 }
290 }
291 fn convert_instruction_to_ir(
292 &self,
293 inst: &Instruction,
294 ) -> crate::codegen::Instruction {
295 match inst {
296 Instruction::InvokeAgent(id) => {
297 crate::codegen::Instruction::DeclareAgent(*id)
298 }
299 Instruction::InvokeCrew(id) => crate::codegen::Instruction::DeclareCrew(*id),
300 Instruction::Pipeline(id) => {
301 crate::codegen::Instruction::DeclareWorkflow(*id)
302 }
303 _ => crate::codegen::Instruction::DeclareAgent(0),
304 }
305 }
306 #[allow(dead_code)]
307 fn convert_value(&self, val: &crate::types::Value) -> Value {
308 match val {
309 crate::types::Value::Bool(b) => Value::Bool(*b),
310 crate::types::Value::Number(n) => Value::Float(*n),
311 crate::types::Value::String(_s) => {
312 let id = 0;
313 Value::String(id)
314 }
315 crate::types::Value::Duration(d) => {
316 let secs = match &d.unit {
317 crate::types::TimeUnit::Seconds => d.value,
318 crate::types::TimeUnit::Minutes => d.value * 60,
319 crate::types::TimeUnit::Hours => d.value * 3600,
320 crate::types::TimeUnit::Days => d.value * 86400,
321 } as u64;
322 Value::Duration(secs)
323 }
324 crate::types::Value::Array(_) => Value::Null,
325 crate::types::Value::Object(_) => Value::Null,
326 crate::types::Value::Reference(_) => Value::Null,
327 }
328 }
329 #[allow(dead_code)]
330 fn convert_value_to_ir(&self, val: &Value) -> crate::types::Value {
331 match val {
332 Value::Null => crate::types::Value::String(String::new()),
333 Value::Bool(b) => crate::types::Value::Bool(*b),
334 Value::Int(i) => crate::types::Value::Number(*i as f64),
335 Value::Float(f) => crate::types::Value::Number(*f),
336 Value::String(_id) => crate::types::Value::String(String::new()),
337 Value::Duration(secs) => {
338 crate::types::Value::Duration(crate::types::Duration {
339 value: (*secs / 60) as u64,
340 unit: crate::types::TimeUnit::Minutes,
341 })
342 }
343 Value::Reference(_id) => crate::types::Value::Reference(String::new()),
344 Value::Array(arr) => {
345 crate::types::Value::Array(
346 arr.iter().map(|v| self.convert_value_to_ir(v)).collect(),
347 )
348 }
349 Value::Object(obj) => {
350 let mut map = std::collections::HashMap::new();
351 for (key_idx, value) in obj {
352 let key = format!("key_{}", key_idx);
353 map.insert(key, self.convert_value_to_ir(value));
354 }
355 crate::types::Value::Object(map)
356 }
357 }
358 }
359 fn calculate_source_hash(&self, ir: &HelixIR) -> String {
360 use std::collections::hash_map::DefaultHasher;
361 use std::hash::{Hash, Hasher};
362 let mut hasher = DefaultHasher::new();
363 ir.version.hash(&mut hasher);
364 ir.string_pool.strings.len().hash(&mut hasher);
365 ir.instructions.len().hash(&mut hasher);
366 format!("{:x}", hasher.finish())
367 }
368}
369#[derive(Debug)]
370pub enum SerializationError {
371 IoError(String),
372 BincodeError(String),
373 CompressionError(String),
374 DecompressionError(String),
375 ValidationError(String),
376}
377impl std::fmt::Display for SerializationError {
378 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
379 match self {
380 Self::IoError(e) => write!(f, "I/O error: {}", e),
381 Self::BincodeError(e) => write!(f, "Bincode error: {}", e),
382 Self::CompressionError(e) => write!(f, "Compression error: {}", e),
383 Self::DecompressionError(e) => write!(f, "Decompression error: {}", e),
384 Self::ValidationError(e) => write!(f, "Validation error: {}", e),
385 }
386 }
387}
388impl std::error::Error for SerializationError {}
389impl From<String> for SerializationError {
390 fn from(s: String) -> Self {
391 Self::CompressionError(s)
392 }
393}
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use crate::codegen::{StringPool, Metadata, ConstantPool};
398 #[test]
399 fn test_serialization_roundtrip() {
400 let mut string_pool = StringPool::new();
401 string_pool.intern("test");
402 let ir = HelixIR {
403 version: 1,
404 metadata: Metadata::default(),
405 symbol_table: crate::codegen::SymbolTable::default(),
406 instructions: vec![
407 crate ::codegen::Instruction::DeclareAgent(1), crate
408 ::codegen::Instruction::DeclareWorkflow(2),
409 ],
410 string_pool,
411 constants: ConstantPool::default(),
412 };
413 let serializer = BinarySerializer::new(false);
414 let binary = serializer.serialize(ir.clone(), None).unwrap();
415 let deserialized = serializer.deserialize_to_ir(&binary).unwrap();
416 assert_eq!(ir.version, deserialized.version);
417 assert_eq!(ir.instructions.len(), deserialized.instructions.len());
418 }
419}