1mod comments;
16mod doc;
17mod rules;
18
19#[cfg(test)]
20mod tests;
21
22use std::fmt;
23
24use pine_ast::Program;
25use pine_core::{PineVersion, VersionError};
26use pine_lexer::{Lexer, LexerError};
27use pine_parser::{Parser, ParserError};
28
29const MAX_WIDTH: usize = 100;
31
32#[derive(Debug)]
34pub enum FormatError {
35 Version(VersionError),
36 Lex(LexerError),
37 Parse(ParserError),
38}
39
40impl fmt::Display for FormatError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 FormatError::Version(e) => write!(f, "{e}"),
44 FormatError::Lex(e) => write!(f, "{e}"),
45 FormatError::Parse(e) => write!(f, "{e}"),
46 }
47 }
48}
49
50impl std::error::Error for FormatError {}
51
52pub fn format(source: &str) -> Result<String, FormatError> {
55 let version = PineVersion::detect(source)
56 .map_err(FormatError::Version)?
57 .unwrap_or(PineVersion::LATEST);
58
59 let tokens = Lexer::with_version(source, version)
60 .tokenize()
61 .map_err(FormatError::Lex)?;
62
63 let statements = Parser::new(tokens.clone())
64 .parse()
65 .map_err(FormatError::Parse)?;
66 let program = Program::new(statements);
67
68 let comments = comments::Comments::extract(&tokens);
69 let document = rules::Rules::new(comments).program(&program);
70
71 let laid_out = doc::layout(&document, MAX_WIDTH);
72 if laid_out.is_empty() {
73 return Ok(laid_out);
74 }
75
76 let mut out: String = laid_out
78 .lines()
79 .map(str::trim_end)
80 .collect::<Vec<_>>()
81 .join("\n");
82 out.push('\n');
83 Ok(out)
84}