mod expr;
mod items;
mod stmts;

#[cfg(test)]
mod tests;

use crate::ast::*;
use crate::lexer::Comment;

pub(crate) use expr::{format_expr, format_type};

const MAX_WIDTH: usize = 80;
const INDENT: &str = "    ";

/// Format a parsed Trident file back to source, preserving comments.
pub(crate) fn format_file(file: &File, comments: &[Comment]) -> String {
    let mut ctx = FormatCtx::new(comments);
    ctx.emit_file(file);
    let mut out = ctx.output;
    // Ensure single trailing newline
    while out.ends_with("\n\n") {
        out.pop();
    }
    if !out.ends_with('\n') {
        out.push('\n');
    }
    out
}

pub(super) struct FormatCtx {
    pub(super) output: String,
    pub(super) comments: Vec<CommentEntry>,
}

#[derive(Clone)]
pub(super) struct CommentEntry {
    pub(super) text: String,
    pub(super) byte_offset: u32,
    pub(super) trailing: bool,
    pub(super) used: bool,
}

impl FormatCtx {
    fn new(comments: &[Comment]) -> Self {
        let entries = comments
            .iter()
            .map(|c| CommentEntry {
                text: c.text.clone(),
                byte_offset: c.span.start,
                trailing: c.trailing,
                used: false,
            })
            .collect();
        Self {
            output: String::new(),
            comments: entries,
        }
    }

    /// Emit leading comments that appear before `span_start`.
    pub(super) fn emit_leading_comments(&mut self, span_start: u32, indent: &str) {
        for i in 0..self.comments.len() {
            if self.comments[i].used || self.comments[i].trailing {
                continue;
            }
            if self.comments[i].byte_offset < span_start {
                let text = self.comments[i].text.clone();
                self.comments[i].used = true;
                self.output.push_str(indent);
                self.output.push_str(&text);
                self.output.push('\n');
            }
        }
    }

    /// Emit trailing comment on the same line as the node ending at `span_end`.
    pub(super) fn emit_trailing_comment(&mut self, span_end: u32) {
        for i in 0..self.comments.len() {
            if self.comments[i].used || !self.comments[i].trailing {
                continue;
            }
            if self.comments[i].byte_offset >= span_end {
                let text = self.comments[i].text.clone();
                self.comments[i].used = true;
                self.output.push(' ');
                self.output.push_str(&text);
                break;
            }
        }
    }

    /// Emit any remaining unused comments (e.g., at end of file).
    fn emit_remaining_comments(&mut self, indent: &str) {
        for i in 0..self.comments.len() {
            if self.comments[i].used {
                continue;
            }
            self.comments[i].used = true;
            self.output.push_str(indent);
            self.output.push_str(&self.comments[i].text.clone());
            self.output.push('\n');
        }
    }

    fn emit_file(&mut self, file: &File) {
        let keyword = match file.kind {
            FileKind::Program => "program",
            FileKind::Module => "module",
        };
        self.emit_leading_comments(file.name.span.start, "");
        self.output.push_str(keyword);
        self.output.push(' ');
        self.output.push_str(&file.name.node);
        self.output.push('\n');

        for u in &file.uses {
            self.output.push('\n');
            self.emit_leading_comments(u.span.start, "");
            self.output.push_str("use ");
            self.output.push_str(&u.node.as_dotted());
            self.output.push('\n');
        }

        for decl in &file.declarations {
            self.output.push('\n');
            match decl {
                Declaration::PubInput(ty) => {
                    self.emit_leading_comments(ty.span.start, "");
                    self.output.push_str("pub input: ");
                    self.output.push_str(&format_type(&ty.node));
                    self.output.push('\n');
                }
                Declaration::PubOutput(ty) => {
                    self.emit_leading_comments(ty.span.start, "");
                    self.output.push_str("pub output: ");
                    self.output.push_str(&format_type(&ty.node));
                    self.output.push('\n');
                }
                Declaration::SecInput(ty) => {
                    self.emit_leading_comments(ty.span.start, "");
                    self.output.push_str("sec input: ");
                    self.output.push_str(&format_type(&ty.node));
                    self.output.push('\n');
                }
                Declaration::SecRam(entries) => {
                    self.output.push_str("sec ram: {\n");
                    for (addr, ty) in entries {
                        self.output.push_str(&format!(
                            "    {}: {},\n",
                            addr,
                            format_type(&ty.node)
                        ));
                    }
                    self.output.push_str("}\n");
                }
            }
        }

        for item in &file.items {
            self.output.push('\n');
            self.emit_item(item, "");
        }

        self.emit_remaining_comments("");
    }

