Skip to main content

bbnf_analysis/features/
formatting.rs

1use ls_types::*;
2
3use bbnf::types::{Expression, Token, AST};
4
5/// Extract the inner value from a TokenExpression.
6fn get_inner_expression<'a, T>(tok: &'a Token<'a, T>) -> &'a T {
7    &tok.value
8}
9
10use crate::state::DocumentState;
11
12const MAX_WIDTH: usize = 66;
13
14pub fn format_document(state: &DocumentState) -> Option<Vec<TextEdit>> {
15    let ast = state.ast()?;
16
17    let formatted = format_ast(ast);
18
19    // Replace entire document.
20    let end = offset_to_end(&state.text);
21    Some(vec![TextEdit {
22        range: Range::new(Position::new(0, 0), end),
23        new_text: formatted,
24    }])
25}
26
27/// Format only rules that overlap the selected range.
28pub fn format_range(state: &DocumentState, range: Range) -> Option<Vec<TextEdit>> {
29    let ast = state.ast()?;
30
31    let range_start = state.line_index.position_to_offset(range.start);
32    let range_end = state.line_index.position_to_offset(range.end);
33
34    let mut edits = Vec::new();
35
36    for (lhs, rhs) in ast.iter() {
37        if let Expression::Nonterminal(Token { value: name, span: name_span, .. }) = lhs {
38            let rule_start = name_span.start;
39            let rule_end = crate::state::compute_expression_end_pub(rhs)
40                .unwrap_or_else(|| panic!("format_range could not compute expression end for rule `{}`", name));
41
42            // Skip rules that don't overlap the selection.
43            if rule_end <= range_start || rule_start >= range_end {
44                continue;
45            }
46
47            let rhs_str = format_expression(rhs, 0);
48            let formatted = format!("{} = {};\n", name, rhs_str);
49
50            // Find the full rule span including the semicolon and any trailing whitespace.
51            let text_after_rule = &state.text[rule_end..];
52            let extra = text_after_rule
53                .find(';')
54                .map(|i| i + 1)
55                .unwrap_or_else(|| panic!("format_range expected `;` terminator for rule `{}`", name));
56            let full_end = rule_end + extra;
57
58            // Skip trailing whitespace/newlines after semicolon.
59            let trailing = state.text[full_end..]
60                .chars()
61                .take_while(|c| c.is_whitespace())
62                .count();
63            let full_end = full_end + trailing;
64
65            let edit_range = state.line_index.span_to_range(rule_start, full_end);
66            edits.push(TextEdit {
67                range: edit_range,
68                new_text: formatted,
69            });
70        }
71    }
72
73    if edits.is_empty() {
74        None
75    } else {
76        Some(edits)
77    }
78}
79
80/// Format the rule that was just completed (triggered by typing `;`).
81pub fn format_on_type(state: &DocumentState, position: Position) -> Option<Vec<TextEdit>> {
82    let offset = state.line_index.position_to_offset(position);
83
84    // Find which rule the cursor is in.
85    for rule in &state.info.rules {
86        if offset >= rule.full_span.0 && offset <= rule.full_span.1 + 2 {
87            // Found the rule — format just this one by delegating to format_range.
88            let rule_range = state.line_index.span_to_range(rule.full_span.0, rule.full_span.1);
89            return format_range(state, rule_range);
90        }
91    }
92
93    None
94}
95
96fn offset_to_end(text: &str) -> Position {
97    let mut line: u32 = 0;
98    let mut col: u32 = 0;
99    for byte in text.bytes() {
100        if byte == b'\n' {
101            line += 1;
102            col = 0;
103        } else {
104            col += 1;
105        }
106    }
107    Position::new(line, col)
108}
109
110fn format_ast(ast: &AST<'_>) -> String {
111    let mut lines = Vec::new();
112    for (lhs, rhs) in ast.iter() {
113        if let Expression::Nonterminal(Token { value: name, .. }) = lhs {
114            let rhs_str = format_expression(rhs, 0);
115            let rule_line = format!("{} = {}", name, rhs_str);
116
117            // Add terminator.
118            lines.push(format!("{};", rule_line));
119            lines.push(String::new()); // blank line between rules
120        }
121    }
122
123    // Remove trailing blank line.
124    if lines.last().is_some_and(|l| l.is_empty()) {
125        lines.pop();
126    }
127
128    lines.join("\n") + "\n"
129}
130
131fn format_expression(expr: &Expression<'_>, indent_level: usize) -> String {
132    match expr {
133        Expression::Literal(tok) => {
134            let s = &tok.value;
135            if s.contains('"') && !s.contains('\'') {
136                format!("'{}'", s)
137            } else {
138                format!("\"{}\"", s)
139            }
140        }
141        Expression::Nonterminal(tok) => tok.value.to_string(),
142        Expression::Regex(tok) => format!("/{}/", tok.value),
143        Expression::Epsilon(_) => "epsilon".into(),
144        Expression::Group(inner) => {
145            let inner_str = format_expression(get_inner_expression(inner), indent_level + 1);
146            format!("({})", inner_str)
147        }
148        Expression::Optional(inner) => {
149            let inner_str = format_expression(get_inner_expression(inner), indent_level + 1);
150            format!("[{}]", inner_str)
151        }
152        Expression::Many(inner) => {
153            let inner_str = format_expression(get_inner_expression(inner), indent_level + 1);
154            format!("{{{}}}", inner_str)
155        }
156        Expression::Many1(inner) => {
157            let inner_str = format_expression(get_inner_expression(inner), indent_level);
158            format!("{}+", inner_str)
159        }
160        Expression::OptionalWhitespace(inner) => {
161            let inner_str = format_expression(get_inner_expression(inner), indent_level);
162            format!("{}?w", inner_str)
163        }
164        Expression::Skip(l, r) => {
165            format!(
166                "{} << {}",
167                format_expression(get_inner_expression(l), indent_level),
168                format_expression(get_inner_expression(r), indent_level),
169            )
170        }
171        Expression::Next(l, r) => {
172            format!(
173                "{} >> {}",
174                format_expression(get_inner_expression(l), indent_level),
175                format_expression(get_inner_expression(r), indent_level),
176            )
177        }
178        Expression::Minus(l, r) => {
179            format!(
180                "{} - {}",
181                format_expression(get_inner_expression(l), indent_level),
182                format_expression(get_inner_expression(r), indent_level),
183            )
184        }
185        Expression::Concatenation(inner) => {
186            let parts: Vec<String> = get_inner_expression(inner)
187                .iter()
188                .map(|e| format_expression(e, indent_level))
189                .collect();
190            let flat = parts.join(", ");
191            if flat.len() + indent_level * 4 <= MAX_WIDTH {
192                flat
193            } else {
194                let indent = "    ".repeat(indent_level + 1);
195                let sep = format!(",\n{}", indent);
196                format!("\n{}{}", indent, parts.join(&sep))
197            }
198        }
199        Expression::Alternation(inner) => {
200            let parts: Vec<String> = get_inner_expression(inner)
201                .iter()
202                .map(|e| format_expression(e, indent_level))
203                .collect();
204            let flat = parts.join(" | ");
205            if flat.len() + indent_level * 4 <= MAX_WIDTH {
206                flat
207            } else {
208                let indent = "    ".repeat(indent_level + 1);
209                let sep = format!("\n{}| ", indent);
210                format!("\n{}{}", indent, parts.join(&sep))
211            }
212        }
213        Expression::Rule(rhs, mapping) => {
214            let rhs_str = format_expression(rhs, indent_level);
215            if let Some(m) = mapping {
216                format!("{} {}", rhs_str, format_expression(m, indent_level))
217            } else {
218                rhs_str
219            }
220        }
221        Expression::ProductionRule(lhs, rhs) => {
222            format!(
223                "{} = {}",
224                format_expression(lhs, indent_level),
225                format_expression(rhs, indent_level),
226            )
227        }
228        Expression::MappedExpression((expr_tok, mapping_tok)) => {
229            format!(
230                "{} {}",
231                format_expression(get_inner_expression(expr_tok), indent_level),
232                format_expression(get_inner_expression(mapping_tok), indent_level),
233            )
234        }
235        Expression::DebugExpression((expr_tok, label)) => {
236            format!(
237                "{}#{}",
238                format_expression(get_inner_expression(expr_tok), indent_level),
239                label,
240            )
241        }
242        Expression::MappingFn(tok) => format!("=> {}", tok.value),
243    }
244}