use crate::ast::{Side, WireOp};
use crate::error::Error;
use crate::lexer;
use crate::span::Span;
use crate::syntax::ast::{
Block, Child, Decl, Define, Endpoint, File, Node, Rule, SelPart, Selector, StyleItem, Value,
Wire, WireBlock,
};
use crate::syntax::parser;
mod trivia;
use trivia::{Trivia, TriviaToken, scan_trivia};
const INDENT: &str = " ";
const MAX_LINE: usize = 80;
pub fn format(src: &str) -> Result<String, Error> {
let tokens = lexer::lex(src)?;
let file = parser::parse(&tokens)?;
let trivia = scan_trivia(src);
let mut out = String::new();
Emitter {
trivia: &trivia,
cursor: 0,
out: &mut out,
align: true,
terse: true,
}
.emit_file(&file, src.len());
Ok(out)
}
pub(crate) fn print_file(file: &File) -> String {
let mut out = String::new();
Emitter {
trivia: &[],
cursor: 0,
out: &mut out,
align: false,
terse: false,
}
.emit_file(file, 0);
out
}
struct Emitter<'a> {
trivia: &'a [TriviaToken],
cursor: usize,
out: &'a mut String,
align: bool,
terse: bool,
}
impl Emitter<'_> {
fn emit_file(&mut self, file: &File, src_len: usize) {
let mut phases_emitted = 0;
if !file.stylesheet.is_empty() {
let items = &file.stylesheet;
let mut i = 0;
while i < items.len() {
if matches!(items[i], StyleItem::RootDecl(_)) {
let start = i;
while i < items.len() && matches!(items[i], StyleItem::RootDecl(_)) {
i += 1;
}
let run: Vec<&Decl> = items[start..i]
.iter()
.map(|it| match it {
StyleItem::RootDecl(d) => d,
_ => unreachable!(),
})
.collect();
self.emit_grouped_decls(&run, 0);
} else {
self.emit_trivia_before(style_item_span(&items[i]).start, 0);
self.emit_style_item(&items[i], 0);
self.cursor = style_item_span(&items[i]).end;
i += 1;
}
}
phases_emitted += 1;
}
if !file.instances.is_empty() {
self.section_break(phases_emitted);
self.emit_children(&file.instances, 0);
phases_emitted += 1;
}
if !file.wires.is_empty() {
self.section_break(phases_emitted);
for w in &file.wires {
self.emit_trivia_before(w.span.start, 0);
self.emit_wire(w, 0);
self.out.push('\n');
self.cursor = w.span.end;
}
}
self.emit_trivia_before(src_len, 0);
if self.out.is_empty() {
return;
}
if !self.out.ends_with('\n') {
self.out.push('\n');
}
}
fn section_break(&mut self, phases_emitted: usize) {
if phases_emitted > 0 && !self.out.is_empty() && !self.out.ends_with("\n\n") {
self.out.push('\n');
}
}
fn emit_style_item(&mut self, item: &StyleItem, depth: usize) {
match item {
StyleItem::RootDecl(d) => {
self.indent(depth);
self.emit_decl(d, false);
self.out.push('\n');
}
StyleItem::Var(d) => {
self.indent(depth);
self.emit_decl(d, true);
self.out.push('\n');
}
StyleItem::Rule(r) => self.emit_rule(r, depth),
StyleItem::Define(d) => self.emit_define(d, depth),
}
}
fn emit_rule(&mut self, rule: &Rule, depth: usize) {
self.indent(depth);
self.emit_selector(&rule.selector);
self.emit_decl_block(&rule.decls, rule.span.end, depth);
self.out.push('\n');
}
fn emit_define(&mut self, def: &Define, depth: usize) {
self.indent(depth);
self.out.push_str(&def.name);
self.out.push_str("::");
self.out.push_str(&def.base);
self.emit_block(&def.body, def.span.end, depth);
self.out.push('\n');
}
fn emit_selector(&mut self, sel: &Selector) {
if let [SelPart::Type(t)] = sel.parts.as_slice()
&& t == "wire"
{
self.out.push_str("->");
return;
}
for (i, part) in sel.parts.iter().enumerate() {
if i > 0 {
self.out.push(' ');
}
match part {
SelPart::Type(t) => self.out.push_str(t),
SelPart::Class(c) => {
self.out.push('.');
self.out.push_str(c);
}
}
}
}
fn emit_children(&mut self, children: &[Child], depth: usize) {
let widths = if self.align {
align::child_widths(children, self.trivia)
} else {
vec![align::NodeWidths::default(); children.len()]
};
for (i, c) in children.iter().enumerate() {
let span = child_span(c);
self.emit_trivia_before(span.start, depth);
match c {
Child::Box(n) => self.emit_node(n, depth, widths[i]),
Child::Text(t) => {
self.indent(depth);
self.emit_string(&t.text);
}
}
self.out.push('\n');
self.cursor = span.end;
}
}
fn table_cols(&self, block: &Block) -> Option<usize> {
let cells = &block.children;
if cells.is_empty() || !cells.iter().all(|c| matches!(c, Child::Text(_))) {
return None;
}
let start = child_span(&cells[0]).start;
let end = child_span(cells.last().unwrap()).end;
if self.has_trivia_between(start, end) {
return None;
}
count_columns(&block.decls)
}
fn emit_aligned_cells(&mut self, cells: &[Child], cols: usize, depth: usize) {
let texts: Vec<String> = cells
.iter()
.map(|c| match c {
Child::Text(t) => quoted(&t.text),
Child::Box(_) => String::new(), })
.collect();
let mut widths = vec![0usize; cols];
for (i, s) in texts.iter().enumerate() {
widths[i % cols] = widths[i % cols].max(s.len());
}
for (i, s) in texts.iter().enumerate() {
let col = i % cols;
if col == 0 {
self.indent(depth);
} else {
self.out.push(' ');
}
self.out.push_str(s);
if col == cols - 1 || i == texts.len() - 1 {
self.out.push('\n');
} else {
pad(self.out, widths[col] - s.len());
}
}
self.cursor = child_span(cells.last().unwrap()).end;
}
fn emit_node(&mut self, node: &Node, depth: usize, w: align::NodeWidths) {
self.indent(depth);
let mut wrote = false;
if let Some(id) = &node.id {
self.out.push_str(id);
pad(self.out, w.id.saturating_sub(id.len()));
wrote = true;
} else if w.id > 0 {
pad(self.out, w.id);
wrote = true;
}
if let Some(ty) = &node.ty {
self.space_if(wrote);
let t = format!("|{}|", ty);
self.out.push_str(&t);
pad(self.out, w.ty.saturating_sub(t.len()));
wrote = true;
} else if w.ty > 0 {
self.space_if(wrote);
pad(self.out, w.ty);
wrote = true;
}
for class in &node.classes {
self.space_if(wrote);
self.out.push('.');
self.out.push_str(class);
wrote = true;
}
if let Some(block) = &node.block
&& !self.try_trailing_labels(
&block.children,
block.decls.is_empty() && block.wires.is_empty(),
node.span.end,
)
{
self.emit_block(block, node.span.end, depth);
}
}
fn try_trailing_labels(&mut self, children: &[Child], no_config: bool, end: usize) -> bool {
let text_only =
!children.is_empty() && children.iter().all(|c| matches!(c, Child::Text(_)));
if !self.terse || !no_config || !text_only || self.has_trivia_between(self.cursor, end) {
return false;
}
for c in children {
if let Child::Text(t) = c {
self.out.push(' ');
self.emit_string(&t.text);
}
}
self.cursor = end;
true
}
fn space_if(&mut self, cond: bool) {
if cond {
self.out.push(' ');
}
}
fn emit_grouped_decls(&mut self, decls: &[&Decl], depth: usize) {
let mut mid_line = false;
for d in decls {
if mid_line && self.has_trivia_between(self.cursor, d.span.start) {
self.out.push('\n');
mid_line = false;
}
self.emit_trivia_before(d.span.start, depth);
if mid_line {
self.out.push(' ');
} else {
self.indent(depth);
}
self.emit_decl(d, false);
self.cursor = d.span.end;
mid_line = true;
}
if mid_line {
self.out.push('\n');
}
}
fn has_trivia_between(&self, start: usize, end: usize) -> bool {
self.trivia.iter().any(|t| t.pos >= start && t.pos < end)
}
fn try_inline(&mut self, decls: &[Decl], texts: &[&str], end: usize) -> bool {
if self.has_trivia_between(self.cursor, end) {
return false;
}
let line_start = self.out.rfind('\n').map_or(0, |i| i + 1);
let saved = self.out.len();
self.out.push_str(" { ");
let mut first = true;
for d in decls {
if !first {
self.out.push(' ');
}
self.emit_decl(d, false);
first = false;
}
for t in texts {
if !first {
self.out.push(' ');
}
self.emit_string(t);
first = false;
}
self.out.push_str(" }");
if self.out.len() - line_start <= MAX_LINE {
self.cursor = end;
true
} else {
self.out.truncate(saved);
false
}
}
fn emit_block(&mut self, block: &Block, end: usize, depth: usize) {
let empty = block.decls.is_empty() && block.children.is_empty() && block.wires.is_empty();
if empty && !self.has_comment_in(self.cursor, end) {
self.out.push_str(" {}");
self.cursor = end;
return;
}
let has_box = block.children.iter().any(|c| matches!(c, Child::Box(_)));
let table = self.table_cols(block);
let multi_row = matches!(table, Some(cols) if block.children.len() > cols);
if !has_box && block.wires.is_empty() && !multi_row {
let texts = text_strs(&block.children);
if self.try_inline(&block.decls, &texts, end) {
return;
}
}
self.out.push_str(" {\n");
let decls: Vec<&Decl> = block.decls.iter().collect();
self.emit_grouped_decls(&decls, depth + 1);
match table {
Some(cols) => self.emit_aligned_cells(&block.children, cols, depth + 1),
None => self.emit_children(&block.children, depth + 1),
}
for wire in &block.wires {
self.emit_trivia_before(wire.span.start, depth + 1);
self.emit_wire(wire, depth + 1);
self.out.push('\n');
self.cursor = wire.span.end;
}
self.emit_trivia_before(end, depth + 1);
self.indent(depth);
self.out.push('}');
}
fn emit_decl_block(&mut self, decls: &[Decl], end: usize, depth: usize) {
if decls.is_empty() && !self.has_comment_in(self.cursor, end) {
self.out.push_str(" {}");
self.cursor = end;
return;
}
if self.try_inline(decls, &[], end) {
return;
}
self.out.push_str(" {\n");
let refs: Vec<&Decl> = decls.iter().collect();
self.emit_grouped_decls(&refs, depth + 1);
self.emit_trivia_before(end, depth + 1);
self.indent(depth);
self.out.push('}');
}
fn emit_wire(&mut self, w: &Wire, depth: usize) {
self.indent(depth);
for (i, group) in w.chain.iter().enumerate() {
if i > 0 {
self.out.push(' ');
self.out.push_str(&wire_op_str(w.op));
self.out.push(' ');
}
for (j, ep) in group.endpoints.iter().enumerate() {
if j > 0 {
self.out.push_str(" & ");
}
self.emit_endpoint(ep);
}
}
for class in &w.classes {
self.out.push_str(" .");
self.out.push_str(class);
}
if let Some(block) = &w.block
&& !self.try_trailing_labels(&block.labels, block.decls.is_empty(), w.span.end)
{
self.emit_wire_block(block, w.span.end, depth);
}
}
fn emit_endpoint(&mut self, ep: &Endpoint) {
self.out.push_str(&ep.path.join("."));
if let Some(side) = ep.side {
self.out.push('.');
self.out.push_str(side_str(side));
}
}
fn emit_wire_block(&mut self, block: &WireBlock, end: usize, depth: usize) {
let empty = block.decls.is_empty() && block.labels.is_empty();
if empty && !self.has_comment_in(self.cursor, end) {
self.out.push_str(" {}");
self.cursor = end;
return;
}
let has_box = block.labels.iter().any(|c| matches!(c, Child::Box(_)));
if !has_box {
let texts = text_strs(&block.labels);
if self.try_inline(&block.decls, &texts, end) {
return;
}
}
self.out.push_str(" {\n");
let decls: Vec<&Decl> = block.decls.iter().collect();
self.emit_grouped_decls(&decls, depth + 1);
self.emit_children(&block.labels, depth + 1);
self.emit_trivia_before(end, depth + 1);
self.indent(depth);
self.out.push('}');
}
fn emit_decl(&mut self, decl: &Decl, is_var: bool) {
if is_var {
self.out.push_str("--");
}
self.out.push_str(&decl.name);
self.out.push_str(": ");
for (i, group) in decl.groups.iter().enumerate() {
if i > 0 {
self.out.push_str(", ");
}
for (j, v) in group.iter().enumerate() {
if j > 0 {
self.out.push(' ');
}
self.emit_value(v);
}
}
self.out.push(';');
}
fn emit_value(&mut self, v: &Value) {
match v {
Value::Number(n) => self.out.push_str(&format_number(*n)),
Value::String(s) => self.emit_string(s),
Value::Hex(h) => {
self.out.push('#');
self.out.push_str(h);
}
Value::Ident(s) => self.out.push_str(s),
Value::Var(name) => {
self.out.push_str("--");
self.out.push_str(name);
}
Value::Call(c) => {
self.out.push_str(&c.name);
self.out.push('(');
for (i, arg) in c.args.iter().enumerate() {
if i > 0 {
self.out.push_str(", ");
}
self.emit_value(arg);
}
self.out.push(')');
}
}
}
fn emit_string(&mut self, s: &str) {
self.out.push_str("ed(s));
}
fn indent(&mut self, depth: usize) {
for _ in 0..depth {
self.out.push_str(INDENT);
}
}
fn has_comment_in(&self, start: usize, end: usize) -> bool {
self.trivia
.iter()
.any(|t| matches!(t.kind, Trivia::Comment(_)) && t.pos >= start && t.pos < end)
}
fn emit_trivia_before(&mut self, until: usize, depth: usize) {
let mut last_was_blank = false;
for t in self.trivia {
if t.pos < self.cursor {
continue;
}
if t.pos >= until {
break;
}
match &t.kind {
Trivia::Comment(text) => {
self.indent(depth);
self.out.push_str(text);
self.out.push('\n');
last_was_blank = false;
}
Trivia::BlankLine => {
if !last_was_blank && !self.out.is_empty() && !self.out.ends_with("\n\n") {
self.out.push('\n');
last_was_blank = true;
}
}
}
}
self.cursor = until;
}
}
fn style_item_span(item: &StyleItem) -> Span {
match item {
StyleItem::RootDecl(d) | StyleItem::Var(d) => d.span,
StyleItem::Rule(r) => r.span,
StyleItem::Define(d) => d.span,
}
}
fn wire_op_str(op: WireOp) -> String {
format!(
"{}{}{}",
op.start.start_str(),
op.line.as_str(),
op.end.end_str()
)
}
fn side_str(s: Side) -> &'static str {
match s {
Side::Top => "top",
Side::Bottom => "bottom",
Side::Left => "left",
Side::Right => "right",
}
}
fn pad(out: &mut String, n: usize) {
for _ in 0..n {
out.push(' ');
}
}
fn child_span(c: &Child) -> Span {
match c {
Child::Box(n) => n.span,
Child::Text(t) => t.span,
}
}
fn quoted(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
_ => out.push(c),
}
}
out.push('"');
out
}
fn count_columns(decls: &[Decl]) -> Option<usize> {
let d = decls.iter().find(|d| d.name == "columns")?;
let n: usize = d
.groups
.iter()
.flatten()
.map(|v| match v {
Value::Call(c) if c.name == "repeat" => c
.args
.first()
.and_then(|a| match a {
Value::Number(x) if *x >= 1.0 => Some(*x as usize),
_ => None,
})
.unwrap_or(1),
_ => 1,
})
.sum();
(n > 0).then_some(n)
}
fn text_strs(children: &[Child]) -> Vec<&str> {
children
.iter()
.filter_map(|c| match c {
Child::Text(t) => Some(t.text.as_str()),
Child::Box(_) => None,
})
.collect()
}
fn format_number(n: f64) -> String {
if n.fract() == 0.0 && n.is_finite() && n.abs() < 1e15 {
format!("{}", n as i64)
} else {
format!("{}", n)
}
}
mod align;
#[cfg(test)]
mod tests;