1use std::collections::{HashMap, VecDeque};
2use std::path::Path;
3use crate::hel::binary::{HelixBinary, Value};
4use crate::hel::error::{RuntimeError, RuntimeErrorKind};
5use crate::atp::types::HelixConfig;
6use std::path::PathBuf;
7use std::process::Command;
8use anyhow::{Result, Context};
9use crate::dna::compiler::Compiler;
10use crate::dna::mds::optimizer::OptimizationLevel;
11pub use crate::mds::codegen::{PipelineNodeIR, ReferenceType, StepDefinition, SecretType, ConstantValue};
12
13pub struct HelixVM {
14 stack: Vec<Value>,
15 memory: HashMap<u32, Value>,
16 registers: VMRegisters,
17 config: HelixConfig,
18 call_stack: VecDeque<CallFrame>,
19 execution_state: ExecutionState,
20 debug_mode: bool,
21 breakpoints: HashMap<usize, Breakpoint>,
22}
23#[derive(Debug, Default)]
24pub struct VMRegisters {
25 pub program_counter: usize,
26 pub stack_pointer: usize,
27 pub frame_pointer: usize,
28 pub return_address: usize,
29 pub flags: VMFlags,
30}
31#[derive(Debug, Default)]
32pub struct VMFlags {
33 pub zero: bool,
34 pub overflow: bool,
35 pub error: bool,
36 pub halted: bool,
37}
38#[derive(Debug)]
39pub struct CallFrame {
40 pub return_address: usize,
41 pub frame_pointer: usize,
42 pub local_vars: HashMap<u32, Value>,
43}
44#[derive(Debug, PartialEq)]
45pub enum ExecutionState {
46 Ready,
47 Running,
48 Paused,
49 Halted,
50 Error(String),
51}
52#[derive(Debug)]
53pub struct Breakpoint {
54 pub active: bool,
55 pub condition: Option<String>,
56 pub hit_count: usize,
57}
58
59pub type VMResult<T> = Result<T, RuntimeError>;
60
61impl HelixVM {
62 pub fn new() -> Self {
63 Self {
64 stack: Vec::new(),
65 memory: HashMap::new(),
66 registers: VMRegisters::default(),
67 config: HelixConfig::default(),
68 call_stack: VecDeque::new(),
69 execution_state: ExecutionState::Ready,
70 debug_mode: false,
71 breakpoints: HashMap::new(),
72 }
73 }
74 pub fn with_debug(mut self) -> Self {
75 self.debug_mode = true;
76 self
77 }
78 pub fn execute_binary(&mut self, binary: &HelixBinary) -> VMResult<HelixConfig> {
79 let serializer = crate::dna::mds::serializer::BinarySerializer::new(false);
80 let ir = serializer
81 .deserialize_to_ir(binary)
82 .map_err(|e| RuntimeError {
83 kind: RuntimeErrorKind::InvalidInstruction,
84 message: format!("Failed to deserialize binary: {}", e),
85 stack_trace: vec![],
86 })?;
87 self.execution_state = ExecutionState::Running;
88 self.registers.program_counter = 0;
89 while self.registers.program_counter < ir.instructions.len()
90 && self.execution_state == ExecutionState::Running
91 {
92 if self.debug_mode {
93 if let Some(bp) = self
94 .breakpoints
95 .get_mut(&self.registers.program_counter)
96 {
97 if bp.active {
98 bp.hit_count += 1;
99 self.execution_state = ExecutionState::Paused;
100 break;
101 }
102 }
103 }
104 let instruction = &ir.instructions[self.registers.program_counter];
105 self.execute_instruction(instruction)?;
106 }
107 Ok(self.config.clone())
108 }
109 fn execute_instruction(
110 &mut self,
111 instruction: &super::codegen::Instruction,
112 ) -> VMResult<()> {
113 use super::codegen::Instruction as IR;
114 match instruction {
115 IR::DeclareAgent(id) => {
116 self.declare_agent(*id)?;
117 }
118 IR::DeclareWorkflow(id) => {
119 self.declare_workflow(*id)?;
120 }
121 IR::DeclareContext(id) => {
122 self.declare_context(*id)?;
123 }
124 IR::DeclareCrew(id) => {
125 self.declare_crew(*id)?;
126 }
127 IR::SetProperty { target, key, value } => {
128 self.set_property(*target, *key, value)?;
129 }
130 IR::SetCapability { agent, capability } => {
131 self.set_capability(*agent, *capability)?;
132 }
133 IR::SetSecret { context, key, secret } => {
134 self.set_secret(*context, *key, secret)?;
135 }
136 IR::DefineStep { workflow, step } => {
137 self.define_step(*workflow, step)?;
138 }
139 IR::DefinePipeline { workflow, nodes } => {
140 self.define_pipeline(*workflow, nodes)?;
141 }
142 IR::ResolveReference { ref_type, index } => {
143 self.resolve_reference(ref_type, *index)?;
144 }
145 IR::SetMetadata { key, value } => {
146 self.set_metadata(*key, *value)?;
147 }
148 }
149 self.registers.program_counter += 1;
150 Ok(())
151 }
152 fn declare_agent(&mut self, _id: u32) -> VMResult<()> {
153 Ok(())
154 }
155 fn declare_workflow(&mut self, _id: u32) -> VMResult<()> {
156 Ok(())
157 }
158 fn declare_context(&mut self, _id: u32) -> VMResult<()> {
159 Ok(())
160 }
161 fn declare_crew(&mut self, _id: u32) -> VMResult<()> {
162 Ok(())
163 }
164 fn set_property(
165 &mut self,
166 _target: u32,
167 _key: u32,
168 _value: &ConstantValue,
169 ) -> VMResult<()> {
170 Ok(())
171 }
172 fn set_capability(&mut self, _agent: u32, _capability: u32) -> VMResult<()> {
173 Ok(())
174 }
175 fn set_secret(
176 &mut self,
177 _context: u32,
178 _key: u32,
179 _secret: &SecretType,
180 ) -> VMResult<()> {
181 Ok(())
182 }
183 fn define_step(
184 &mut self,
185 _workflow: u32,
186 _step: &StepDefinition,
187 ) -> VMResult<()> {
188 Ok(())
189 }
190 fn define_pipeline(
191 &mut self,
192 _workflow: u32,
193 _nodes: &[PipelineNodeIR],
194 ) -> VMResult<()> {
195 Ok(())
196 }
197 fn resolve_reference(
198 &mut self,
199 _ref_type: &ReferenceType,
200 _index: u32,
201 ) -> VMResult<()> {
202 Ok(())
203 }
204 fn set_metadata(&mut self, _key: u32, _value: u32) -> VMResult<()> {
205 Ok(())
206 }
207 pub fn push(&mut self, value: Value) -> VMResult<()> {
208 if self.stack.len() >= 1024 {
209 return Err(RuntimeError {
210 kind: RuntimeErrorKind::StackOverflow,
211 message: "Stack overflow".to_string(),
212 stack_trace: self.get_stack_trace(),
213 });
214 }
215 self.stack.push(value);
216 self.registers.stack_pointer += 1;
217 Ok(())
218 }
219 pub fn pop(&mut self) -> VMResult<Value> {
220 if self.stack.is_empty() {
221 return Err(RuntimeError {
222 kind: RuntimeErrorKind::StackUnderflow,
223 message: "Stack underflow".to_string(),
224 stack_trace: self.get_stack_trace(),
225 });
226 }
227 self.registers.stack_pointer -= 1;
228 Ok(self.stack.pop().unwrap())
229 }
230 pub fn load_memory(&self, address: u32) -> VMResult<&Value> {
231 self.memory
232 .get(&address)
233 .ok_or_else(|| RuntimeError {
234 kind: RuntimeErrorKind::MemoryAccessViolation,
235 message: format!("Invalid memory access at address {}", address),
236 stack_trace: self.get_stack_trace(),
237 })
238 }
239 pub fn store_memory(&mut self, address: u32, value: Value) -> VMResult<()> {
240 self.memory.insert(address, value);
241 Ok(())
242 }
243 pub fn set_breakpoint(&mut self, address: usize) {
244 self.breakpoints
245 .insert(
246 address,
247 Breakpoint {
248 active: true,
249 condition: None,
250 hit_count: 0,
251 },
252 );
253 }
254 pub fn remove_breakpoint(&mut self, address: usize) {
255 self.breakpoints.remove(&address);
256 }
257 pub fn continue_execution(&mut self) {
258 if self.execution_state == ExecutionState::Paused {
259 self.execution_state = ExecutionState::Running;
260 }
261 }
262 pub fn step(&mut self) {
263 if self.execution_state == ExecutionState::Paused {
264 self.execution_state = ExecutionState::Running;
265 }
266 }
267 pub fn state(&self) -> &ExecutionState {
268 &self.execution_state
269 }
270 fn get_stack_trace(&self) -> Vec<String> {
271 let mut trace = Vec::new();
272 trace.push(format!("PC: {}", self.registers.program_counter));
273 for (i, frame) in self.call_stack.iter().enumerate() {
274 trace.push(format!("Frame {}: return address {}", i, frame.return_address));
275 }
276 trace
277 }
278 pub fn stats(&self) -> VMStats {
279 VMStats {
280 instructions_executed: self.registers.program_counter,
281 stack_size: self.stack.len(),
282 memory_usage: self.memory.len(),
283 call_depth: self.call_stack.len(),
284 }
285 }
286}
287#[derive(Debug)]
288pub struct VMStats {
289 pub instructions_executed: usize,
290 pub stack_size: usize,
291 pub memory_usage: usize,
292 pub call_depth: usize,
293}
294impl Default for HelixVM {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299pub struct VMExecutor {
300 vm: HelixVM,
301}
302impl VMExecutor {
303 pub fn new() -> Self {
304 Self { vm: HelixVM::new() }
305 }
306 pub fn execute_file<P: AsRef<Path>>(&mut self, path: P) -> VMResult<HelixConfig> {
307 let loader = crate::dna::mds::loader::BinaryLoader::new();
308 let binary = loader
309 .load_file(path.as_ref())
310 .map_err(|e| RuntimeError {
311 kind: RuntimeErrorKind::ResourceNotFound,
312 message: format!("Failed to load binary: {}", e),
313 stack_trace: vec![],
314 })?;
315 self.vm.execute_binary(&binary)
316 }
317 pub fn execute_with_debug<P: AsRef<Path>>(
318 &mut self,
319 path: P,
320 ) -> VMResult<HelixConfig> {
321 self.vm = HelixVM::new().with_debug();
322 self.execute_file(path)
323 }
324 pub fn vm(&mut self) -> &mut HelixVM {
325 &mut self.vm
326 }
327}
328impl Default for VMExecutor {
329 fn default() -> Self {
330 Self::new()
331 }
332}
333pub struct VMConfig {
334 pub max_stack_size: usize,
335 pub max_memory: usize,
336 pub max_call_depth: usize,
337 pub enable_gc: bool,
338 pub gc_threshold: usize,
339}
340impl Default for VMConfig {
341 fn default() -> Self {
342 Self {
343 max_stack_size: 1024,
344 max_memory: 65536,
345 max_call_depth: 256,
346 enable_gc: false,
347 gc_threshold: 1000,
348 }
349 }
350}
351#[cfg(test)]
352mod tests {
353 use super::*;
354 #[test]
355 fn test_vm_creation() {
356 let vm = HelixVM::new();
357 assert_eq!(vm.execution_state, ExecutionState::Ready);
358 assert!(vm.stack.is_empty());
359 assert!(vm.memory.is_empty());
360 }
361 #[test]
362 fn test_stack_operations() {
363 let mut vm = HelixVM::new();
364 vm.push(Value::Int(42)).unwrap();
365 assert_eq!(vm.stack.len(), 1);
366 assert_eq!(vm.registers.stack_pointer, 1);
367 let value = vm.pop().unwrap();
368 match value {
369 Value::Int(42) => {}
370 _ => panic!("Expected Int(42)"),
371 }
372 assert!(vm.stack.is_empty());
373 assert_eq!(vm.registers.stack_pointer, 0);
374 }
375 #[test]
376 fn test_memory_operations() {
377 let mut vm = HelixVM::new();
378 vm.store_memory(100, Value::Bool(true)).unwrap();
379 let value = vm.load_memory(100).unwrap();
380 match value {
381 Value::Bool(true) => {}
382 _ => panic!("Expected Bool(true)"),
383 }
384 }
385 #[test]
386 fn test_stack_overflow() {
387 let mut vm = HelixVM::new();
388 for _ in 0..1024 {
389 vm.push(Value::Int(1)).unwrap();
390 }
391 let result = vm.push(Value::Int(2));
392 assert!(result.is_err());
393 if let Err(e) = result {
394 assert_eq!(e.kind, RuntimeErrorKind::StackOverflow);
395 }
396 }
397 #[test]
398 fn test_stack_underflow() {
399 let mut vm = HelixVM::new();
400 let result = vm.pop();
401 assert!(result.is_err());
402 if let Err(e) = result {
403 assert_eq!(e.kind, RuntimeErrorKind::StackUnderflow);
404 }
405 }
406 #[test]
407 fn test_breakpoints() {
408 let mut vm = HelixVM::new().with_debug();
409 vm.set_breakpoint(10);
410 assert!(vm.breakpoints.contains_key(& 10));
411 vm.remove_breakpoint(10);
412 assert!(! vm.breakpoints.contains_key(& 10));
413 }
414 #[test]
415 fn test_vm_stats() {
416 let vm = HelixVM::new();
417 let stats = vm.stats();
418 assert_eq!(stats.instructions_executed, 0);
419 assert_eq!(stats.stack_size, 0);
420 assert_eq!(stats.memory_usage, 0);
421 assert_eq!(stats.call_depth, 0);
422 }
423}
424
425
426pub fn run_project(
427 input: Option<PathBuf>,
428 args: Vec<String>,
429 optimize: u8,
430 verbose: bool,
431) -> Result<()> {
432 let project_dir = find_project_root()?;
433 let input_file = match input {
434 Some(path) => path,
435 None => {
436 let main_file = project_dir.join("src").join("main.hlx");
437 if main_file.exists() {
438 main_file
439 } else {
440 return Err(
441 anyhow::anyhow!(
442 "No input file specified and no src/main.hlx found.\n\
443 Specify a file with: helix run <file.hlx>"
444 ),
445 );
446 }
447 }
448 };
449 if verbose {
450 println!("🚀 Running HELIX project:");
451 println!(" Input: {}", input_file.display());
452 println!(" Optimization: Level {}", optimize);
453 if !args.is_empty() {
454 println!(" Arguments: {:?}", args);
455 }
456 }
457 let output_file = compile_for_run(&input_file, optimize, verbose)?;
458 execute_binary(&output_file, args, verbose)?;
459 Ok(())
460}
461fn compile_for_run(input: &PathBuf, optimize: u8, verbose: bool) -> Result<PathBuf> {
462 let project_dir = find_project_root()?;
463 let target_dir = project_dir.join("target");
464 std::fs::create_dir_all(&target_dir).context("Failed to create target directory")?;
465 let input_stem = input
466 .file_stem()
467 .and_then(|s| s.to_str())
468 .ok_or_else(|| anyhow::anyhow!("Invalid input filename"))?;
469 let output_file = target_dir.join(format!("{}.hlxb", input_stem));
470 if verbose {
471 println!("📦 Compiling for execution...");
472 }
473 let compiler = Compiler::builder()
474 .optimization_level(OptimizationLevel::from(optimize))
475 .compression(true)
476 .cache(true)
477 .verbose(verbose)
478 .build();
479 let binary = compiler.compile_file(input).context("Failed to compile HELIX file")?;
480 let serializer = crate::dna::mds::serializer::BinarySerializer::new(true);
481 serializer
482 .write_to_file(&binary, &output_file)
483 .context("Failed to write compiled binary")?;
484 if verbose {
485 println!("✅ Compiled successfully: {}", output_file.display());
486 println!(" Size: {} bytes", binary.size());
487 }
488 Ok(output_file)
489}
490fn execute_binary(
491 binary_path: &PathBuf,
492 args: Vec<String>,
493 verbose: bool,
494) -> Result<()> {
495 if verbose {
496 println!("▶️ Executing binary: {}", binary_path.display());
497 }
498 let mut cmd = Command::new("echo");
499 cmd.arg("HELIX Runtime not yet implemented");
500 cmd.arg("Binary compiled successfully:");
501 cmd.arg(binary_path.to_string_lossy().as_ref());
502 if !args.is_empty() {
503 cmd.arg("Arguments:");
504 for arg in &args {
505 cmd.arg(arg);
506 }
507 }
508 let output = cmd.output().context("Failed to execute binary")?;
509 if !output.status.success() {
510 return Err(
511 anyhow::anyhow!(
512 "Binary execution failed with exit code: {}", output.status.code()
513 .unwrap_or(- 1)
514 ),
515 );
516 }
517 if !output.stdout.is_empty() {
518 print!("{}", String::from_utf8_lossy(& output.stdout));
519 }
520 if !output.stderr.is_empty() {
521 eprint!("{}", String::from_utf8_lossy(& output.stderr));
522 }
523 Ok(())
524}
525fn find_project_root() -> Result<PathBuf> {
526 let mut current_dir = std::env::current_dir()
527 .context("Failed to get current directory")?;
528 loop {
529 let manifest_path = current_dir.join("project.hlx");
530 if manifest_path.exists() {
531 return Ok(current_dir);
532 }
533 if let Some(parent) = current_dir.parent() {
534 current_dir = parent.to_path_buf();
535 } else {
536 break;
537 }
538 }
539 Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
540}