This repository has been archived on 2026-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-retired/_archive/rust-bootstrap/engrams/el-fmt/src/formatter.rs
T

543 lines
19 KiB
Rust

//! AST pretty-printer — the core of el-fmt.
use el_parser::{BinOp, Expr, Literal, MatchArm, Pattern, Program, Stmt, TypeExpr};
use crate::{FmtConfig, FmtError};
use crate::config::IndentStyle;
pub struct Formatter {
config: FmtConfig,
}
impl Formatter {
pub fn new(config: FmtConfig) -> Self {
Self { config }
}
pub fn format(&self, program: &Program) -> Result<String, FmtError> {
let mut out = String::new();
for (i, stmt) in program.stmts.iter().enumerate() {
if i > 0 {
out.push('\n');
}
self.fmt_stmt(&mut out, stmt, 0);
}
if self.config.trailing_newline && !out.ends_with('\n') {
out.push('\n');
}
Ok(out)
}
fn indent(&self, depth: usize) -> String {
match self.config.indent {
IndentStyle::Spaces => " ".repeat(depth * self.config.indent_width),
IndentStyle::Tabs => "\t".repeat(depth),
}
}
fn fmt_stmt(&self, out: &mut String, stmt: &Stmt, depth: usize) {
let ind = self.indent(depth);
match stmt {
Stmt::Let { name, type_ann, value, .. } => {
out.push_str(&ind);
out.push_str("let ");
out.push_str(name);
if let Some(ty) = type_ann {
out.push_str(": ");
out.push_str(&self.fmt_type(ty));
}
out.push_str(" = ");
self.fmt_expr(out, value, depth);
out.push('\n');
}
Stmt::Return(expr, _) => {
out.push_str(&format!("{ind}return "));
self.fmt_expr(out, expr, depth);
out.push('\n');
}
Stmt::Expr(expr, _) => {
out.push_str(&ind);
self.fmt_expr(out, expr, depth);
out.push('\n');
}
Stmt::FnDef { name, params, body, decorators, return_type, .. } => {
// Decorators
for dec in decorators {
out.push_str(&format!("{ind}@{}\n", dec.name));
}
// Parameters
let params_str: Vec<String> = params
.iter()
.map(|p| format!("{}: {}", p.name, self.fmt_type(&p.type_ann)))
.collect();
// Always emit return type — the parser requires `->`.
let ret = format!(" -> {}", self.fmt_type(return_type));
let brace_space = if self.config.space_before_brace { " " } else { "" };
out.push_str(&format!(
"{ind}fn {name}({}){}{brace_space}{{\n",
params_str.join(", "),
ret,
));
for s in body {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{ind}}}\n"));
}
Stmt::TypeDef { name, fields, .. } => {
out.push_str(&format!("{ind}type {name} {{\n"));
for f in fields {
out.push_str(&format!(
"{} {}: {}\n",
ind,
f.name,
self.fmt_type(&f.type_ann)
));
}
out.push_str(&format!("{ind}}}\n"));
}
Stmt::EnumDef { name, variants, .. } => {
out.push_str(&format!("{ind}enum {name} {{\n"));
for v in variants {
if let Some(payload) = &v.payload {
out.push_str(&format!(
"{} {}({})\n",
ind,
v.name,
self.fmt_type(payload)
));
} else {
out.push_str(&format!("{} {}\n", ind, v.name));
}
}
out.push_str(&format!("{ind}}}\n"));
}
Stmt::TestDef { name, body, .. } => {
out.push_str(&format!("{ind}test {:?} {{\n", name));
for s in body {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{ind}}}\n"));
}
Stmt::Assert(expr, _) => {
out.push_str(&format!("{ind}assert "));
self.fmt_expr(out, expr, depth);
out.push('\n');
}
Stmt::Import { path, names, alias, .. } => {
if names.is_empty() {
let joined = path.join("::");
if let Some(a) = alias {
out.push_str(&format!("{ind}import {joined} as {a}\n"));
} else {
out.push_str(&format!("{ind}import {joined}\n"));
}
} else {
let joined = path.join("::");
let items = names.join(", ");
out.push_str(&format!("{ind}from {joined} import {{ {items} }}\n"));
}
}
Stmt::ProtocolDef { name, methods, .. } => {
out.push_str(&format!("{ind}protocol {name} {{\n"));
for m in methods {
let params_str: Vec<String> = m
.params
.iter()
.map(|p| format!("{}: {}", p.name, self.fmt_type(&p.type_ann)))
.collect();
out.push_str(&format!(
"{} fn {}({}) -> {}\n",
ind,
m.name,
params_str.join(", "),
self.fmt_type(&m.return_type)
));
}
out.push_str(&format!("{ind}}}\n"));
}
Stmt::ImplDef { protocol_name, type_name, methods, .. } => {
out.push_str(&format!("{ind}impl {protocol_name} for {type_name} {{\n"));
for m in methods {
self.fmt_stmt(out, m, depth + 1);
}
out.push_str(&format!("{ind}}}\n"));
}
Stmt::Seed(seed, _) => {
use el_parser::SeedStmt;
match seed {
SeedStmt::Node { node_type, content, importance, tier } => {
let tier_str = tier
.as_deref()
.map(|t| format!(", tier: {t}"))
.unwrap_or_default();
out.push_str(&format!(
"{ind}seed {node_type} {{ content: {:?}, importance: {importance}{tier_str} }}\n",
content
));
}
SeedStmt::Edge { from, to, relation, weight } => {
out.push_str(&format!(
"{ind}seed Edge {{ from: {from}, to: {to}, relation: {relation:?}, weight: {weight} }}\n"
));
}
}
}
Stmt::Retry { count, body, fallback, .. } => {
out.push_str(&format!("{ind}retry "));
self.fmt_expr(out, count, depth);
out.push_str(" times {\n");
for s in body {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{ind}}}"));
if let Some(fb) = fallback {
out.push_str(" fallback {\n");
for s in fb {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{ind}}}"));
}
out.push('\n');
}
Stmt::Deploy { fn_name, route, target, .. } => {
out.push_str(&format!("{ind}deploy {fn_name} to \"{route}\" via {target}\n"));
}
Stmt::While { condition, body, .. } => {
out.push_str(&format!("{ind}while "));
self.fmt_expr(out, condition, depth);
out.push_str(" {\n");
for s in body {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{ind}}}\n"));
}
// Component definition (UI/reactive components) — emit as-is placeholder.
Stmt::ComponentDef { name, methods, .. } => {
out.push_str(&format!("{ind}component {name} {{\n"));
for s in methods {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{ind}}}\n"));
}
// Catch-all: unknown/future statement kinds are emitted as a comment.
#[allow(unreachable_patterns)]
_ => {
out.push_str(&format!("{ind}// [unformatted statement]\n"));
}
}
}
fn fmt_expr(&self, out: &mut String, expr: &Expr, depth: usize) {
match expr {
Expr::Literal(lit) => self.fmt_literal(out, lit),
Expr::Ident(name) => out.push_str(name),
Expr::Path { segments } => out.push_str(&segments.join("::")),
Expr::BinOp { op, left, right } => {
self.fmt_expr(out, left, depth);
out.push_str(&format!(" {} ", self.fmt_binop(op)));
self.fmt_expr(out, right, depth);
}
Expr::UnaryNot(inner) => {
out.push('!');
self.fmt_expr(out, inner, depth);
}
Expr::UnaryBitNot(inner) => {
out.push('~');
self.fmt_expr(out, inner, depth);
}
Expr::Try(inner) => {
self.fmt_expr(out, inner, depth);
out.push('?');
}
Expr::Call { func, args } => {
self.fmt_expr(out, func, depth);
out.push('(');
for (i, arg) in args.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
self.fmt_expr(out, arg, depth);
}
out.push(')');
}
Expr::Block(stmts) => {
out.push_str("{\n");
for s in stmts {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{}}}", self.indent(depth)));
}
Expr::If { cond, then, else_ } => {
out.push_str("if ");
self.fmt_expr(out, cond, depth);
out.push(' ');
self.fmt_expr(out, then, depth);
if let Some(else_expr) = else_ {
out.push_str(" else ");
self.fmt_expr(out, else_expr, depth);
}
}
Expr::Activate { type_name, query } => {
out.push_str(&format!("activate {type_name} where {:?}", query));
}
Expr::Field { object, field } => {
self.fmt_expr(out, object, depth);
out.push('.');
out.push_str(field);
}
Expr::Index { object, index } => {
self.fmt_expr(out, object, depth);
out.push('[');
self.fmt_expr(out, index, depth);
out.push(']');
}
Expr::Array(elems) => {
out.push('[');
for (i, e) in elems.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
self.fmt_expr(out, e, depth);
}
out.push(']');
}
Expr::MapLiteral(pairs) => {
out.push('{');
for (i, (k, v)) in pairs.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
self.fmt_expr(out, k, depth);
out.push_str(": ");
self.fmt_expr(out, v, depth);
}
out.push('}');
}
Expr::Sealed(stmts) => {
out.push_str("sealed {\n");
for s in stmts {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{}}}", self.indent(depth)));
}
Expr::Match { subject, arms } => {
out.push_str("match ");
self.fmt_expr(out, subject, depth);
out.push_str(" {\n");
for arm in arms {
self.fmt_match_arm(out, arm, depth);
}
out.push_str(&format!("{}}}", self.indent(depth)));
}
Expr::Closure { params, return_type, body, .. } => {
out.push('|');
let params_str: Vec<String> = params
.iter()
.map(|p| format!("{}: {}", p.name, self.fmt_type(&p.type_ann)))
.collect();
out.push_str(&params_str.join(", "));
out.push('|');
if let Some(rt) = return_type {
out.push_str(&format!(" -> {}", self.fmt_type(rt)));
}
out.push(' ');
self.fmt_expr(out, body, depth);
}
Expr::StructLit { type_name, fields, .. } => {
out.push_str(type_name);
out.push_str(" { ");
let fields_str: Vec<String> = fields
.iter()
.map(|(name, val)| {
let mut s = format!("{name}: ");
self.fmt_expr(&mut s, val, depth);
s
})
.collect();
out.push_str(&fields_str.join(", "));
out.push_str(" }");
}
Expr::With { base, updates } => {
self.fmt_expr(out, base, depth);
out.push_str(" with { ");
for (k, v) in updates {
out.push_str(&format!("{k}: "));
self.fmt_expr(out, v, depth);
out.push_str(", ");
}
out.push('}');
}
Expr::Reason { query } => {
out.push_str(&format!("reason {:?}", query));
}
Expr::Parallel { entries } => {
out.push_str("parallel { ");
for (name, e) in entries {
out.push_str(&format!("{name}: "));
self.fmt_expr(out, e, depth);
out.push_str(", ");
}
out.push('}');
}
Expr::Trace { label, body } => {
out.push_str(&format!("trace {:?} {{\n", label));
for s in body {
self.fmt_stmt(out, s, depth + 1);
}
out.push_str(&format!("{}}}", self.indent(depth)));
}
// JSX expressions — emit minimal JSX syntax.
Expr::JsxElement { tag, attrs, children, self_closing } => {
out.push('<');
out.push_str(tag);
for (key, val) in attrs {
out.push(' ');
out.push_str(key);
match val {
el_parser::JsxAttrValue::Str(s) => out.push_str(&format!("=\"{s}\"")),
el_parser::JsxAttrValue::Expr(e) => {
out.push_str("={");
self.fmt_expr(out, e, depth);
out.push('}');
}
}
}
if *self_closing {
out.push_str(" />");
} else {
out.push('>');
for child in children {
self.fmt_expr(out, child, depth);
}
out.push_str(&format!("</{tag}>"));
}
}
Expr::JsxExpr(inner) => {
out.push('{');
self.fmt_expr(out, inner, depth);
out.push('}');
}
Expr::JsxText(text) => {
out.push_str(text);
}
// Catch-all: unknown/future expression kinds.
#[allow(unreachable_patterns)]
_ => {
out.push_str("/* [unformatted expr] */");
}
}
}
fn fmt_literal(&self, out: &mut String, lit: &Literal) {
match lit {
Literal::Int(n) => out.push_str(&n.to_string()),
Literal::Float(f) => out.push_str(&f.to_string()),
Literal::Str(s) => out.push_str(&format!("{s:?}")),
Literal::Bool(b) => out.push_str(&b.to_string()),
}
}
fn fmt_binop(&self, op: &BinOp) -> &'static str {
match op {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Eq => "==",
BinOp::NotEq => "!=",
BinOp::Lt => "<",
BinOp::Gt => ">",
BinOp::LtEq => "<=",
BinOp::GtEq => ">=",
BinOp::And => "&&",
BinOp::Or => "||",
BinOp::Mod => "%",
BinOp::BitAnd => "&",
BinOp::BitOr => "|",
BinOp::BitXor => "^",
BinOp::Shl => "<<",
BinOp::Shr => ">>",
BinOp::NullCoalesce => "??",
}
}
fn fmt_match_arm(&self, out: &mut String, arm: &MatchArm, depth: usize) {
out.push_str(&format!("{} ", self.indent(depth)));
self.fmt_pattern(out, &arm.pattern);
out.push_str(" => ");
self.fmt_expr(out, &arm.body, depth + 1);
out.push('\n');
}
fn fmt_pattern(&self, out: &mut String, pat: &Pattern) {
match pat {
Pattern::Wildcard => out.push('_'),
Pattern::Binding(name) => out.push_str(name),
Pattern::Literal(lit) => self.fmt_literal(out, lit),
Pattern::EnumVariant { enum_name, variant, payload } => {
out.push_str(&format!("{enum_name}::"));
out.push_str(variant);
if let Some(bind) = payload {
out.push_str(&format!("({bind})"));
}
}
}
}
pub fn fmt_type(&self, ty: &TypeExpr) -> String {
match ty {
TypeExpr::Named(n) => n.clone(),
TypeExpr::Array(inner) => format!("[{}]", self.fmt_type(inner)),
TypeExpr::Optional(inner) => format!("{}?", self.fmt_type(inner)),
TypeExpr::Result { ok, err } => {
format!("Result<{}, {}>", self.fmt_type(ok), self.fmt_type(err))
}
TypeExpr::Map { key, value } => {
format!("Map<{}, {}>", self.fmt_type(key), self.fmt_type(value))
}
TypeExpr::Fn { params, return_type } => {
let ps: Vec<_> = params.iter().map(|p| self.fmt_type(p)).collect();
format!("fn({}) -> {}", ps.join(", "), self.fmt_type(return_type))
}
TypeExpr::TypeParam(n) => n.clone(),
}
}
}