LCOV - code coverage report
Current view: top level - dashql-core/src/formatter - program.rs Coverage Total Hit
Test: DashQL 915828b Lines: 96.3 % 539 519
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : //! A small document arena for width-aware SQL layout.
       2              : //!
       3              : //! The formatter builds a program of formatting operations (text, breaks, concatenations, joins,
       4              : //! indentation and parentheses) and renders it with a width-aware stack machine. Joins are rendered
       5              : //! inline if they fit, otherwise broken according to their join policy.
       6              : 
       7              : use dashql_buffers::formatting::{FormattingConfigT, FormattingDialect, FormattingMode, KeywordCase};
       8              : use dashql_parser::keywords::Keyword;
       9              : use std::cell::RefCell;
      10              : 
      11              : pub const FORMATTING_DEFAULT_INDENTATION_WIDTH: usize = 2;
      12              : pub const FORMATTING_DEFAULT_HANGING_INDENTATION_WIDTH: usize = 2;
      13              : pub const FORMATTING_DEFAULT_MAX_WIDTH: usize = 120;
      14              : 
      15              : /// Parse a formatting mode, compact is the default
      16         3606 : pub fn parse_formatting_mode(value: &str) -> FormattingMode {
      17         3606 :     match value {
      18         3606 :         "inline" => FormattingMode::INLINE,
      19         2404 :         "compact" => FormattingMode::COMPACT,
      20         1202 :         "pretty" => FormattingMode::PRETTY,
      21            0 :         _ => FormattingMode::COMPACT,
      22              :     }
      23         3606 : }
      24              : 
      25              : /// The name of a formatting mode
      26         2271 : pub fn formatting_mode_to_string(mode: FormattingMode) -> &'static str {
      27         2271 :     match mode {
      28          757 :         FormattingMode::INLINE => "inline",
      29          757 :         FormattingMode::COMPACT => "compact",
      30          757 :         FormattingMode::PRETTY => "pretty",
      31            0 :         _ => "compact",
      32              :     }
      33         2271 : }
      34              : 
      35              : /// Parse a formatting dialect, hyper is the default
      36         1204 : pub fn parse_formatting_dialect(value: &str) -> FormattingDialect {
      37         1204 :     match value {
      38         1204 :         "hyper" => FormattingDialect::HYPER,
      39           18 :         "trino" => FormattingDialect::TRINO,
      40            0 :         _ => FormattingDialect::HYPER,
      41              :     }
      42         1204 : }
      43              : 
      44              : /// The name of a formatting dialect
      45            0 : pub fn formatting_dialect_to_string(dialect: FormattingDialect) -> &'static str {
      46            0 :     match dialect {
      47            0 :         FormattingDialect::HYPER => "hyper",
      48            0 :         FormattingDialect::TRINO => "trino",
      49            0 :         _ => "hyper",
      50              :     }
      51            0 : }
      52              : 
      53              : /// A register of the formatting program, 0 is the empty document
      54              : pub type FmtReg = u32;
      55              : 
      56              : #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
      57              : pub enum FormattingOpCode {
      58              :     #[default]
      59              :     Empty,
      60              :     Text,
      61              :     Break,
      62              :     Concat,
      63              :     Join,
      64              :     Indent,
      65              :     Parenthesis,
      66              :     Brackets,
      67              : }
      68              : 
      69              : #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
      70              : pub enum FormattingJoinPolicy {
      71              :     /// Render inline only if the entire join can be rendered without breaks.
      72              :     #[default]
      73              :     BreakAllOrNone,
      74              :     /// Render progressively: keep each separator inline while the next segment fits.
      75              :     BreakOnOverflow,
      76              :     /// Always render this join broken unless global INLINE mode forces flat output.
      77              :     ForceBreak,
      78              : }
      79              : 
      80              : /// An operation of a formatting program, a node of the document tree
      81              : #[derive(Clone, Debug, Default)]
      82              : pub struct FormattingOperation<'a> {
      83              :     /// The operation kind
      84              :     pub code: FormattingOpCode,
      85              :     /// The text of a text operation
      86              :     pub text: &'a str,
      87              :     /// Verbatim text keeps its case when keywords are upper-cased
      88              :     pub verbatim: bool,
      89              :     /// The child documents of a concat, join, indent, parenthesis or brackets operation
      90              :     pub children: Vec<FmtReg>,
      91              :     /// The separator between join children when rendered inline
      92              :     pub inline_separator: FmtReg,
      93              :     /// The separator between join children when rendered broken
      94              :     pub break_separator: FmtReg,
      95              :     /// Indent the join children when the join is rendered broken?
      96              :     pub indent_after_breaks: bool,
      97              :     /// The policy that decides where a join breaks
      98              :     pub join_policy: FormattingJoinPolicy,
      99              : }
     100              : 
     101              : /// The options for rendering a formatting program
     102              : #[derive(Clone, Copy, Debug)]
     103              : pub struct FormattingRenderOptions {
     104              :     /// The maximum line width in bytes
     105              :     pub max_width: usize,
     106              :     /// The number of spaces per indentation level
     107              :     pub indentation_width: usize,
     108              :     /// Append the line width as `/*n*/` comment before every line break?
     109              :     pub debug_mode: bool,
     110              :     /// The formatting mode, INLINE renders every join and parenthesis flat
     111              :     pub mode: FormattingMode,
     112              :     /// The case of keywords in generated text
     113              :     pub keyword_case: KeywordCase,
     114              : }
     115              : 
     116              : impl Default for FormattingRenderOptions {
     117           15 :     fn default() -> Self {
     118           15 :         Self {
     119           15 :             max_width: FORMATTING_DEFAULT_MAX_WIDTH,
     120           15 :             indentation_width: FORMATTING_DEFAULT_INDENTATION_WIDTH,
     121           15 :             debug_mode: false,
     122           15 :             mode: FormattingMode::COMPACT,
     123           15 :             keyword_case: KeywordCase::LOWER,
     124           15 :         }
     125           15 :     }
     126              : }
     127              : 
     128              : /// A small document arena for width-aware SQL layout.
     129              : ///
     130              : /// The builder methods take `&self` (the arena lives in a `RefCell`), so that operations can be
     131              : /// nested: `f.concat(&[f.text("a"), f.text("b")])`.
     132              : pub struct FormattingProgram<'a> {
     133              :     /// The formatting config, selects the default join policy
     134              :     pub config: FormattingConfigT,
     135              :     /// The operations, register 0 is the empty document
     136              :     program: RefCell<Vec<FormattingOperation<'a>>>,
     137              : }
     138              : 
     139              : impl Default for FormattingProgram<'_> {
     140            0 :     fn default() -> Self {
     141            0 :         Self::new()
     142            0 :     }
     143              : }
     144              : 
     145              : impl<'a> FormattingProgram<'a> {
     146         4155 :     pub fn new() -> Self {
     147         4155 :         let mut config = FormattingConfigT::default();
     148         4155 :         config.mode = FormattingMode::COMPACT;
     149         4155 :         let program = Self { config, program: RefCell::new(Vec::new()) };
     150         4155 :         program.reset();
     151         4155 :         program
     152         4155 :     }
     153              : 
     154         5669 :     pub fn set_config(&mut self, value: &FormattingConfigT) {
     155         5669 :         self.config = value.clone();
     156         5669 :     }
     157              : 
     158         9824 :     pub fn reset(&self) {
     159         9824 :         let mut program = self.program.borrow_mut();
     160         9824 :         program.clear();
     161         9824 :         program.push(FormattingOperation::default());
     162         9824 :     }
     163              : 
     164              :     /// The number of operations, including the empty document
     165            1 :     pub fn len(&self) -> usize {
     166            1 :         self.program.borrow().len()
     167            1 :     }
     168              : 
     169              :     /// Is the program empty (only the empty document)?
     170            1 :     pub fn is_empty(&self) -> bool {
     171            1 :         self.len() <= 1
     172            1 :     }
     173              : 
     174              :     /// A copy of an operation
     175            2 :     pub fn get(&self, reg: FmtReg) -> FormattingOperation<'a> {
     176            2 :         self.program.borrow()[reg as usize].clone()
     177            2 :     }
     178              : 
     179        10324 :     pub fn empty(&self) -> FmtReg {
     180        10324 :         0
     181        10324 :     }
     182              : 
     183       467962 :     pub fn text(&self, text: &'a str) -> FmtReg {
     184       467962 :         self.push(FormattingOperation { code: FormattingOpCode::Text, text, ..Default::default() })
     185       467962 :     }
     186              : 
     187       122665 :     pub fn verbatim(&self, text: &'a str) -> FmtReg {
     188       122665 :         self.push(FormattingOperation { code: FormattingOpCode::Text, text, verbatim: true, ..Default::default() })
     189       122665 :     }
     190              : 
     191       179257 :     pub fn break_(&self) -> FmtReg {
     192       179257 :         self.push(FormattingOperation { code: FormattingOpCode::Break, ..Default::default() })
     193       179257 :     }
     194              : 
     195              :     /// Concatenate documents, skipping empty ones
     196       257783 :     pub fn concat(&self, parts: &[FmtReg]) -> FmtReg {
     197       590762 :         let parts: Vec<FmtReg> = parts.iter().copied().filter(|&p| p != 0).collect();
     198       257783 :         match parts.len() {
     199          301 :             0 => self.empty(),
     200        11544 :             1 => parts[0],
     201              :             _ => {
     202       245938 :                 self.push(FormattingOperation { code: FormattingOpCode::Concat, children: parts, ..Default::default() })
     203              :             }
     204              :         }
     205       257783 :     }
     206              : 
     207          327 :     pub fn indented(&self, child: FmtReg) -> FmtReg {
     208          327 :         if child == 0 {
     209            1 :             return self.empty();
     210          326 :         }
     211          326 :         self.push(FormattingOperation { code: FormattingOpCode::Indent, children: vec![child], ..Default::default() })
     212          327 :     }
     213              : 
     214        16097 :     pub fn parenthesized(&self, child: FmtReg) -> FmtReg {
     215        16097 :         if child == 0 {
     216            1 :             return self.empty();
     217        16096 :         }
     218        16096 :         self.push(FormattingOperation {
     219        16096 :             code: FormattingOpCode::Parenthesis,
     220        16096 :             children: vec![child],
     221        16096 :             ..Default::default()
     222        16096 :         })
     223        16097 :     }
     224              : 
     225           90 :     pub fn bracketed(&self, child: FmtReg) -> FmtReg {
     226           90 :         if child == 0 {
     227            0 :             return self.empty();
     228           90 :         }
     229           90 :         self.push(FormattingOperation { code: FormattingOpCode::Brackets, children: vec![child], ..Default::default() })
     230           90 :     }
     231              : 
     232              :     /// Join documents with separators, skipping empty ones.
     233              :     ///
     234              :     /// Without a policy, joins break all or none in pretty mode and on overflow otherwise.
     235       178122 :     pub fn join(
     236       178122 :         &self,
     237       178122 :         items: &[FmtReg],
     238       178122 :         inline_separator: FmtReg,
     239       178122 :         break_separator: FmtReg,
     240       178122 :         join_policy: Option<FormattingJoinPolicy>,
     241       178122 :         indent_after_breaks: bool,
     242       178122 :     ) -> FmtReg {
     243       319880 :         let filtered: Vec<FmtReg> = items.iter().copied().filter(|&i| i != 0).collect();
     244       178122 :         match filtered.len() {
     245          153 :             0 => return self.empty(),
     246        91441 :             1 => return filtered[0],
     247        86528 :             _ => {}
     248              :         }
     249        86528 :         let selected_policy = join_policy.unwrap_or(if self.config.mode == FormattingMode::PRETTY {
     250        29111 :             FormattingJoinPolicy::BreakAllOrNone
     251              :         } else {
     252        57417 :             FormattingJoinPolicy::BreakOnOverflow
     253              :         });
     254        86528 :         self.push(FormattingOperation {
     255        86528 :             code: FormattingOpCode::Join,
     256        86528 :             children: filtered,
     257        86528 :             inline_separator,
     258        86528 :             break_separator,
     259        86528 :             indent_after_breaks,
     260        86528 :             join_policy: selected_policy,
     261        86528 :             ..Default::default()
     262        86528 :         })
     263       178122 :     }
     264              : 
     265      1118862 :     fn push(&self, doc: FormattingOperation<'a>) -> FmtReg {
     266      1118862 :         let mut program = self.program.borrow_mut();
     267      1118862 :         program.push(doc);
     268      1118862 :         (program.len() - 1) as FmtReg
     269      1118862 :     }
     270              : 
     271              :     /// Render a document
     272         5825 :     pub fn render(&self, root: FmtReg, options: &FormattingRenderOptions) -> String {
     273         5825 :         if root == 0 {
     274            1 :             return String::new();
     275         5824 :         }
     276         5824 :         let program = self.program.borrow();
     277         5824 :         render(&program, root, options)
     278         5825 :     }
     279              : }
     280              : 
     281              : const FORMATTING_INLINE_WIDTH_SAFETY_BUFFER: isize = 2;
     282              : 
     283        53696 : fn remaining_inline_width(max_width: usize, current_line_width: usize) -> isize {
     284        53696 :     let remaining = max_width as isize - current_line_width as isize;
     285        53696 :     remaining - FORMATTING_INLINE_WIDTH_SAFETY_BUFFER
     286        53696 : }
     287              : 
     288              : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     289              : enum RendererOpCode {
     290              :     Format,
     291              :     JoinNextBreakOnOverflow,
     292              :     /// Close a parenthesis or brackets on the same line
     293              :     Close(char),
     294              :     /// Close a parenthesis or brackets on a new line
     295              :     CloseAfterBreak(char),
     296              : }
     297              : 
     298              : /// A pending document of an inline dry run
     299              : #[derive(Clone, Copy, Debug)]
     300              : struct InlineRenderCommand {
     301              :     /// The document to measure
     302              :     doc: FmtReg,
     303              :     /// The indentation of the document
     304              :     indentation: usize,
     305              : }
     306              : 
     307              : /// A pending step of the renderer
     308              : #[derive(Clone, Copy, Debug)]
     309              : struct RenderCommand {
     310              :     /// The step kind
     311              :     kind: RendererOpCode,
     312              :     /// The document to render, or the join that continues
     313              :     reg: FmtReg,
     314              :     /// The indentation of the document
     315              :     indentation: usize,
     316              :     /// The index of the next join child to render on overflow
     317              :     next_index: usize,
     318              : }
     319              : 
     320              : impl RenderCommand {
     321       541924 :     fn format(reg: FmtReg, indentation: usize) -> Self {
     322       541924 :         Self { kind: RendererOpCode::Format, reg, indentation, next_index: 0 }
     323       541924 :     }
     324              : }
     325              : 
     326        42968 : fn get_only_child(doc: &FormattingOperation) -> FmtReg {
     327        42968 :     debug_assert_eq!(doc.children.len(), 1);
     328        42968 :     doc.children[0]
     329        42968 : }
     330              : 
     331        93350 : fn push_inline_children(stack: &mut Vec<InlineRenderCommand>, children: &[FmtReg], indentation: usize) {
     332       266826 :     for &child in children.iter().rev() {
     333       266826 :         stack.push(InlineRenderCommand { doc: child, indentation });
     334       266826 :     }
     335        93350 : }
     336              : 
     337       100635 : fn push_docs(stack: &mut Vec<RenderCommand>, children: &[FmtReg], indentation: usize) {
     338       271926 :     for &child in children.iter().rev() {
     339       271926 :         stack.push(RenderCommand::format(child, indentation));
     340       271926 :     }
     341       100635 : }
     342              : 
     343        85754 : fn push_inline_join(stack: &mut Vec<InlineRenderCommand>, doc: &FormattingOperation, indentation: usize) {
     344       217646 :     for i in (1..=doc.children.len()).rev() {
     345       217646 :         stack.push(InlineRenderCommand { doc: doc.children[i - 1], indentation });
     346       217646 :         if i > 1 && doc.inline_separator != 0 {
     347       131492 :             stack.push(InlineRenderCommand { doc: doc.inline_separator, indentation });
     348       131492 :         }
     349              :     }
     350        85754 : }
     351              : 
     352        54316 : fn push_rendered_join(
     353        54316 :     stack: &mut Vec<RenderCommand>,
     354        54316 :     doc: &FormattingOperation,
     355        54316 :     indentation: usize,
     356        54316 :     separator: FmtReg,
     357        54316 : ) {
     358       141263 :     for i in (1..=doc.children.len()).rev() {
     359       141263 :         stack.push(RenderCommand::format(doc.children[i - 1], indentation));
     360       141263 :         if i > 1 && separator != 0 {
     361        86777 :             stack.push(RenderCommand::format(separator, indentation));
     362        86777 :         }
     363              :     }
     364        54316 : }
     365              : 
     366              : /// The width of the text that a document starts with, up to its first possible line break.
     367              : /// Returns the width and whether a line break could follow.
     368       114626 : fn leading_inline_width(reg: FmtReg, program: &[FormattingOperation]) -> (usize, bool) {
     369       114626 :     let doc = &program[reg as usize];
     370       114626 :     match doc.code {
     371            0 :         FormattingOpCode::Empty => (0, false),
     372        64634 :         FormattingOpCode::Text => (doc.text.len(), false),
     373        20986 :         FormattingOpCode::Break => (0, true),
     374              :         FormattingOpCode::Concat => {
     375        19459 :             let mut width = 0;
     376        41639 :             for &child in doc.children.iter() {
     377        41639 :                 let (child_width, breaks) = leading_inline_width(child, program);
     378        41639 :                 width += child_width;
     379        41639 :                 if breaks {
     380        10885 :                     return (width, true);
     381        30754 :                 }
     382              :             }
     383         8574 :             (width, false)
     384              :         }
     385           11 :         FormattingOpCode::Indent => doc.children.first().map_or((0, false), |&child| leading_inline_width(child, program)),
     386              :         // A join may break after its first item, parentheses after the opening one
     387         6689 :         FormattingOpCode::Join => match doc.children.first() {
     388         6689 :             Some(&first) => {
     389         6689 :                 let (width, breaks) = leading_inline_width(first, program);
     390         6689 :                 (width, breaks || doc.children.len() > 1)
     391              :             }
     392            0 :             None => (0, false),
     393              :         },
     394         2847 :         FormattingOpCode::Parenthesis | FormattingOpCode::Brackets => (1, true),
     395              :     }
     396       114626 : }
     397              : 
     398              : /// The width of the text that follows on the current line, up to the first possible line break of the pending steps.
     399              : /// A document fits inline only if this text fits behind it, e.g. the alias after a parenthesized window.
     400        46565 : fn trailing_inline_width(stack: &[RenderCommand], program: &[FormattingOperation]) -> usize {
     401        46565 :     let mut width = 0;
     402        87942 :     for command in stack.iter().rev() {
     403        87933 :         let (command_width, breaks) = match command.kind {
     404        66287 :             RendererOpCode::Format => leading_inline_width(command.reg, program),
     405         6522 :             RendererOpCode::Close(_) => (1, false),
     406         4213 :             RendererOpCode::CloseAfterBreak(_) => (0, true),
     407              :             // The next item of a join follows its separator, which may break. The safety buffer of the remaining
     408              :             // width covers a separator that stays on the line.
     409              :             RendererOpCode::JoinNextBreakOnOverflow => {
     410        10911 :                 let doc = &program[command.reg as usize];
     411        10911 :                 (0, command.next_index < doc.children.len())
     412              :             }
     413              :         };
     414        87933 :         width += command_width;
     415        87933 :         if breaks {
     416        41786 :             break;
     417        46147 :         }
     418              :     }
     419        46565 :     width
     420        46565 : }
     421              : 
     422              : /// Does the stack render inline within the remaining width?
     423        53690 : fn dry_run_inline(
     424        53690 :     mut remaining: isize,
     425        53690 :     mut stack: Vec<InlineRenderCommand>,
     426        53690 :     program: &[FormattingOperation],
     427        53690 :     options: &FormattingRenderOptions,
     428        53690 : ) -> bool {
     429       642354 :     while remaining >= 0 {
     430       634234 :         let Some(command) = stack.pop() else { break };
     431       590314 :         let doc = &program[command.doc as usize];
     432       590314 :         match doc.code {
     433            0 :             FormattingOpCode::Empty => {}
     434       427853 :             FormattingOpCode::Text => remaining -= doc.text.len() as isize,
     435            3 :             FormattingOpCode::Break => return false,
     436        93350 :             FormattingOpCode::Concat => push_inline_children(&mut stack, &doc.children, command.indentation),
     437              :             FormattingOpCode::Join => {
     438        53318 :                 if doc.join_policy == FormattingJoinPolicy::ForceBreak && doc.children.len() > 1 {
     439         1647 :                     return false;
     440        51671 :                 }
     441        51671 :                 push_inline_join(&mut stack, doc, command.indentation);
     442              :             }
     443              :             FormattingOpCode::Indent => {
     444            3 :                 if !doc.children.is_empty() {
     445            3 :                     stack.push(InlineRenderCommand {
     446            3 :                         doc: get_only_child(doc),
     447            3 :                         indentation: command.indentation + options.indentation_width,
     448            3 :                     });
     449            3 :                 }
     450              :             }
     451              :             FormattingOpCode::Parenthesis | FormattingOpCode::Brackets => {
     452        15787 :                 remaining -= 2;
     453        15787 :                 if !doc.children.is_empty() {
     454        15787 :                     stack.push(InlineRenderCommand { doc: get_only_child(doc), indentation: command.indentation });
     455        15787 :                 }
     456              :             }
     457              :         }
     458              :     }
     459        52040 :     remaining >= 0
     460        53690 : }
     461              : 
     462        34083 : fn join_fits_inline(
     463        34083 :     remaining: isize,
     464        34083 :     doc: &FormattingOperation,
     465        34083 :     indentation: usize,
     466        34083 :     program: &[FormattingOperation],
     467        34083 :     options: &FormattingRenderOptions,
     468        34083 : ) -> bool {
     469        34083 :     let mut stack = Vec::new();
     470        34083 :     push_inline_join(&mut stack, doc, indentation);
     471        34083 :     dry_run_inline(remaining, stack, program, options)
     472        34083 : }
     473              : 
     474         8931 : fn can_inline_join_step(
     475         8931 :     remaining: isize,
     476         8931 :     doc: &FormattingOperation,
     477         8931 :     next_index: usize,
     478         8931 :     indentation: usize,
     479         8931 :     program: &[FormattingOperation],
     480         8931 :     options: &FormattingRenderOptions,
     481         8931 : ) -> bool {
     482         8931 :     if next_index >= doc.children.len() {
     483            0 :         return true;
     484         8931 :     }
     485         8931 :     let mut stack = vec![InlineRenderCommand { doc: doc.children[next_index], indentation }];
     486         8931 :     if doc.inline_separator != 0 {
     487         8891 :         stack.push(InlineRenderCommand { doc: doc.inline_separator, indentation });
     488         8891 :     }
     489         8931 :     dry_run_inline(remaining, stack, program, options)
     490         8931 : }
     491              : 
     492        10682 : fn parenthesis_fits_inline(
     493        10682 :     remaining: isize,
     494        10682 :     doc: &FormattingOperation,
     495        10682 :     indentation: usize,
     496        10682 :     program: &[FormattingOperation],
     497        10682 :     options: &FormattingRenderOptions,
     498        10682 : ) -> bool {
     499        10682 :     if remaining < 2 {
     500            6 :         return false;
     501        10676 :     }
     502        10676 :     let mut stack = Vec::new();
     503        10676 :     if !doc.children.is_empty() {
     504        10676 :         stack.push(InlineRenderCommand { doc: get_only_child(doc), indentation });
     505        10676 :     }
     506        10676 :     dry_run_inline(remaining - 2, stack, program, options)
     507        10682 : }
     508              : 
     509        26039 : fn append_line_break(output: &mut String, current_line_width: &mut usize, indentation: usize, debug_mode: bool) {
     510        26039 :     if debug_mode {
     511            1 :         output.push_str(" /*");
     512            1 :         output.push_str(&current_line_width.to_string());
     513            1 :         output.push_str("*/");
     514        26038 :     }
     515        26039 :     output.push('\n');
     516        26039 :     output.extend(std::iter::repeat(' ').take(indentation));
     517        26039 :     *current_line_width = indentation;
     518        26039 : }
     519              : 
     520              : /// Append generated text, upper-casing the words that are keywords
     521            3 : fn append_generated_text(output: &mut String, text: &str) {
     522           72 :     let is_word = |ch: u8| ch.is_ascii_alphanumeric() || ch == b'_';
     523            3 :     let bytes = text.as_bytes();
     524            3 :     let mut pos = 0;
     525           17 :     while pos < bytes.len() {
     526           14 :         let start = pos;
     527           14 :         if !is_word(bytes[pos]) {
     528              :             // Words are ASCII, so runs of other bytes end at char boundaries
     529           16 :             while pos < bytes.len() && !is_word(bytes[pos]) {
     530            9 :                 pos += 1;
     531            9 :             }
     532            7 :             output.push_str(&text[start..pos]);
     533            7 :             continue;
     534            7 :         }
     535           45 :         while pos < bytes.len() && is_word(bytes[pos]) {
     536           38 :             pos += 1;
     537           38 :         }
     538            7 :         let word = &text[start..pos];
     539            7 :         if Keyword::find(word.as_bytes()).is_none() {
     540            4 :             output.push_str(word);
     541            4 :         } else {
     542            3 :             output.push_str(&word.to_ascii_uppercase());
     543            3 :         }
     544              :     }
     545            3 : }
     546              : 
     547         5824 : fn render(program: &[FormattingOperation], root: FmtReg, options: &FormattingRenderOptions) -> String {
     548         5824 :     let mut output = String::new();
     549         5824 :     let mut current_line_width = 0usize;
     550         5824 :     let force_inline = options.mode == FormattingMode::INLINE;
     551              : 
     552         5824 :     let mut stack = vec![RenderCommand::format(root, 0)];
     553       574654 :     while let Some(command) = stack.pop() {
     554       568830 :         match command.kind {
     555        14108 :             RendererOpCode::Close(close) => {
     556        14108 :                 output.push(close);
     557        14108 :                 current_line_width += 1;
     558        14108 :                 continue;
     559              :             }
     560         2067 :             RendererOpCode::CloseAfterBreak(close) => {
     561         2067 :                 append_line_break(&mut output, &mut current_line_width, command.indentation, options.debug_mode);
     562         2067 :                 output.push(close);
     563         2067 :                 current_line_width += 1;
     564         2067 :                 continue;
     565              :             }
     566              :             RendererOpCode::JoinNextBreakOnOverflow => {
     567        10731 :                 let doc = &program[command.reg as usize];
     568        10731 :                 if command.next_index >= doc.children.len() {
     569         1800 :                     continue;
     570         8931 :                 }
     571         8931 :                 let next = RenderCommand {
     572         8931 :                     kind: RendererOpCode::JoinNextBreakOnOverflow,
     573         8931 :                     reg: command.reg,
     574         8931 :                     indentation: command.indentation,
     575         8931 :                     next_index: command.next_index + 1,
     576         8931 :                 };
     577         8931 :                 let child = doc.children[command.next_index];
     578              :                 // The last item is followed by the text after the join
     579         8931 :                 let trailing = if command.next_index + 1 >= doc.children.len() {
     580         1800 :                     trailing_inline_width(&stack, program)
     581              :                 } else {
     582         7131 :                     0
     583              :                 };
     584         8931 :                 if can_inline_join_step(
     585         8931 :                     remaining_inline_width(options.max_width, current_line_width) - trailing as isize,
     586         8931 :                     doc,
     587         8931 :                     command.next_index,
     588         8931 :                     command.indentation,
     589         8931 :                     program,
     590         8931 :                     options,
     591              :                 ) {
     592         6038 :                     stack.push(next);
     593         6038 :                     stack.push(RenderCommand::format(child, command.indentation));
     594         6038 :                     if doc.inline_separator != 0 {
     595         6008 :                         stack.push(RenderCommand::format(doc.inline_separator, command.indentation));
     596         6008 :                     }
     597              :                 } else {
     598         2893 :                     let next_is_parenthesis = (child as usize) < program.len()
     599         2893 :                         && program[child as usize].code == FormattingOpCode::Parenthesis;
     600         2893 :                     let use_inline_separator = next_is_parenthesis && doc.inline_separator != 0;
     601         2893 :                     stack.push(next);
     602         2893 :                     stack.push(RenderCommand::format(child, command.indentation));
     603         2893 :                     if doc.break_separator != 0 {
     604         2893 :                         let separator = if use_inline_separator { doc.inline_separator } else { doc.break_separator };
     605         2893 :                         stack.push(RenderCommand::format(separator, command.indentation));
     606            0 :                     }
     607              :                 }
     608         8931 :                 continue;
     609              :             }
     610       541924 :             RendererOpCode::Format => {}
     611              :         }
     612              : 
     613       541924 :         let doc = &program[command.reg as usize];
     614       541924 :         match doc.code {
     615            0 :             FormattingOpCode::Empty => {}
     616              :             FormattingOpCode::Text => {
     617       346766 :                 if options.keyword_case == KeywordCase::UPPER && !doc.verbatim {
     618            3 :                     append_generated_text(&mut output, doc.text);
     619       346763 :                 } else {
     620       346763 :                     output.push_str(doc.text);
     621       346763 :                 }
     622       346766 :                 current_line_width += doc.text.len();
     623              :             }
     624        21905 :             FormattingOpCode::Break => {
     625        21905 :                 append_line_break(&mut output, &mut current_line_width, command.indentation, options.debug_mode);
     626        21905 :             }
     627       100635 :             FormattingOpCode::Concat => push_docs(&mut stack, &doc.children, command.indentation),
     628              :             FormattingOpCode::Join => {
     629        56116 :                 if force_inline
     630        37540 :                     || (doc.join_policy != FormattingJoinPolicy::ForceBreak
     631        34083 :                         && join_fits_inline(
     632        34083 :                             remaining_inline_width(options.max_width, current_line_width)
     633        34083 :                                 - trailing_inline_width(&stack, program) as isize,
     634        34083 :                             doc,
     635        34083 :                             command.indentation,
     636        34083 :                             program,
     637        34083 :                             options,
     638              :                         ))
     639        47843 :                 {
     640        47843 :                     push_rendered_join(&mut stack, doc, command.indentation, doc.inline_separator);
     641        47843 :                 } else {
     642         8273 :                     let indent =
     643         8273 :                         command.indentation + if doc.indent_after_breaks { options.indentation_width } else { 0 };
     644         8273 :                     match doc.join_policy {
     645         6473 :                         FormattingJoinPolicy::BreakAllOrNone | FormattingJoinPolicy::ForceBreak => {
     646         6473 :                             push_rendered_join(&mut stack, doc, indent, doc.break_separator);
     647         6473 :                         }
     648              :                         FormattingJoinPolicy::BreakOnOverflow => {
     649         1800 :                             if !doc.children.is_empty() {
     650         1800 :                                 if doc.children.len() > 1 {
     651         1800 :                                     stack.push(RenderCommand {
     652         1800 :                                         kind: RendererOpCode::JoinNextBreakOnOverflow,
     653         1800 :                                         reg: command.reg,
     654         1800 :                                         indentation: indent,
     655         1800 :                                         next_index: 1,
     656         1800 :                                     });
     657         1800 :                                 }
     658         1800 :                                 stack.push(RenderCommand::format(doc.children[0], indent));
     659            0 :                             }
     660              :                         }
     661              :                     }
     662              :                 }
     663              :             }
     664              :             FormattingOpCode::Indent => {
     665          327 :                 if !doc.children.is_empty() {
     666          327 :                     stack.push(RenderCommand::format(
     667          327 :                         get_only_child(doc),
     668          327 :                         command.indentation + options.indentation_width,
     669          327 :                     ));
     670          327 :                 }
     671              :             }
     672              :             FormattingOpCode::Parenthesis | FormattingOpCode::Brackets => {
     673        16175 :                 let (open, close) = if doc.code == FormattingOpCode::Brackets { ('[', ']') } else { ('(', ')') };
     674        16175 :                 let render_flat = force_inline
     675        10682 :                     || parenthesis_fits_inline(
     676        10682 :                         remaining_inline_width(options.max_width, current_line_width)
     677        10682 :                             - trailing_inline_width(&stack, program) as isize,
     678        10682 :                         doc,
     679        10682 :                         command.indentation,
     680        10682 :                         program,
     681        10682 :                         options,
     682              :                     );
     683        16175 :                 output.push(open);
     684        16175 :                 current_line_width += 1;
     685        16175 :                 let close = if render_flat {
     686        14108 :                     RendererOpCode::Close(close)
     687              :                 } else {
     688         2067 :                     append_line_break(
     689         2067 :                         &mut output,
     690         2067 :                         &mut current_line_width,
     691         2067 :                         command.indentation + options.indentation_width,
     692         2067 :                         options.debug_mode,
     693              :                     );
     694         2067 :                     RendererOpCode::CloseAfterBreak(close)
     695              :                 };
     696        16175 :                 stack.push(RenderCommand { kind: close, reg: 0, indentation: command.indentation, next_index: 0 });
     697        16175 :                 if !doc.children.is_empty() {
     698        16175 :                     stack.push(RenderCommand::format(
     699        16175 :                         get_only_child(doc),
     700        16175 :                         command.indentation + options.indentation_width,
     701        16175 :                     ));
     702        16175 :                 }
     703              :             }
     704              :         }
     705              :     }
     706         5824 :     output
     707         5824 : }
     708              : 
     709              : #[cfg(test)]
     710              : mod tests {
     711              :     use super::*;
     712              : 
     713           15 :     fn options(mode: FormattingMode, max_width: usize) -> FormattingRenderOptions {
     714           15 :         FormattingRenderOptions { mode, max_width, ..Default::default() }
     715           15 :     }
     716              : 
     717            5 :     fn comma_list<'a>(f: &FormattingProgram<'a>, items: &[&'a str], policy: Option<FormattingJoinPolicy>) -> FmtReg {
     718           14 :         let regs: Vec<FmtReg> = items.iter().map(|i| f.text(i)).collect();
     719            5 :         let break_separator = f.concat(&[f.text(","), f.break_()]);
     720            5 :         f.join(&regs, f.text(", "), break_separator, policy, true)
     721            5 :     }
     722              : 
     723              :     #[test]
     724            1 :     fn concat_skips_empty_documents() {
     725            1 :         let f = FormattingProgram::new();
     726            1 :         let a = f.text("a");
     727            1 :         assert_eq!(f.concat(&[]), 0);
     728            1 :         assert_eq!(f.concat(&[0, a, 0]), a);
     729            1 :         assert_eq!(f.indented(0), 0);
     730            1 :         assert_eq!(f.parenthesized(0), 0);
     731            1 :         assert_eq!(f.join(&[0, a], 0, 0, None, false), a);
     732            1 :         let both = f.concat(&[a, f.text("b")]);
     733            1 :         assert_eq!(f.render(both, &options(FormattingMode::COMPACT, 120)), "ab");
     734            1 :         assert_eq!(f.render(0, &options(FormattingMode::COMPACT, 120)), "");
     735            1 :     }
     736              : 
     737              :     #[test]
     738            1 :     fn join_breaks_all_or_none() {
     739            1 :         let f = FormattingProgram::new();
     740            1 :         let list = comma_list(&f, &["aaaa", "bbbb", "cccc"], Some(FormattingJoinPolicy::BreakAllOrNone));
     741            1 :         let root = f.concat(&[f.text("x "), list]);
     742            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 120)), "x aaaa, bbbb, cccc");
     743              :         // The safety buffer of 2 counts: 18 characters need a width of 20.
     744            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 20)), "x aaaa, bbbb, cccc");
     745            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 19)), "x aaaa,\n  bbbb,\n  cccc");
     746            1 :         assert_eq!(f.render(root, &options(FormattingMode::INLINE, 1)), "x aaaa, bbbb, cccc");
     747            1 :     }
     748              : 
     749              :     #[test]
     750            1 :     fn join_breaks_on_overflow() {
     751            1 :         let f = FormattingProgram::new();
     752            1 :         let list = comma_list(&f, &["aaaa", "bbbb", "cccc"], Some(FormattingJoinPolicy::BreakOnOverflow));
     753            1 :         assert_eq!(f.render(list, &options(FormattingMode::COMPACT, 14)), "aaaa, bbbb,\n  cccc");
     754            1 :     }
     755              : 
     756              :     #[test]
     757            1 :     fn join_force_break() {
     758            1 :         let f = FormattingProgram::new();
     759            1 :         let list = comma_list(&f, &["a", "b"], Some(FormattingJoinPolicy::ForceBreak));
     760            1 :         assert_eq!(f.render(list, &options(FormattingMode::COMPACT, 120)), "a,\n  b");
     761            1 :         assert_eq!(f.render(list, &options(FormattingMode::INLINE, 120)), "a, b");
     762              :         // A forced break inside a parenthesis breaks the parenthesis as well
     763            1 :         let parens = f.parenthesized(list);
     764            1 :         assert_eq!(f.render(parens, &options(FormattingMode::COMPACT, 120)), "(\n  a,\n    b\n)");
     765            1 :     }
     766              : 
     767              :     #[test]
     768            1 :     fn default_join_policy_depends_on_mode() {
     769            1 :         let mut f = FormattingProgram::new();
     770            1 :         let overflow = comma_list(&f, &["a"; 3], None);
     771            1 :         let mut config = FormattingConfigT::default();
     772            1 :         config.mode = FormattingMode::PRETTY;
     773            1 :         f.set_config(&config);
     774            1 :         let all_or_none = comma_list(&f, &["a"; 3], None);
     775            1 :         assert_eq!(f.get(overflow).join_policy, FormattingJoinPolicy::BreakOnOverflow);
     776            1 :         assert_eq!(f.get(all_or_none).join_policy, FormattingJoinPolicy::BreakAllOrNone);
     777            1 :         f.reset();
     778            1 :         assert!(f.is_empty());
     779            1 :     }
     780              : 
     781              :     #[test]
     782            1 :     fn parenthesis_breaks_when_too_wide() {
     783            1 :         let f = FormattingProgram::new();
     784            1 :         let inner = f.text("0123456789");
     785            1 :         let root = f.concat(&[f.text("f"), f.parenthesized(inner)]);
     786            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 15)), "f(0123456789)");
     787            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 14)), "f(\n  0123456789\n)");
     788            1 :     }
     789              : 
     790              :     #[test]
     791            1 :     fn overflowing_parenthesis_keeps_inline_separator() {
     792            1 :         let f = FormattingProgram::new();
     793            1 :         let items = [f.text("aaaa"), f.parenthesized(f.text("bbbbbbbb"))];
     794            1 :         let join = f.join(&items, f.text(" "), f.break_(), Some(FormattingJoinPolicy::BreakOnOverflow), false);
     795            1 :         assert_eq!(f.render(join, &options(FormattingMode::COMPACT, 12)), "aaaa (\n  bbbbbbbb\n)");
     796            1 :     }
     797              : 
     798              :     #[test]
     799            1 :     fn indentation_and_debug_mode() {
     800            1 :         let f = FormattingProgram::new();
     801            1 :         let root = f.concat(&[f.text("a"), f.indented(f.concat(&[f.break_(), f.text("b")]))]);
     802            1 :         let mut opts = options(FormattingMode::COMPACT, 120);
     803            1 :         opts.indentation_width = 4;
     804            1 :         assert_eq!(f.render(root, &opts), "a\n    b");
     805            1 :         opts.debug_mode = true;
     806            1 :         assert_eq!(f.render(root, &opts), "a /*1*/\n    b");
     807            1 :     }
     808              : 
     809              :     #[test]
     810            1 :     fn upper_cases_keywords_of_generated_text() {
     811            1 :         let f = FormattingProgram::new();
     812            1 :         let root = f.concat(&[f.text("select foo_bar, y1"), f.text(" from "), f.verbatim("select")]);
     813            1 :         let mut opts = options(FormattingMode::COMPACT, 120);
     814            1 :         opts.keyword_case = KeywordCase::UPPER;
     815            1 :         assert_eq!(f.render(root, &opts), "SELECT foo_bar, y1 FROM select");
     816              :         // Only lower-case words are keywords
     817            1 :         let mixed = f.text("Select order1 'between'");
     818            1 :         assert_eq!(f.render(mixed, &opts), "Select order1 'BETWEEN'");
     819            1 :     }
     820              : }
        

Generated by: LCOV version 2.0-1