Build a complete static site generator in Lux that faithfully clones
blu.cx (elmstatic). Generates 14 post pages, section indexes, tag pages,
and a home page with snippets grid from markdown content.
Language fixes discovered during development:
- Add \{ and \} escape sequences in string literals (lexer)
- Register String.indexOf and String.lastIndexOf in type checker
- Fix formatter to preserve brace escapes in string literals
- Improve LSP hover to show documentation for let bindings and functions
ISSUES.md documents 15 Lux language limitations found during the project.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
828 lines
27 KiB
Rust
828 lines
27 KiB
Rust
//! Code formatter for Lux
|
|
//!
|
|
//! Formats Lux source code according to standard style guidelines.
|
|
|
|
use crate::ast::{
|
|
BehavioralProperty, BinaryOp, Declaration, EffectDecl, Expr, FunctionDecl, HandlerDecl,
|
|
ImplDecl, ImplMethod, LetDecl, Literal, LiteralKind, Pattern, Program, Statement, TraitDecl,
|
|
TypeDecl, TypeDef, TypeExpr, UnaryOp, VariantFields,
|
|
};
|
|
use crate::lexer::Lexer;
|
|
use crate::parser::Parser;
|
|
|
|
/// Formatter configuration
|
|
#[derive(Debug, Clone)]
|
|
#[allow(dead_code)]
|
|
pub struct FormatConfig {
|
|
/// Number of spaces for indentation
|
|
pub indent_size: usize,
|
|
/// Maximum line width before wrapping
|
|
pub max_width: usize,
|
|
/// Add trailing commas in multi-line constructs
|
|
pub trailing_commas: bool,
|
|
}
|
|
|
|
impl Default for FormatConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
indent_size: 4,
|
|
max_width: 100,
|
|
trailing_commas: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Format Lux source code
|
|
pub fn format(source: &str, config: &FormatConfig) -> Result<String, String> {
|
|
// Parse the source
|
|
let lexer = Lexer::new(source);
|
|
let tokens = lexer.tokenize().map_err(|e| e.message)?;
|
|
let mut parser = Parser::new(tokens);
|
|
let program = parser.parse_program().map_err(|e| e.message)?;
|
|
|
|
// Format the AST
|
|
let mut formatter = Formatter::new(config.clone());
|
|
Ok(formatter.format_program(&program))
|
|
}
|
|
|
|
/// Formatter state
|
|
struct Formatter {
|
|
config: FormatConfig,
|
|
output: String,
|
|
indent_level: usize,
|
|
}
|
|
|
|
impl Formatter {
|
|
fn new(config: FormatConfig) -> Self {
|
|
Self {
|
|
config,
|
|
output: String::new(),
|
|
indent_level: 0,
|
|
}
|
|
}
|
|
|
|
fn indent(&self) -> String {
|
|
" ".repeat(self.indent_level * self.config.indent_size)
|
|
}
|
|
|
|
fn write(&mut self, s: &str) {
|
|
self.output.push_str(s);
|
|
}
|
|
|
|
fn writeln(&mut self, s: &str) {
|
|
self.output.push_str(s);
|
|
self.output.push('\n');
|
|
}
|
|
|
|
fn newline(&mut self) {
|
|
self.output.push('\n');
|
|
}
|
|
|
|
fn format_program(&mut self, program: &Program) -> String {
|
|
let mut first = true;
|
|
for decl in &program.declarations {
|
|
if !first {
|
|
self.newline();
|
|
}
|
|
first = false;
|
|
self.format_declaration(decl);
|
|
}
|
|
// Ensure file ends with newline
|
|
if !self.output.ends_with('\n') {
|
|
self.newline();
|
|
}
|
|
self.output.clone()
|
|
}
|
|
|
|
fn format_declaration(&mut self, decl: &Declaration) {
|
|
match decl {
|
|
Declaration::Function(f) => self.format_function(f),
|
|
Declaration::Let(l) => self.format_let(l),
|
|
Declaration::Type(t) => self.format_type_decl(t),
|
|
Declaration::Effect(e) => self.format_effect(e),
|
|
Declaration::Handler(h) => self.format_handler(h),
|
|
Declaration::Trait(t) => self.format_trait(t),
|
|
Declaration::Impl(i) => self.format_impl(i),
|
|
}
|
|
}
|
|
|
|
fn format_function(&mut self, func: &FunctionDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("fn ");
|
|
self.write(&func.name.name);
|
|
|
|
// Type parameters
|
|
if !func.type_params.is_empty() {
|
|
self.write("<");
|
|
self.write(
|
|
&func
|
|
.type_params
|
|
.iter()
|
|
.map(|p| p.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(">");
|
|
}
|
|
|
|
// Parameters
|
|
self.write("(");
|
|
let params: Vec<String> = func
|
|
.params
|
|
.iter()
|
|
.map(|p| format!("{}: {}", p.name.name, self.format_type_expr(&p.typ)))
|
|
.collect();
|
|
self.write(¶ms.join(", "));
|
|
self.write("): ");
|
|
|
|
// Return type
|
|
self.write(&self.format_type_expr(&func.return_type));
|
|
|
|
// Effects
|
|
if !func.effects.is_empty() {
|
|
self.write(" with {");
|
|
self.write(
|
|
&func
|
|
.effects
|
|
.iter()
|
|
.map(|e| e.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write("}");
|
|
}
|
|
|
|
// Properties
|
|
if !func.properties.is_empty() {
|
|
self.write(" is ");
|
|
self.write(
|
|
&func
|
|
.properties
|
|
.iter()
|
|
.map(|p| self.format_property(p))
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
}
|
|
|
|
self.write(" =");
|
|
|
|
// Body - handle blocks specially to keep `= {` on same line
|
|
if let Expr::Block { statements, result, .. } = &func.body {
|
|
self.write(" {");
|
|
self.newline();
|
|
self.indent_level += 1;
|
|
for stmt in statements {
|
|
let indent = self.indent();
|
|
match stmt {
|
|
Statement::Let { name, typ, value, .. } => {
|
|
let type_str = typ.as_ref()
|
|
.map(|t| format!(": {}", self.format_type_expr(t)))
|
|
.unwrap_or_default();
|
|
self.write(&indent);
|
|
self.writeln(&format!(
|
|
"let {}{} = {}",
|
|
name.name,
|
|
type_str,
|
|
self.format_expr(value)
|
|
));
|
|
}
|
|
Statement::Expr(e) => {
|
|
self.write(&indent);
|
|
self.writeln(&self.format_expr(e));
|
|
}
|
|
}
|
|
}
|
|
self.write(&self.indent());
|
|
self.writeln(&self.format_expr(result));
|
|
self.indent_level -= 1;
|
|
self.write(&self.indent());
|
|
self.writeln("}");
|
|
} else {
|
|
let body_str = self.format_expr(&func.body);
|
|
if body_str.contains('\n') {
|
|
self.newline();
|
|
self.indent_level += 1;
|
|
self.write(&self.indent());
|
|
self.write(&body_str);
|
|
self.indent_level -= 1;
|
|
self.newline();
|
|
} else {
|
|
self.write(" ");
|
|
self.writeln(&body_str);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_let(&mut self, let_decl: &LetDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("let ");
|
|
self.write(&let_decl.name.name);
|
|
|
|
if let Some(ref typ) = let_decl.typ {
|
|
self.write(": ");
|
|
self.write(&self.format_type_expr(typ));
|
|
}
|
|
|
|
self.write(" = ");
|
|
self.write(&self.format_expr(&let_decl.value));
|
|
self.newline();
|
|
}
|
|
|
|
fn format_type_decl(&mut self, type_decl: &TypeDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("type ");
|
|
self.write(&type_decl.name.name);
|
|
|
|
// Type parameters
|
|
if !type_decl.type_params.is_empty() {
|
|
self.write("<");
|
|
self.write(
|
|
&type_decl
|
|
.type_params
|
|
.iter()
|
|
.map(|p| p.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(">");
|
|
}
|
|
|
|
self.write(" =");
|
|
|
|
match &type_decl.definition {
|
|
TypeDef::Alias(t) => {
|
|
self.write(" ");
|
|
self.writeln(&self.format_type_expr(t));
|
|
}
|
|
TypeDef::Record(fields) => {
|
|
self.writeln(" {");
|
|
self.indent_level += 1;
|
|
for field in fields {
|
|
self.write(&self.indent());
|
|
self.write(&field.name.name);
|
|
self.write(": ");
|
|
self.write(&self.format_type_expr(&field.typ));
|
|
self.writeln(",");
|
|
}
|
|
self.indent_level -= 1;
|
|
self.write(&self.indent());
|
|
self.writeln("}");
|
|
}
|
|
TypeDef::Enum(variants) => {
|
|
self.newline();
|
|
self.indent_level += 1;
|
|
for variant in variants {
|
|
self.write(&self.indent());
|
|
self.write("| ");
|
|
self.write(&variant.name.name);
|
|
match &variant.fields {
|
|
VariantFields::Unit => {}
|
|
VariantFields::Tuple(types) => {
|
|
self.write("(");
|
|
self.write(
|
|
&types
|
|
.iter()
|
|
.map(|t| self.format_type_expr(t))
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(")");
|
|
}
|
|
VariantFields::Record(fields) => {
|
|
self.write(" { ");
|
|
self.write(
|
|
&fields
|
|
.iter()
|
|
.map(|f| format!("{}: {}", f.name.name, self.format_type_expr(&f.typ)))
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(" }");
|
|
}
|
|
}
|
|
self.newline();
|
|
}
|
|
self.indent_level -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_effect(&mut self, effect: &EffectDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("effect ");
|
|
self.write(&effect.name.name);
|
|
|
|
if !effect.type_params.is_empty() {
|
|
self.write("<");
|
|
self.write(
|
|
&effect
|
|
.type_params
|
|
.iter()
|
|
.map(|p| p.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(">");
|
|
}
|
|
|
|
self.writeln(" {");
|
|
self.indent_level += 1;
|
|
|
|
for op in &effect.operations {
|
|
self.write(&self.indent());
|
|
self.write("fn ");
|
|
self.write(&op.name.name);
|
|
self.write("(");
|
|
let params: Vec<String> = op
|
|
.params
|
|
.iter()
|
|
.map(|p| format!("{}: {}", p.name.name, self.format_type_expr(&p.typ)))
|
|
.collect();
|
|
self.write(¶ms.join(", "));
|
|
self.write("): ");
|
|
self.writeln(&self.format_type_expr(&op.return_type));
|
|
}
|
|
|
|
self.indent_level -= 1;
|
|
self.write(&self.indent());
|
|
self.writeln("}");
|
|
}
|
|
|
|
fn format_handler(&mut self, handler: &HandlerDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("handler ");
|
|
self.write(&handler.name.name);
|
|
self.write(": ");
|
|
self.write(&handler.effect.name);
|
|
self.writeln(" {");
|
|
self.indent_level += 1;
|
|
|
|
for impl_ in &handler.implementations {
|
|
self.write(&self.indent());
|
|
self.write("fn ");
|
|
self.write(&impl_.op_name.name);
|
|
self.write("(");
|
|
self.write(
|
|
&impl_
|
|
.params
|
|
.iter()
|
|
.map(|p| p.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(") = ");
|
|
|
|
let body_str = self.format_expr(&impl_.body);
|
|
if body_str.contains('\n') {
|
|
self.newline();
|
|
self.indent_level += 1;
|
|
self.write(&self.indent());
|
|
self.write(&body_str);
|
|
self.indent_level -= 1;
|
|
} else {
|
|
self.write(&body_str);
|
|
}
|
|
self.newline();
|
|
}
|
|
|
|
self.indent_level -= 1;
|
|
self.write(&self.indent());
|
|
self.writeln("}");
|
|
}
|
|
|
|
fn format_trait(&mut self, trait_decl: &TraitDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("trait ");
|
|
self.write(&trait_decl.name.name);
|
|
|
|
if !trait_decl.type_params.is_empty() {
|
|
self.write("<");
|
|
self.write(
|
|
&trait_decl
|
|
.type_params
|
|
.iter()
|
|
.map(|p| p.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
self.write(">");
|
|
}
|
|
|
|
self.writeln(" {");
|
|
self.indent_level += 1;
|
|
|
|
for method in &trait_decl.methods {
|
|
self.write(&self.indent());
|
|
self.write("fn ");
|
|
self.write(&method.name.name);
|
|
self.write("(");
|
|
let params: Vec<String> = method
|
|
.params
|
|
.iter()
|
|
.map(|p| format!("{}: {}", p.name.name, self.format_type_expr(&p.typ)))
|
|
.collect();
|
|
self.write(¶ms.join(", "));
|
|
self.write("): ");
|
|
self.writeln(&self.format_type_expr(&method.return_type));
|
|
}
|
|
|
|
self.indent_level -= 1;
|
|
self.write(&self.indent());
|
|
self.writeln("}");
|
|
}
|
|
|
|
fn format_impl(&mut self, impl_decl: &ImplDecl) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("impl ");
|
|
self.write(&impl_decl.trait_name.name);
|
|
self.write(" for ");
|
|
self.write(&self.format_type_expr(&impl_decl.target_type));
|
|
self.writeln(" {");
|
|
self.indent_level += 1;
|
|
|
|
for method in &impl_decl.methods {
|
|
self.format_impl_method(method);
|
|
}
|
|
|
|
self.indent_level -= 1;
|
|
self.write(&self.indent());
|
|
self.writeln("}");
|
|
}
|
|
|
|
fn format_impl_method(&mut self, method: &ImplMethod) {
|
|
let indent = self.indent();
|
|
self.write(&indent);
|
|
self.write("fn ");
|
|
self.write(&method.name.name);
|
|
self.write("(");
|
|
let params: Vec<String> = method
|
|
.params
|
|
.iter()
|
|
.map(|p| format!("{}: {}", p.name.name, self.format_type_expr(&p.typ)))
|
|
.collect();
|
|
self.write(¶ms.join(", "));
|
|
self.write(")");
|
|
|
|
if let Some(ref ret) = method.return_type {
|
|
self.write(": ");
|
|
self.write(&self.format_type_expr(ret));
|
|
}
|
|
|
|
self.write(" = ");
|
|
|
|
let body_str = self.format_expr(&method.body);
|
|
if self.is_block_expr(&method.body) || body_str.contains('\n') {
|
|
self.newline();
|
|
self.indent_level += 1;
|
|
self.write(&self.indent());
|
|
self.write(&body_str);
|
|
self.indent_level -= 1;
|
|
} else {
|
|
self.write(&body_str);
|
|
}
|
|
self.newline();
|
|
}
|
|
|
|
fn format_property(&self, prop: &BehavioralProperty) -> String {
|
|
match prop {
|
|
BehavioralProperty::Pure => "pure".to_string(),
|
|
BehavioralProperty::Total => "total".to_string(),
|
|
BehavioralProperty::Idempotent => "idempotent".to_string(),
|
|
BehavioralProperty::Deterministic => "deterministic".to_string(),
|
|
BehavioralProperty::Commutative => "commutative".to_string(),
|
|
}
|
|
}
|
|
|
|
fn format_type_expr(&self, typ: &TypeExpr) -> String {
|
|
match typ {
|
|
TypeExpr::Named(ident) => ident.name.clone(),
|
|
TypeExpr::App(base, args) => {
|
|
format!(
|
|
"{}<{}>",
|
|
self.format_type_expr(base),
|
|
args.iter()
|
|
.map(|a| self.format_type_expr(a))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
TypeExpr::Function { params, return_type, effects } => {
|
|
let mut s = format!(
|
|
"fn({}): {}",
|
|
params
|
|
.iter()
|
|
.map(|p| self.format_type_expr(p))
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
self.format_type_expr(return_type)
|
|
);
|
|
if !effects.is_empty() {
|
|
s.push_str(" with {");
|
|
s.push_str(
|
|
&effects
|
|
.iter()
|
|
.map(|e| e.name.clone())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
);
|
|
s.push('}');
|
|
}
|
|
s
|
|
}
|
|
TypeExpr::Tuple(types) => {
|
|
format!(
|
|
"({})",
|
|
types
|
|
.iter()
|
|
.map(|t| self.format_type_expr(t))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
TypeExpr::Record(fields) => {
|
|
format!(
|
|
"{{ {} }}",
|
|
fields
|
|
.iter()
|
|
.map(|f| format!("{}: {}", f.name.name, self.format_type_expr(&f.typ)))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
TypeExpr::Unit => "Unit".to_string(),
|
|
TypeExpr::Versioned { base, constraint } => {
|
|
let mut s = self.format_type_expr(base);
|
|
s.push_str(&format!(" {}", constraint));
|
|
s
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_block_expr(&self, expr: &Expr) -> bool {
|
|
matches!(expr, Expr::Block { .. } | Expr::Match { .. })
|
|
}
|
|
|
|
fn format_expr(&self, expr: &Expr) -> String {
|
|
match expr {
|
|
Expr::Literal(lit) => self.format_literal(lit),
|
|
Expr::Var(ident) => ident.name.clone(),
|
|
Expr::BinaryOp { left, op, right, .. } => {
|
|
format!(
|
|
"{} {} {}",
|
|
self.format_expr(left),
|
|
self.format_binop(op),
|
|
self.format_expr(right)
|
|
)
|
|
}
|
|
Expr::UnaryOp { op, operand, .. } => {
|
|
format!("{}{}", self.format_unaryop(op), self.format_expr(operand))
|
|
}
|
|
Expr::Call { func, args, .. } => {
|
|
format!(
|
|
"{}({})",
|
|
self.format_expr(func),
|
|
args.iter()
|
|
.map(|a| self.format_expr(a))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
Expr::Field { object, field, .. } => {
|
|
format!("{}.{}", self.format_expr(object), field.name)
|
|
}
|
|
Expr::If { condition, then_branch, else_branch, .. } => {
|
|
format!(
|
|
"if {} then {} else {}",
|
|
self.format_expr(condition),
|
|
self.format_expr(then_branch),
|
|
self.format_expr(else_branch)
|
|
)
|
|
}
|
|
Expr::Match { scrutinee, arms, .. } => {
|
|
let mut s = format!("match {} {{\n", self.format_expr(scrutinee));
|
|
for arm in arms {
|
|
s.push_str(&format!(
|
|
" {} => {},\n",
|
|
self.format_pattern(&arm.pattern),
|
|
self.format_expr(&arm.body)
|
|
));
|
|
}
|
|
s.push('}');
|
|
s
|
|
}
|
|
Expr::Block { statements, result, .. } => {
|
|
let mut s = String::from("{\n");
|
|
for stmt in statements {
|
|
match stmt {
|
|
Statement::Let { name, typ, value, .. } => {
|
|
let type_str = typ.as_ref()
|
|
.map(|t| format!(": {}", self.format_type_expr(t)))
|
|
.unwrap_or_default();
|
|
s.push_str(&format!(
|
|
" let {}{} = {}\n",
|
|
name.name,
|
|
type_str,
|
|
self.format_expr(value)
|
|
));
|
|
}
|
|
Statement::Expr(e) => {
|
|
s.push_str(&format!(" {}\n", self.format_expr(e)));
|
|
}
|
|
}
|
|
}
|
|
s.push_str(&format!(" {}\n", self.format_expr(result)));
|
|
s.push('}');
|
|
s
|
|
}
|
|
Expr::Lambda { params, body, return_type, .. } => {
|
|
let params_str: Vec<String> = params
|
|
.iter()
|
|
.map(|p| format!("{}: {}", p.name.name, self.format_type_expr(&p.typ)))
|
|
.collect();
|
|
let ret_str = return_type
|
|
.as_ref()
|
|
.map(|t| format!(": {}", self.format_type_expr(t)))
|
|
.unwrap_or_default();
|
|
format!("fn({}){} => {}", params_str.join(", "), ret_str, self.format_expr(body))
|
|
}
|
|
Expr::Let { name, value, body, typ, .. } => {
|
|
let type_str = typ.as_ref()
|
|
.map(|t| format!(": {}", self.format_type_expr(t)))
|
|
.unwrap_or_default();
|
|
format!(
|
|
"let {}{} = {}; {}",
|
|
name.name,
|
|
type_str,
|
|
self.format_expr(value),
|
|
self.format_expr(body)
|
|
)
|
|
}
|
|
Expr::List { elements, .. } => {
|
|
format!(
|
|
"[{}]",
|
|
elements
|
|
.iter()
|
|
.map(|e| self.format_expr(e))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
Expr::Tuple { elements, .. } => {
|
|
format!(
|
|
"({})",
|
|
elements
|
|
.iter()
|
|
.map(|e| self.format_expr(e))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
Expr::Record { fields, .. } => {
|
|
format!(
|
|
"{{ {} }}",
|
|
fields
|
|
.iter()
|
|
.map(|(name, val)| format!("{}: {}", name.name, self.format_expr(val)))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
Expr::EffectOp { effect, operation, args, .. } => {
|
|
format!(
|
|
"{}.{}({})",
|
|
effect.name,
|
|
operation.name,
|
|
args.iter()
|
|
.map(|a| self.format_expr(a))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
Expr::Run { expr, handlers, .. } => {
|
|
if handlers.is_empty() {
|
|
format!("run {} with {{}}", self.format_expr(expr))
|
|
} else {
|
|
let mut s = format!("run {} with {{\n", self.format_expr(expr));
|
|
for (effect, handler) in handlers {
|
|
s.push_str(&format!(" {} = {},\n", effect.name, self.format_expr(handler)));
|
|
}
|
|
s.push('}');
|
|
s
|
|
}
|
|
}
|
|
Expr::Resume { value, .. } => {
|
|
format!("resume({})", self.format_expr(value))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_literal(&self, lit: &Literal) -> String {
|
|
match &lit.kind {
|
|
LiteralKind::Int(n) => n.to_string(),
|
|
LiteralKind::Float(f) => format!("{}", f),
|
|
LiteralKind::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"").replace('{', "\\{").replace('}', "\\}")),
|
|
LiteralKind::Char(c) => format!("'{}'", c),
|
|
LiteralKind::Bool(b) => b.to_string(),
|
|
LiteralKind::Unit => "()".to_string(),
|
|
}
|
|
}
|
|
|
|
fn format_binop(&self, op: &BinaryOp) -> &'static str {
|
|
match op {
|
|
BinaryOp::Add => "+",
|
|
BinaryOp::Sub => "-",
|
|
BinaryOp::Mul => "*",
|
|
BinaryOp::Div => "/",
|
|
BinaryOp::Mod => "%",
|
|
BinaryOp::Eq => "==",
|
|
BinaryOp::Ne => "!=",
|
|
BinaryOp::Lt => "<",
|
|
BinaryOp::Le => "<=",
|
|
BinaryOp::Gt => ">",
|
|
BinaryOp::Ge => ">=",
|
|
BinaryOp::And => "&&",
|
|
BinaryOp::Or => "||",
|
|
BinaryOp::Pipe => "|>",
|
|
}
|
|
}
|
|
|
|
fn format_unaryop(&self, op: &UnaryOp) -> &'static str {
|
|
match op {
|
|
UnaryOp::Neg => "-",
|
|
UnaryOp::Not => "!",
|
|
}
|
|
}
|
|
|
|
fn format_pattern(&self, pattern: &Pattern) -> String {
|
|
match pattern {
|
|
Pattern::Wildcard(_) => "_".to_string(),
|
|
Pattern::Var(ident) => ident.name.clone(),
|
|
Pattern::Literal(lit) => self.format_literal(lit),
|
|
Pattern::Constructor { name, fields, .. } => {
|
|
if fields.is_empty() {
|
|
name.name.clone()
|
|
} else {
|
|
format!(
|
|
"{}({})",
|
|
name.name,
|
|
fields
|
|
.iter()
|
|
.map(|f| self.format_pattern(f))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
}
|
|
Pattern::Tuple { elements, .. } => {
|
|
format!(
|
|
"({})",
|
|
elements
|
|
.iter()
|
|
.map(|e| self.format_pattern(e))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
Pattern::Record { fields, .. } => {
|
|
format!(
|
|
"{{ {} }}",
|
|
fields
|
|
.iter()
|
|
.map(|(name, pat)| {
|
|
format!("{}: {}", name.name, self.format_pattern(pat))
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_format_simple_function() {
|
|
let source = "fn add( x:Int,y:Int ):Int=x+y";
|
|
let result = format(source, &FormatConfig::default()).unwrap();
|
|
assert!(result.contains("fn add(x: Int, y: Int): Int = x + y"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_let() {
|
|
let source = "let x=42";
|
|
let result = format(source, &FormatConfig::default()).unwrap();
|
|
assert!(result.contains("let x = 42"));
|
|
}
|
|
}
|