Skip to main content

avm_rs/cli/commands/
assemble.rs

1//! Assemble command implementation
2
3use crate::assembler::Assembler;
4use crate::cli::{AssembleCommand, BytecodeFormat, GlobalOptions};
5use anyhow::{Context, Result};
6use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
7use std::fs;
8
9/// Handle the assemble command
10pub fn handle(cmd: AssembleCommand, global: &GlobalOptions) -> Result<()> {
11    if !global.quiet && global.verbose {
12        println!("šŸ”§ Assembling TEAL source...");
13        println!("Input: {:?}", cmd.input);
14        println!("Output format: {:?}", cmd.output_format);
15    }
16
17    // Read TEAL source
18    let source = fs::read_to_string(&cmd.input)
19        .with_context(|| format!("Failed to read TEAL file: {:?}", cmd.input))?;
20
21    // Assemble to bytecode
22    let mut assembler = Assembler::new();
23    let bytecode = assembler
24        .assemble(&source)
25        .map_err(|e| anyhow::anyhow!("Assembly failed: {}", e))?;
26
27    // Format bytecode
28    let formatted = format_bytecode(&bytecode, &cmd.output_format)?;
29
30    // Output result
31    if let Some(output_path) = &cmd.output {
32        fs::write(output_path, &formatted)
33            .with_context(|| format!("Failed to write output: {output_path:?}"))?;
34
35        if !global.quiet {
36            println!("āœ… Assembled {} bytes to {:?}", bytecode.len(), output_path);
37        }
38    } else {
39        // Output to stdout
40        println!("{formatted}");
41    }
42
43    // Show statistics if requested
44    if cmd.stats && !global.quiet {
45        show_assembly_stats(&bytecode, &source, global)?;
46    }
47
48    Ok(())
49}
50
51/// Format bytecode according to specified format
52fn format_bytecode(bytecode: &[u8], format: &BytecodeFormat) -> Result<String> {
53    match format {
54        BytecodeFormat::Hex => Ok(hex::encode(bytecode)),
55        BytecodeFormat::Base64 => Ok(BASE64_STANDARD.encode(bytecode)),
56        BytecodeFormat::Binary => {
57            // For binary output to stdout, we'll use hex representation
58            // True binary output only makes sense when writing to a file
59            Ok(hex::encode(bytecode))
60        }
61        BytecodeFormat::Auto => Ok(hex::encode(bytecode)), // Default to hex
62    }
63}
64
65/// Show assembly statistics
66fn show_assembly_stats(bytecode: &[u8], source: &str, global: &GlobalOptions) -> Result<()> {
67    let source_lines = source
68        .lines()
69        .filter(|line| {
70            let line = line.trim();
71            !line.is_empty()
72                && !line.starts_with("//")
73                && !line.starts_with(";")
74                && !line.starts_with("#pragma")
75        })
76        .count();
77
78    match global.format {
79        crate::cli::OutputFormat::Text => {
80            println!("\nšŸ“Š Assembly Statistics:");
81            println!("  Source lines: {source_lines}");
82            println!("  Bytecode size: {} bytes", bytecode.len());
83            println!(
84                "  Compression ratio: {:.2}x",
85                source.len() as f64 / bytecode.len() as f64
86            );
87
88            // Estimate cost (rough approximation)
89            let estimated_cost = estimate_execution_cost(bytecode);
90            println!("  Estimated cost: ~{estimated_cost} units");
91        }
92        crate::cli::OutputFormat::Json => {
93            let stats = serde_json::json!({
94                "source_lines": source_lines,
95                "bytecode_size": bytecode.len(),
96                "compression_ratio": source.len() as f64 / bytecode.len() as f64,
97                "estimated_cost": estimate_execution_cost(bytecode)
98            });
99            println!("{}", serde_json::to_string_pretty(&stats)?);
100        }
101    }
102
103    Ok(())
104}
105
106/// Estimate execution cost (rough approximation)
107fn estimate_execution_cost(bytecode: &[u8]) -> u64 {
108    // Very rough estimation: each byte approximately costs 1 unit
109    // Real cost depends on actual opcodes being executed
110    bytecode.len() as u64
111}