///|
/// Reindent and space tokens while retaining line breaks, comments, commas,
/// and exact literal spellings. In particular text-block indentation is data.
struct Layout {
out : StringBuilder
mut indent : Int32
mut breaks : Int32
mut started : Bool
mut previous : @syntax.TokenKind?
mut separated : Bool
}
///|
fn Layout::flush(self : Layout) -> Unit {
if self.breaks > 0 {
if self.started {
self.out.write_string("\n".repeat(Int32::min(self.breaks, 2)))
}
self.out.write_string(" ".repeat(Int32::max(self.indent, 0)))
self.breaks = 0
self.previous = None
self.separated = true
}
}
///|
fn Layout::trivia(self : Layout, trivia : ArrayView[@syntax.Trivia]) -> Unit {
for item in trivia {
match item {
Newline(_, _) => self.breaks += 1
Whitespace(_, _) => ()
Comma(_) => {
self.out.write_string(",")
self.separated = false
self.previous = None
}
Comment(_) => {
let same_line = self.breaks == 0 && self.started
self.flush()
if same_line {
self.out.write_string(" ")
}
self.out.write_string(item.text())
self.started = true
self.breaks = 0
self.previous = None
self.separated = true
}
}
}
}
///|
fn spaced(previous : @syntax.TokenKind?, current : @syntax.TokenKind) -> Bool {
match (previous, current) {
(_, Dot | Pound | Colon | RParen | RBracket) => false
(Some(At | Dot | Pound | Dollar | LParen | LBracket), _) => false
(_, LParen) => false
(Some(LBrace), RBrace) => false
(_, _) => true
}
}
///|
fn format_idl(parsed : @syntax.ParseResult) -> String {
let layout : Layout = {
out: StringBuilder(),
indent: 0,
breaks: 1,
started: false,
previous: None,
separated: true,
}
layout.trivia(parsed.cst.trivia)
for token in parsed.cst.tokens {
layout.trivia(token.trivia_before)
if token.kind is (RBrace | RBracket | RParen) {
layout.indent -= 1
}
let new_line = layout.breaks > 0
layout.flush()
if !new_line &&
layout.started &&
!layout.separated &&
spaced(layout.previous, token.kind) {
layout.out.write_string(" ")
}
layout.out.write_string(token.lexeme)
layout.started = true
layout.separated = false
layout.previous = Some(token.kind)
if token.kind is (LBrace | LBracket | LParen) {
layout.indent += 1
}
layout.trivia(token.trivia_after)
}
let text = layout.out.to_string()
if text.is_empty() {
""
} else {
text + "\n"
}
}
///|
fn format_text(
text : String,
name : String,
format : String,
) -> String raise CliError {
if format == "json" {
let ast = @syntax.parse_json_ast(text) catch {
error => raise CliError("\{name}: \{error}", 1)
}
return @syntax.encode_json_ast(ast).stringify(indent=4) + "\n"
}
let source = @syntax.SourceText::new(text, name~)
let parsed = @syntax.parse(source)
if parsed.has_errors() {
let messages = parsed.diagnostics.map(d => @syntax.render_short(d, source))
raise CliError(messages.join("\n"), 1)
}
format_idl(parsed)
}