Skip to main content

fmt_lang/
style.rs

1//! [`Style`]: rules compiled against one language's kinds, ready to format
2//! with.
3//!
4//! Compiling turns every kind name into the language's own kind value once,
5//! and sorts the rules by kind, so the formatter's per-token lookups are a
6//! binary search over a small, dense array rather than string comparisons.
7
8use alloc::string::String;
9use alloc::vec::Vec;
10
11use crate::error::RuleError;
12use crate::rules::{Indent, MAX_INDENT_STEP, NodeRule, Rules, Space, TokenRule, Trailing};
13
14/// A gap's spacing as two independent facts: is there a space when flat, and
15/// how readily does it break. Joining two gaps takes the larger of each, which
16/// is exactly the "most generous rule wins" semantics of [`Space`].
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
18pub(crate) struct Sp {
19    /// A space when laid out flat.
20    pub(crate) space: bool,
21    /// 0 never breaks, 1 breaks with its group, 2 always breaks.
22    pub(crate) brk: u8,
23}
24
25impl Sp {
26    pub(crate) const NONE: Sp = Sp {
27        space: false,
28        brk: 0,
29    };
30    pub(crate) const SINGLE: Sp = Sp {
31        space: true,
32        brk: 0,
33    };
34    pub(crate) const HARD: Sp = Sp {
35        space: false,
36        brk: 2,
37    };
38
39    #[inline]
40    pub(crate) fn join(self, other: Sp) -> Sp {
41        Sp {
42            space: self.space | other.space,
43            brk: self.brk.max(other.brk),
44        }
45    }
46
47    #[inline]
48    pub(crate) fn is_hard(self) -> bool {
49        self.brk >= 2
50    }
51}
52
53impl From<Space> for Sp {
54    #[inline]
55    fn from(space: Space) -> Self {
56        match space {
57            Space::None => Sp::NONE,
58            Space::Single => Sp::SINGLE,
59            Space::SoftLine => Sp {
60                space: false,
61                brk: 1,
62            },
63            Space::Line => Sp {
64                space: true,
65                brk: 1,
66            },
67            Space::Hard => Sp::HARD,
68        }
69    }
70}
71
72/// Joins an optional contribution into an optional accumulator: `None` means
73/// "no rule spoke", which is different from a rule asking for nothing.
74#[inline]
75pub(crate) fn join_opt(acc: Option<Sp>, add: Option<Sp>) -> Option<Sp> {
76    match (acc, add) {
77        (Some(a), Some(b)) => Some(a.join(b)),
78        (a, None) => a,
79        (None, b) => b,
80    }
81}
82
83/// One side's spacing for a token kind.
84#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
85pub(crate) struct Sides {
86    pub(crate) before: Option<Sp>,
87    pub(crate) after: Option<Sp>,
88}
89
90impl Sides {
91    fn merge(self, later: Sides) -> Sides {
92        Sides {
93            before: later.before.or(self.before),
94            after: later.after.or(self.after),
95        }
96    }
97}
98
99/// Token rules in one place (top level or one node), sorted by kind.
100#[derive(Clone, Debug)]
101pub(crate) struct TokenTable<K> {
102    exact: Vec<(K, Sides)>,
103    any: Sides,
104}
105
106impl<K> Default for TokenTable<K> {
107    fn default() -> Self {
108        Self {
109            exact: Vec::new(),
110            any: Sides::default(),
111        }
112    }
113}
114
115impl<K: Ord> TokenTable<K> {
116    #[inline]
117    fn sides(&self, kind: &K) -> Sides {
118        match self.exact.binary_search_by(|(k, _)| k.cmp(kind)) {
119            Ok(i) => self.exact.get(i).map_or(Sides::default(), |(_, s)| *s),
120            Err(_) => Sides::default(),
121        }
122    }
123
124    #[inline]
125    fn before(&self, kind: &K) -> Option<Sp> {
126        self.sides(kind).before.or(self.any.before)
127    }
128
129    #[inline]
130    fn after(&self, kind: &K) -> Option<Sp> {
131        self.sides(kind).after.or(self.any.after)
132    }
133}
134
135/// A list's delimiters, compiled.
136#[derive(Clone, Debug)]
137pub(crate) struct Delims<K> {
138    pub(crate) open: K,
139    pub(crate) close: K,
140    pub(crate) inner: Sp,
141}
142
143/// A list's separator, compiled.
144#[derive(Clone, Debug)]
145pub(crate) struct Sep<K> {
146    pub(crate) kind: K,
147    pub(crate) text: String,
148    pub(crate) before: Sp,
149    pub(crate) after: Sp,
150    pub(crate) trailing: Trailing,
151}
152
153/// A node rule, compiled.
154#[derive(Clone, Debug)]
155pub(crate) struct NodeStyle<K> {
156    pub(crate) kind: K,
157    pub(crate) group: bool,
158    pub(crate) indent: Indent,
159    pub(crate) before: Option<Sp>,
160    pub(crate) after: Option<Sp>,
161    pub(crate) delims: Option<Delims<K>>,
162    pub(crate) empty: Sp,
163    pub(crate) sep: Option<Sep<K>>,
164    pub(crate) blank_lines: Option<u8>,
165    tokens: TokenTable<K>,
166}
167
168impl<K: Ord> NodeStyle<K> {
169    /// Whether nodes of this kind need their own document (to group or
170    /// indent); nodes that do not write straight into their parent's.
171    #[inline]
172    pub(crate) fn owns_doc(&self) -> bool {
173        self.group || self.indent != Indent::None
174    }
175}
176
177/// Formatting rules compiled against one language, ready for
178/// [`format()`](crate::format).
179///
180/// Build one with [`Rules::compile`]. [`Style::default`] is the empty style:
181/// every gap keeps its original whitespace.
182///
183/// # Examples
184///
185/// ```
186/// use fmt_lang::{Rules, Space, Style, TokenRule};
187///
188/// // Kinds here are plain strings; a real language resolves names to its own
189/// // kind type (for lang-forge: `|name| lang.kind(name)`).
190/// let style: Style<&str> = Rules::new()
191///     .token(TokenRule::new("+").around(Space::Single))
192///     .compile(|name| ["+", "num"].into_iter().find(|k| *k == name))?;
193/// # let _ = style;
194/// # Ok::<(), fmt_lang::RuleError>(())
195/// ```
196#[derive(Clone, Debug)]
197pub struct Style<K> {
198    pub(crate) indent: u32,
199    pub(crate) max_indent: u32,
200    pub(crate) blank_lines: u8,
201    pub(crate) final_newline: bool,
202    pub(crate) touch: fn(&str, &str) -> bool,
203    verbatim: Vec<K>,
204    tokens: TokenTable<K>,
205    nodes: Vec<NodeStyle<K>>,
206}
207
208impl<K> Default for Style<K> {
209    fn default() -> Self {
210        let rules = Rules::new();
211        Self {
212            indent: u32::from(rules.indent),
213            max_indent: u32::from(rules.max_indent),
214            blank_lines: rules.max_blank_lines,
215            final_newline: rules.final_newline,
216            touch: crate::can_touch,
217            verbatim: Vec::new(),
218            tokens: TokenTable::default(),
219            nodes: Vec::new(),
220        }
221    }
222}
223
224impl<K> Style<K> {
225    /// Replaces the test that decides whether two tokens may be written with
226    /// nothing between them (default: [`can_touch`](crate::can_touch)).
227    ///
228    /// The formatter calls it only where a rule would remove whitespace the
229    /// source had between two tokens; if it answers `false`, one space is kept.
230    /// A language whose lexer would read the touching texts differently (for
231    /// example one with a `-1` literal, where `-` and `1` must not touch)
232    /// supplies its own test here.
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// use fmt_lang::{can_touch, Style};
238    ///
239    /// fn no_minus_digit(left: &str, right: &str) -> bool {
240    ///     let digit = right.starts_with(|c: char| c.is_ascii_digit());
241    ///     !(left.ends_with('-') && digit) && can_touch(left, right)
242    /// }
243    /// let style = Style::<u8>::default().with_touch(no_minus_digit);
244    /// # let _ = style;
245    /// ```
246    #[must_use]
247    pub fn with_touch(mut self, touch: fn(&str, &str) -> bool) -> Self {
248        self.touch = touch;
249        self
250    }
251}
252
253impl<K: Ord> Style<K> {
254    /// The index of the rule for node kind `kind`.
255    #[inline]
256    pub(crate) fn node_index(&self, kind: &K) -> Option<u32> {
257        self.nodes
258            .binary_search_by(|n| n.kind.cmp(kind))
259            .ok()
260            .and_then(|i| u32::try_from(i).ok())
261    }
262
263    #[inline]
264    pub(crate) fn node(&self, index: Option<u32>) -> Option<&NodeStyle<K>> {
265        index.and_then(|i| self.nodes.get(i as usize))
266    }
267
268    #[inline]
269    pub(crate) fn is_verbatim(&self, kind: &K) -> bool {
270        self.verbatim.binary_search(kind).is_ok()
271    }
272
273    /// Spacing a token asks for after itself, given its parent's rule: the
274    /// parent's separator role first, then the most specific token rule.
275    pub(crate) fn token_after(&self, parent: Option<&NodeStyle<K>>, kind: &K) -> Option<Sp> {
276        if let Some(p) = parent {
277            if let Some(sep) = &p.sep {
278                if sep.kind == *kind {
279                    return Some(sep.after);
280                }
281            }
282            if let Some(sp) = p.tokens.after(kind) {
283                return Some(sp);
284            }
285        }
286        self.tokens.after(kind)
287    }
288
289    /// Spacing a token asks for before itself, given its parent's rule.
290    pub(crate) fn token_before(&self, parent: Option<&NodeStyle<K>>, kind: &K) -> Option<Sp> {
291        if let Some(p) = parent {
292            if let Some(sep) = &p.sep {
293                if sep.kind == *kind {
294                    return Some(sep.before);
295                }
296            }
297            if let Some(sp) = p.tokens.before(kind) {
298                return Some(sp);
299            }
300        }
301        self.tokens.before(kind)
302    }
303}
304
305impl Rules {
306    /// Resolves every kind name with `resolve` and compiles the rules into a
307    /// [`Style`] for that language.
308    ///
309    /// `resolve` maps a name to the language's kind, or `None` if the language
310    /// has no such kind. For a language forged by lang-forge it is
311    /// `|name| lang.kind(name)`.
312    ///
313    /// Later top-level token rules for the same kind refine earlier ones (a
314    /// side set later wins), so a preset such as [`Rules::conventional`] can be
315    /// adjusted by adding rules after it. The same holds for token rules within
316    /// one node rule.
317    ///
318    /// # Errors
319    ///
320    /// - [`RuleError::UnknownKind`] if a non-optional rule names a kind
321    ///   `resolve` does not know.
322    /// - [`RuleError::DuplicateNode`] if two node rules name the same kind.
323    /// - [`RuleError::IndentTooWide`] if [`Rules::indent`] exceeds
324    ///   [`MAX_INDENT_STEP`](crate::MAX_INDENT_STEP).
325    /// - [`RuleError::SeparatorIsDelimiter`] and
326    ///   [`RuleError::TrailingNeedsDelimiters`] for inconsistent list rules.
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use fmt_lang::{NodeRule, Rules, Space};
332    ///
333    /// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
334    /// enum Kind { Stmt, Semi }
335    ///
336    /// let style = Rules::new()
337    ///     .node(NodeRule::new("stmt").before(Space::Hard))
338    ///     .compile(|name| match name {
339    ///         "stmt" => Some(Kind::Stmt),
340    ///         ";" => Some(Kind::Semi),
341    ///         _ => None,
342    ///     })?;
343    /// # let _ = style;
344    /// # Ok::<(), fmt_lang::RuleError>(())
345    /// ```
346    pub fn compile<K: Ord + Clone>(
347        &self,
348        mut resolve: impl FnMut(&str) -> Option<K>,
349    ) -> Result<Style<K>, RuleError> {
350        if self.indent > MAX_INDENT_STEP {
351            return Err(RuleError::IndentTooWide {
352                columns: self.indent,
353            });
354        }
355        let mut verbatim = Vec::with_capacity(self.verbatim.len());
356        for name in &self.verbatim {
357            verbatim.push(required(&mut resolve, name)?);
358        }
359        verbatim.sort();
360        verbatim.dedup();
361
362        let tokens = compile_tokens(&self.tokens, &mut resolve)?;
363
364        let mut nodes: Vec<(NodeStyle<K>, &str)> = Vec::with_capacity(self.nodes.len());
365        for rule in &self.nodes {
366            if let Some(node) = compile_node(rule, &mut resolve)? {
367                nodes.push((node, rule.kind.as_str()));
368            }
369        }
370        nodes.sort_by(|a, b| a.0.kind.cmp(&b.0.kind));
371        for pair in nodes.windows(2) {
372            if let [a, b] = pair {
373                if a.0.kind == b.0.kind {
374                    return Err(RuleError::DuplicateNode { name: b.1.into() });
375                }
376            }
377        }
378
379        Ok(Style {
380            indent: u32::from(self.indent),
381            max_indent: u32::from(self.max_indent),
382            blank_lines: self.max_blank_lines,
383            final_newline: self.final_newline,
384            touch: crate::can_touch,
385            verbatim,
386            tokens,
387            nodes: nodes.into_iter().map(|(n, _)| n).collect(),
388        })
389    }
390}
391
392fn required<K>(resolve: &mut impl FnMut(&str) -> Option<K>, name: &str) -> Result<K, RuleError> {
393    resolve(name).ok_or_else(|| RuleError::UnknownKind { name: name.into() })
394}
395
396/// Resolves a name, honouring `optional`: `Ok(None)` means "drop the rule".
397fn resolve_in<K>(
398    resolve: &mut impl FnMut(&str) -> Option<K>,
399    name: &str,
400    optional: bool,
401) -> Result<Option<K>, RuleError> {
402    match resolve(name) {
403        Some(k) => Ok(Some(k)),
404        None if optional => Ok(None),
405        None => Err(RuleError::UnknownKind { name: name.into() }),
406    }
407}
408
409fn compile_tokens<K: Ord>(
410    rules: &[TokenRule],
411    resolve: &mut impl FnMut(&str) -> Option<K>,
412) -> Result<TokenTable<K>, RuleError> {
413    let mut table = TokenTable::default();
414    let mut exact: Vec<(K, Sides)> = Vec::with_capacity(rules.len());
415    for rule in rules {
416        let sides = Sides {
417            before: rule.before.map(Sp::from),
418            after: rule.after.map(Sp::from),
419        };
420        match &rule.kind {
421            None => table.any = table.any.merge(sides),
422            Some(name) => {
423                if let Some(kind) = resolve_in(resolve, name, rule.optional)? {
424                    exact.push((kind, sides));
425                }
426            }
427        }
428    }
429    // Stable sort keeps rules for one kind in the order written, so the merge
430    // below lets a later rule refine an earlier one.
431    exact.sort_by(|a, b| a.0.cmp(&b.0));
432    for (kind, sides) in exact {
433        match table.exact.last_mut() {
434            Some((last, merged)) if *last == kind => *merged = merged.merge(sides),
435            _ => table.exact.push((kind, sides)),
436        }
437    }
438    Ok(table)
439}
440
441fn compile_node<K: Ord + Clone>(
442    rule: &NodeRule,
443    resolve: &mut impl FnMut(&str) -> Option<K>,
444) -> Result<Option<NodeStyle<K>>, RuleError> {
445    let opt = rule.optional;
446    let Some(kind) = resolve_in(resolve, &rule.kind, opt)? else {
447        return Ok(None);
448    };
449    let delims = match &rule.delimiters {
450        None => None,
451        Some(d) => {
452            let (Some(open), Some(close)) = (
453                resolve_in(resolve, &d.open, opt)?,
454                resolve_in(resolve, &d.close, opt)?,
455            ) else {
456                return Ok(None);
457            };
458            Some(Delims {
459                open,
460                close,
461                inner: Sp::from(d.inner),
462            })
463        }
464    };
465    let sep = match &rule.separator {
466        None => None,
467        Some(s) => {
468            let Some(sep_kind) = resolve_in(resolve, &s.kind, opt)? else {
469                return Ok(None);
470            };
471            if let Some(d) = &delims {
472                if d.open == sep_kind || d.close == sep_kind {
473                    return Err(RuleError::SeparatorIsDelimiter {
474                        node: rule.kind.clone(),
475                    });
476                }
477            }
478            if s.trailing != Trailing::Preserve && delims.is_none() {
479                return Err(RuleError::TrailingNeedsDelimiters {
480                    node: rule.kind.clone(),
481                });
482            }
483            Some(Sep {
484                kind: sep_kind,
485                text: s.text.clone().unwrap_or_else(|| s.kind.clone()),
486                before: Sp::from(s.before),
487                after: Sp::from(s.after),
488                trailing: s.trailing,
489            })
490        }
491    };
492    let tokens = if opt {
493        // An optional node rule drops unknown token kinds inside it too.
494        let relaxed: Vec<TokenRule> = rule.tokens.iter().cloned().map(|t| t.optional()).collect();
495        compile_tokens(&relaxed, resolve)?
496    } else {
497        compile_tokens(&rule.tokens, resolve)?
498    };
499    Ok(Some(NodeStyle {
500        kind,
501        group: rule.group,
502        indent: rule.indent,
503        before: rule.before.map(Sp::from),
504        after: rule.after.map(Sp::from),
505        delims,
506        empty: Sp::from(rule.empty),
507        sep,
508        blank_lines: rule.blank_lines,
509        tokens,
510    }))
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use crate::rules::NodeRule;
517
518    fn names(name: &str) -> Option<&'static str> {
519        [
520            "a", "b", ",", "(", ")", "[", "]", "+", "list", "stmt", "ERROR",
521        ]
522        .into_iter()
523        .find(|k| *k == name)
524    }
525
526    #[test]
527    fn test_join_is_the_lattice_max() {
528        let soft = Sp::from(Space::SoftLine);
529        let single = Sp::from(Space::Single);
530        assert_eq!(soft.join(single), Sp::from(Space::Line));
531        assert_eq!(Sp::from(Space::Line).join(Sp::HARD).brk, 2);
532        assert_eq!(Sp::NONE.join(Sp::NONE), Sp::NONE);
533        assert_eq!(join_opt(None, None), None);
534        assert_eq!(join_opt(Some(Sp::NONE), None), Some(Sp::NONE));
535    }
536
537    #[test]
538    fn test_later_token_rules_refine_earlier() {
539        let style = Rules::new()
540            .token(TokenRule::new("+").before(Space::None).after(Space::None))
541            .token(TokenRule::new("+").after(Space::Single))
542            .compile(names)
543            .unwrap_or_default();
544        assert_eq!(style.token_before(None, &"+"), Some(Sp::NONE));
545        assert_eq!(style.token_after(None, &"+"), Some(Sp::SINGLE));
546        assert_eq!(style.token_after(None, &"a"), None);
547    }
548
549    #[test]
550    fn test_context_rules_win_over_top_level() {
551        let style = Rules::new()
552            .token(TokenRule::new("+").around(Space::Single))
553            .node(NodeRule::new("list").token(TokenRule::any().after(Space::None)))
554            .compile(names)
555            .unwrap_or_default();
556        let list = style.node(style.node_index(&"list"));
557        assert!(list.is_some());
558        assert_eq!(style.token_after(list, &"+"), Some(Sp::NONE));
559        // The node says nothing about `before`, so the top-level rule applies.
560        assert_eq!(style.token_before(list, &"+"), Some(Sp::SINGLE));
561    }
562
563    #[test]
564    fn test_unknown_kinds_are_errors_unless_optional() {
565        let err = Rules::new()
566            .token(TokenRule::new("nope").before(Space::None))
567            .compile(names)
568            .map(|_| ());
569        assert_eq!(
570            err,
571            Err(RuleError::UnknownKind {
572                name: "nope".into()
573            })
574        );
575        assert!(
576            Rules::new()
577                .token(TokenRule::new("nope").optional())
578                .node(NodeRule::new("missing").optional())
579                .node(
580                    NodeRule::new("list")
581                        .delimiters("{", "}", Space::Line)
582                        .optional()
583                )
584                .compile(names)
585                .is_ok()
586        );
587        assert!(Rules::conventional().compile(names).is_ok());
588    }
589
590    #[test]
591    fn test_rule_consistency_errors() {
592        let dup = Rules::new()
593            .node(NodeRule::new("list"))
594            .node(NodeRule::new("list"))
595            .compile(names)
596            .map(|_| ());
597        assert_eq!(
598            dup,
599            Err(RuleError::DuplicateNode {
600                name: "list".into()
601            })
602        );
603        let wide = Rules::new().indent(17).compile(names).map(|_| ());
604        assert_eq!(wide, Err(RuleError::IndentTooWide { columns: 17 }));
605        let same = Rules::new()
606            .node(
607                NodeRule::new("list")
608                    .delimiters("(", ")", Space::None)
609                    .separator(")", Space::Single, Trailing::Preserve),
610            )
611            .compile(names)
612            .map(|_| ());
613        assert_eq!(
614            same,
615            Err(RuleError::SeparatorIsDelimiter {
616                node: "list".into()
617            })
618        );
619        let trailing = Rules::new()
620            .node(NodeRule::new("list").separator(",", Space::Single, Trailing::Always))
621            .compile(names)
622            .map(|_| ());
623        assert_eq!(
624            trailing,
625            Err(RuleError::TrailingNeedsDelimiters {
626                node: "list".into()
627            })
628        );
629    }
630
631    #[test]
632    fn test_verbatim_and_node_lookup() {
633        let style = Rules::new()
634            .verbatim("ERROR")
635            .verbatim("ERROR")
636            .node(NodeRule::new("stmt").group())
637            .node(NodeRule::new("list"))
638            .compile(names)
639            .unwrap_or_default();
640        assert!(style.is_verbatim(&"ERROR"));
641        assert!(!style.is_verbatim(&"stmt"));
642        let stmt = style.node(style.node_index(&"stmt"));
643        assert!(stmt.is_some_and(NodeStyle::owns_doc));
644        let list = style.node(style.node_index(&"list"));
645        assert!(list.is_some_and(|n| !n.owns_doc()));
646        assert!(style.node_index(&"a").is_none());
647    }
648
649    #[test]
650    fn test_separator_text_defaults_to_kind_name() {
651        let style = Rules::new()
652            .node(
653                NodeRule::new("list")
654                    .delimiters("[", "]", Space::None)
655                    .separator(",", Space::Single, Trailing::Always),
656            )
657            .compile(names)
658            .unwrap_or_default();
659        let sep = style
660            .node(style.node_index(&"list"))
661            .and_then(|n| n.sep.as_ref())
662            .map(|s| s.text.as_str());
663        assert_eq!(sep, Some(","));
664    }
665}