Move BibTeX formatter to syntax crate

This commit is contained in:
Patrick Förster 2019-12-12 10:20:42 +01:00
parent 864a109ab8
commit 4338b7315a
11 changed files with 14 additions and 16 deletions

View file

@ -14,3 +14,6 @@ serde = { version = "1.0.103", features = ["derive", "rc"] }
serde_json = "1.0.44"
texlab-distro = { path = "../texlab_distro" }
texlab-protocol = { path = "../texlab_protocol" }
[dev-dependencies]
indoc = "0.3.4"

View file

@ -0,0 +1,370 @@
use super::ast::*;
use crate::text::SyntaxNode;
use texlab_protocol::BibtexFormattingOptions;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BibtexFormattingParams {
pub tab_size: usize,
pub insert_spaces: bool,
pub options: BibtexFormattingOptions,
}
impl BibtexFormattingParams {
pub fn line_length(&self) -> i32 {
let line_length = self.options.line_length.unwrap_or(120);
if line_length <= 0 {
std::i32::MAX
} else {
line_length
}
}
}
impl Default for BibtexFormattingParams {
fn default() -> Self {
Self {
tab_size: 4,
insert_spaces: true,
options: BibtexFormattingOptions::default(),
}
}
}
struct BibtexFormatter<'a> {
params: &'a BibtexFormattingParams,
indent: String,
output: String,
}
impl<'a> BibtexFormatter<'a> {
fn new(params: &'a BibtexFormattingParams) -> Self {
let indent = if params.insert_spaces {
let mut buffer = String::new();
for _ in 0..params.tab_size {
buffer.push(' ');
}
buffer
} else {
"\t".into()
};
Self {
params,
indent,
output: String::new(),
}
}
fn format_comment(&mut self, comment: &BibtexComment) {
self.output.push_str(comment.token.text());
}
fn format_preamble(&mut self, preamble: &BibtexPreamble) {
self.format_token(&preamble.ty);
self.output.push('{');
if let Some(ref content) = preamble.content {
self.format_content(content, self.output.chars().count());
self.output.push('}');
}
}
fn format_string(&mut self, string: &BibtexString) {
self.format_token(&string.ty);
self.output.push('{');
if let Some(ref name) = string.name {
self.output.push_str(name.text());
self.output.push_str(" = ");
if let Some(ref value) = string.value {
self.format_content(value, self.output.chars().count());
self.output.push('}');
}
}
}
fn format_entry(&mut self, entry: &BibtexEntry) {
self.format_token(&entry.ty);
self.output.push('{');
if let Some(ref key) = entry.key {
self.output.push_str(key.text());
self.output.push(',');
self.output.push('\n');
for field in &entry.fields {
self.format_field(field);
}
self.output.push('}');
}
}
fn format_field(&mut self, field: &BibtexField) {
self.output.push_str(self.indent.as_ref());
self.format_token(&field.name);
self.output.push_str(" = ");
let count = field.name.text().chars().count();
let align = self.params.tab_size as usize + count + 3;
if let Some(ref content) = field.content {
self.format_content(content, align);
self.output.push(',');
self.output.push('\n');
}
}
fn format_content(&mut self, content: &BibtexContent, align: usize) {
let mut analyzer = BibtexContentAnalyzer::new();
content.accept(&mut analyzer);
let tokens = analyzer.tokens;
self.output.push_str(tokens[0].text());
let mut length = align + tokens[0].text().chars().count();
for i in 1..tokens.len() {
let previous = tokens[i - 1];
let current = tokens[i];
let current_length = current.text().chars().count();
let insert_space = Self::should_insert_space(previous, current);
let space_length = if insert_space { 1 } else { 0 };
if length + current_length + space_length > self.params.line_length() as usize {
self.output.push('\n');
self.output.push_str(self.indent.as_ref());
for _ in 0..=align - self.params.tab_size {
self.output.push(' ');
}
length = align;
} else if insert_space {
self.output.push(' ');
length += 1;
}
self.output.push_str(current.text());
length += current_length;
}
}
fn format_token(&mut self, token: &BibtexToken) {
self.output.push_str(token.text().to_lowercase().as_ref());
}
fn should_insert_space(previous: &BibtexToken, current: &BibtexToken) -> bool {
previous.start().line != current.start().line
|| previous.end().character < current.start().character
}
}
struct BibtexContentAnalyzer<'a> {
pub tokens: Vec<&'a BibtexToken>,
}
impl<'a> BibtexContentAnalyzer<'a> {
pub fn new() -> Self {
BibtexContentAnalyzer { tokens: Vec::new() }
}
}
impl<'a> BibtexVisitor<'a> for BibtexContentAnalyzer<'a> {
fn visit_root(&mut self, _root: &'a BibtexRoot) {}
fn visit_comment(&mut self, _comment: &'a BibtexComment) {}
fn visit_preamble(&mut self, _preamble: &'a BibtexPreamble) {}
fn visit_string(&mut self, _string: &'a BibtexString) {}
fn visit_entry(&mut self, _entry: &'a BibtexEntry) {}
fn visit_field(&mut self, _field: &'a BibtexField) {}
fn visit_word(&mut self, word: &'a BibtexWord) {
self.tokens.push(&word.token);
}
fn visit_command(&mut self, command: &'a BibtexCommand) {
self.tokens.push(&command.token);
}
fn visit_quoted_content(&mut self, content: &'a BibtexQuotedContent) {
self.tokens.push(&content.left);
BibtexWalker::walk_quoted_content(self, content);
if let Some(ref right) = content.right {
self.tokens.push(right);
}
}
fn visit_braced_content(&mut self, content: &'a BibtexBracedContent) {
self.tokens.push(&content.left);
BibtexWalker::walk_braced_content(self, content);
if let Some(ref right) = content.right {
self.tokens.push(right);
}
}
fn visit_concat(&mut self, concat: &'a BibtexConcat) {
concat.left.accept(self);
self.tokens.push(&concat.operator);
if let Some(ref right) = concat.right {
right.accept(self);
}
}
}
pub fn format_declaration(
declaration: &BibtexDeclaration,
params: &BibtexFormattingParams,
) -> String {
match declaration {
BibtexDeclaration::Comment(comment) => format_comment(comment, params),
BibtexDeclaration::Preamble(preamble) => format_preamble(preamble, params),
BibtexDeclaration::String(string) => format_string(string, params),
BibtexDeclaration::Entry(entry) => format_entry(entry, params),
}
}
pub fn format_comment(comment: &BibtexComment, params: &BibtexFormattingParams) -> String {
let mut formatter = BibtexFormatter::new(params);
formatter.format_comment(&comment);
formatter.output
}
pub fn format_preamble(preamble: &BibtexPreamble, params: &BibtexFormattingParams) -> String {
let mut formatter = BibtexFormatter::new(params);
formatter.format_preamble(&preamble);
formatter.output
}
pub fn format_string(string: &BibtexString, params: &BibtexFormattingParams) -> String {
let mut formatter = BibtexFormatter::new(params);
formatter.format_string(&string);
formatter.output
}
pub fn format_entry(entry: &BibtexEntry, params: &BibtexFormattingParams) -> String {
let mut formatter = BibtexFormatter::new(params);
formatter.format_entry(&entry);
formatter.output
}
pub fn format_content(content: &BibtexContent, params: &BibtexFormattingParams) -> String {
let mut formatter = BibtexFormatter::new(params);
formatter.format_content(content, 0);
formatter.output
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
fn verify(source: &str, expected: &str, line_length: i32) {
let tree = BibtexSyntaxTree::from(source);
let params = BibtexFormattingParams {
tab_size: 4,
insert_spaces: true,
options: BibtexFormattingOptions {
line_length: Some(line_length),
},
};
assert_eq!(
expected,
format_declaration(&tree.root.children[0], &params)
);
}
#[test]
fn test_wrap_long_lines() {
let source =
"@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}";
let expected = indoc!(
"
@article{foo,
bar = {Lorem ipsum dolor
sit amet,
consectetur
adipiscing elit.},
}"
);
verify(source, expected, 30);
}
#[test]
fn test_line_length_zero() {
let source =
"@article{foo, bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},}";
let expected = indoc!(
"
@article{foo,
bar = {Lorem ipsum dolor sit amet, consectetur adipiscing elit.},
}"
);
verify(source, expected, 0);
}
#[test]
fn test_trailing_commas() {
let source = "@article{foo, bar = baz}";
let expected = indoc!(
"
@article{foo,
bar = baz,
}"
);
verify(source, expected, 30);
}
#[test]
fn test_insert_braces() {
let source = "@article{foo, bar = baz,";
let expected = indoc!(
"
@article{foo,
bar = baz,
}"
);
verify(source, expected, 30);
}
#[test]
fn test_commands() {
let source = "@article{foo, bar = \"\\baz\",}";
let expected = indoc!(
"@article{foo,
bar = \"\\baz\",
}"
);
verify(source, expected, 30);
}
#[test]
fn test_concatenation() {
let source = "@article{foo, bar = \"baz\" # \"qux\"}";
let expected = indoc!(
"
@article{foo,
bar = \"baz\" # \"qux\",
}"
);
verify(source, expected, 30);
}
#[test]
fn test_parentheses() {
let source = "@article(foo,)";
let expected = indoc!(
"
@article{foo,
}"
);
verify(source, expected, 30);
}
#[test]
fn test_string() {
let source = "@string{foo=\"bar\"}";
let expected = "@string{foo = \"bar\"}";
verify(source, expected, 30);
}
#[test]
fn test_preamble() {
let source = "@preamble{\n\"foo bar baz\"}";
let expected = "@preamble{\"foo bar baz\"}";
verify(source, expected, 30);
}
}

View file

@ -1,10 +1,12 @@
mod ast;
mod finder;
mod formatting;
mod lexer;
mod parser;
pub use self::ast::*;
pub use self::finder::*;
pub use self::formatting::*;
use self::lexer::BibtexLexer;
use self::parser::BibtexParser;