    pub(super) fn emit_block(&mut self, block: &Block, outer_indent: &str) {
        let indent = format!("{}{}", outer_indent, INDENT);
        for stmt in &block.stmts {
            self.emit_stmt(stmt, &indent);
        }
        if let Some(tail) = &block.tail_expr {
            self.emit_leading_comments(tail.span.start, &indent);
            self.output.push_str(&indent);
            self.emit_expr_wrapped(&tail.node, &indent);
            self.output.push('\n');
        }
    }

    /// Format an expression, wrapping long function calls.
    pub(super) fn emit_expr_wrapped(&mut self, expr: &Expr, indent: &str) {
        let flat = format_expr(expr);
        let current_line_len = self.current_line_len();
        if current_line_len + flat.len() <= MAX_WIDTH {
            self.output.push_str(&flat);
        } else if let Expr::Call { path, args, .. } = expr {
            if args.is_empty() {
                self.output.push_str(&flat);
            } else {
                let arg_indent = format!("{}{}", indent, INDENT);
                self.output.push_str(&path.node.as_dotted());
                self.output.push_str("(\n");
                for (i, arg) in args.iter().enumerate() {
                    self.output.push_str(&arg_indent);
                    self.output.push_str(&format_expr(&arg.node));
                    if i + 1 < args.len() {
                        self.output.push(',');
                    }
                    self.output.push('\n');
                }
                self.output.push_str(indent);
                self.output.push(')');
            }
        } else {
            self.output.push_str(&flat);
        }
    }

    pub(super) fn current_line_len(&self) -> usize {
        match self.output.rfind('\n') {
            Some(pos) => self.output.len() - pos - 1,
            None => self.output.len(),
        }
    }
}

Dimensions

trident/src/diagnostic/mod.rs
trident/src/ir/mod.rs
trident/src/deploy/mod.rs
trident/src/syntax/mod.rs
trident/src/api/mod.rs
nebu/rs/extension/mod.rs
optica/src/render/mod.rs
trident/src/config/mod.rs
trident/src/field/mod.rs
trident/src/cli/mod.rs
optica/src/parser/mod.rs
trident/src/neural/mod.rs
trident/src/cost/mod.rs
trident/src/typecheck/mod.rs
optica/src/server/mod.rs
trident/src/package/mod.rs
optica/src/scanner/mod.rs
optica/src/output/mod.rs
trident/src/verify/mod.rs
optica/src/graph/mod.rs
trident/src/ast/mod.rs
trident/src/lsp/mod.rs
trident/src/runtime/mod.rs
trident/src/gpu/mod.rs
optica/src/query/mod.rs
trident/src/lsp/semantic/mod.rs
trident/src/verify/equiv/mod.rs
trident/src/package/hash/mod.rs
trident/src/neural/training/mod.rs
trident/src/verify/synthesize/mod.rs
trident/src/ir/tir/mod.rs
rs/macros/src/addressed/mod.rs
trident/src/package/registry/mod.rs
rs/rsc/src/lints/mod.rs
trident/src/verify/report/mod.rs
trident/src/config/resolve/mod.rs
trident/src/verify/solve/mod.rs
rs/macros/src/registers/mod.rs
trident/src/verify/smt/mod.rs
rs/macros/src/cell/mod.rs
rs/core/src/fixed_point/mod.rs
trident/src/neural/data/mod.rs
rs/core/src/bounded/mod.rs
trident/src/lsp/util/mod.rs
trident/src/typecheck/tests/mod.rs
trident/src/neural/model/mod.rs
trident/src/cost/stack_verifier/mod.rs
trident/src/syntax/grammar/mod.rs
trident/src/package/manifest/mod.rs
trident/src/syntax/parser/mod.rs
trident/src/ir/kir/mod.rs
trident/src/neural/inference/mod.rs
trident/src/syntax/lexer/mod.rs
trident/src/cost/model/mod.rs
trident/src/ir/lir/mod.rs
trident/src/config/scaffold/mod.rs
trident/src/verify/sym/mod.rs
trident/src/api/tests/mod.rs
trident/src/package/store/mod.rs
trident/src/ir/tree/mod.rs
trident/src/ir/kir/lower/mod.rs
trident/src/ir/lir/lower/mod.rs
trident/src/ir/tir/lower/mod.rs
trident/src/ir/tir/builder/mod.rs
trident/src/ir/tir/neural/mod.rs
trident/src/neural/data/tir_graph/mod.rs
trident/src/syntax/parser/tests/mod.rs
cw-cyber/packages/cyber-std/src/tokenfactory/mod.rs
trident/src/ir/tree/lower/mod.rs
trident/src/ir/tir/stack/mod.rs
cw-cyber/contracts/cybernet/src/tests/mod.rs
trident/src/ir/tir/optimize/mod.rs

Local Graph