oxur-ast 0.2.0

Rust AST ↔ S-expression conversion for Oxur
Documentation
//! Core Rust code generation engine
//!
//! This module implements the `RustCodegen` struct which manages the
//! code generation process, including indentation tracking and output
//! buffer management.

use crate::ast::Crate;
use anyhow::Result;
use std::fmt::Write;

/// Main code generator for transforming Oxur AST into Rust source code
///
/// `RustCodegen` maintains the output buffer and tracks indentation level
/// during code generation. It provides methods to generate code for all
/// Rust AST node types.
///
/// # Example
///
/// ```no_run
/// use oxur_ast::gen_rs::RustCodegen;
/// use oxur_ast::ast::Crate;
///
/// # fn example(crate_node: Crate) -> anyhow::Result<()> {
/// let mut codegen = RustCodegen::new();
/// let rust_code = codegen.generate_crate(&crate_node)?;
/// println!("{}", rust_code);
/// # Ok(())
/// # }
/// ```
pub struct RustCodegen {
    /// Output buffer containing generated Rust code
    output: String,
    /// Current indentation level (in number of 4-space units)
    indent: usize,
    /// Whether to include the "Generated by" header comment
    include_header: bool,
}

impl RustCodegen {
    /// Create a new code generator with empty output and zero indentation
    ///
    /// By default, does not include a header comment. Use `with_header()` to enable it.
    pub fn new() -> Self {
        Self { output: String::new(), indent: 0, include_header: false }
    }

    /// Create a new code generator with header comment enabled
    pub fn with_header() -> Self {
        Self { output: String::new(), indent: 0, include_header: true }
    }

    /// Increase indentation level by one unit (4 spaces)
    pub(crate) fn indent(&mut self) {
        self.indent += 1;
    }

    /// Decrease indentation level by one unit (4 spaces)
    ///
    /// # Panics
    ///
    /// Panics if indent level is already at 0 (indicates a codegen bug)
    pub(crate) fn dedent(&mut self) {
        assert!(self.indent > 0, "Cannot dedent below 0");
        self.indent -= 1;
    }

    /// Write the current indentation to the output buffer
    ///
    /// Uses 4 spaces per indentation level, following Rust style guidelines.
    pub(crate) fn write_indent(&mut self) {
        for _ in 0..self.indent {
            self.output.push_str("    ");
        }
    }

    /// Write a string to the output buffer
    pub(crate) fn write(&mut self, s: &str) {
        self.output.push_str(s);
    }

    /// Write a newline to the output buffer
    pub(crate) fn writeln(&mut self) {
        self.output.push('\n');
    }

    /// Write a formatted string to the output buffer
    ///
    /// Uses the same interface as `write!()` macro but writes to our buffer.
    #[allow(dead_code)] // Will be used in later stages
    fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<()> {
        write!(&mut self.output, "{}", args)?;
        Ok(())
    }

    /// Get the generated output as a string (for testing)
    #[cfg(test)]
    pub(crate) fn output(&self) -> &str {
        &self.output
    }

    /// Generate Rust code for a Crate node
    ///
    /// This is the main entry point for code generation. It processes
    /// all items in the crate and generates formatted Rust code.
    ///
    /// # Arguments
    ///
    /// * `crate_node` - The Crate AST node to generate code from
    ///
    /// # Returns
    ///
    /// Returns the generated Rust code as a String, or an error if
    /// code generation fails.
    pub fn generate_crate(&mut self, crate_node: &Crate) -> Result<String> {
        // Optionally generate header comment
        if self.include_header {
            self.write("// Generated by Oxur AST codegen\n");
            self.writeln();
        }

        // Generate each item in the crate
        for (i, item) in crate_node.items.iter().enumerate() {
            // Add blank line between items (but not before first item)
            if i > 0 {
                self.writeln();
            }
            self.generate_item(item)?;
        }

        Ok(self.output.clone())
    }
}

impl Default for RustCodegen {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_codegen() {
        let codegen = RustCodegen::new();
        assert_eq!(codegen.output, "");
        assert_eq!(codegen.indent, 0);
    }

    #[test]
    fn test_indent_dedent() {
        let mut codegen = RustCodegen::new();
        assert_eq!(codegen.indent, 0);

        codegen.indent();
        assert_eq!(codegen.indent, 1);

        codegen.indent();
        assert_eq!(codegen.indent, 2);

        codegen.dedent();
        assert_eq!(codegen.indent, 1);

        codegen.dedent();
        assert_eq!(codegen.indent, 0);
    }

    #[test]
    #[should_panic(expected = "Cannot dedent below 0")]
    fn test_dedent_panic() {
        let mut codegen = RustCodegen::new();
        codegen.dedent(); // Should panic
    }

    #[test]
    fn test_write_indent() {
        let mut codegen = RustCodegen::new();

        codegen.write_indent();
        assert_eq!(codegen.output, "");

        codegen.indent();
        codegen.write_indent();
        assert_eq!(codegen.output, "    ");

        codegen.output.clear();
        codegen.indent();
        codegen.write_indent();
        assert_eq!(codegen.output, "        "); // 2 * 4 spaces
    }

    #[test]
    fn test_write() {
        let mut codegen = RustCodegen::new();
        codegen.write("hello");
        codegen.write(" ");
        codegen.write("world");
        assert_eq!(codegen.output, "hello world");
    }

    #[test]
    fn test_writeln() {
        let mut codegen = RustCodegen::new();
        codegen.write("line1");
        codegen.writeln();
        codegen.write("line2");
        assert_eq!(codegen.output, "line1\nline2");
    }

    #[test]
    fn test_write_fmt() {
        let mut codegen = RustCodegen::new();
        codegen.write_fmt(format_args!("Value: {}", 42)).unwrap();
        assert_eq!(codegen.output, "Value: 42");
    }
}