Archived
543 lines
19 KiB
Rust
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(¶ms_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(),
|
|
}
|
|
}
|
|
}
|