LCOV - code coverage report
Current view: top level - dashql-core/src/formatter - program.rs Coverage Total Hit
Test: DashQL d805a4b Lines: 96.0 % 569 546
Test Date: 2026-11-09 20:22:13

            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         3762 : pub fn parse_formatting_mode(value: &str) -> FormattingMode {
      17         3762 :     match value {
      18         3762 :         "inline" => FormattingMode::INLINE,
      19         2508 :         "compact" => FormattingMode::COMPACT,
      20         1254 :         "pretty" => FormattingMode::PRETTY,
      21            0 :         _ => FormattingMode::COMPACT,
      22              :     }
      23         3762 : }
      24              : 
      25              : /// The name of a formatting mode
      26         2361 : pub fn formatting_mode_to_string(mode: FormattingMode) -> &'static str {
      27         2361 :     match mode {
      28          787 :         FormattingMode::INLINE => "inline",
      29          787 :         FormattingMode::COMPACT => "compact",
      30          787 :         FormattingMode::PRETTY => "pretty",
      31            0 :         _ => "compact",
      32              :     }
      33         2361 : }
      34              : 
      35              : /// Parse a formatting dialect, hyper is the default
      36         1256 : pub fn parse_formatting_dialect(value: &str) -> FormattingDialect {
      37         1256 :     match value {
      38         1256 :         "hyper" => FormattingDialect::HYPER,
      39           18 :         "trino" => FormattingDialect::TRINO,
      40            0 :         _ => FormattingDialect::HYPER,
      41              :     }
      42         1256 : }
      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              :     /// Parentheses or brackets that break also break an all-or-none join inside, with one item per line
     100              :     pub break_list: bool,
     101              : }
     102              : 
     103              : /// The options for rendering a formatting program
     104              : #[derive(Clone, Copy, Debug)]
     105              : pub struct FormattingRenderOptions {
     106              :     /// The maximum line width in bytes
     107              :     pub max_width: usize,
     108              :     /// The number of spaces per indentation level
     109              :     pub indentation_width: usize,
     110              :     /// Append the line width as `/*n*/` comment before every line break?
     111              :     pub debug_mode: bool,
     112              :     /// The formatting mode, INLINE renders every join and parenthesis flat
     113              :     pub mode: FormattingMode,
     114              :     /// The case of keywords in generated text
     115              :     pub keyword_case: KeywordCase,
     116              : }
     117              : 
     118              : impl Default for FormattingRenderOptions {
     119           15 :     fn default() -> Self {
     120           15 :         Self {
     121           15 :             max_width: FORMATTING_DEFAULT_MAX_WIDTH,
     122           15 :             indentation_width: FORMATTING_DEFAULT_INDENTATION_WIDTH,
     123           15 :             debug_mode: false,
     124           15 :             mode: FormattingMode::COMPACT,
     125           15 :             keyword_case: KeywordCase::LOWER,
     126           15 :         }
     127           15 :     }
     128              : }
     129              : 
     130              : /// A small document arena for width-aware SQL layout.
     131              : ///
     132              : /// The builder methods take `&self` (the arena lives in a `RefCell`), so that operations can be
     133              : /// nested: `f.concat(&[f.text("a"), f.text("b")])`.
     134              : pub struct FormattingProgram<'a> {
     135              :     /// The formatting config, selects the default join policy
     136              :     pub config: FormattingConfigT,
     137              :     /// The operations, register 0 is the empty document
     138              :     program: RefCell<Vec<FormattingOperation<'a>>>,
     139              : }
     140              : 
     141              : impl Default for FormattingProgram<'_> {
     142            0 :     fn default() -> Self {
     143            0 :         Self::new()
     144            0 :     }
     145              : }
     146              : 
     147              : impl<'a> FormattingProgram<'a> {
     148         4407 :     pub fn new() -> Self {
     149         4407 :         let mut config = FormattingConfigT::default();
     150         4407 :         config.mode = FormattingMode::COMPACT;
     151         4407 :         let program = Self { config, program: RefCell::new(Vec::new()) };
     152         4407 :         program.reset();
     153         4407 :         program
     154         4407 :     }
     155              : 
     156         5981 :     pub fn set_config(&mut self, value: &FormattingConfigT) {
     157         5981 :         self.config = value.clone();
     158         5981 :     }
     159              : 
     160        10388 :     pub fn reset(&self) {
     161        10388 :         let mut program = self.program.borrow_mut();
     162        10388 :         program.clear();
     163        10388 :         program.push(FormattingOperation::default());
     164        10388 :     }
     165              : 
     166              :     /// The number of operations, including the empty document
     167            1 :     pub fn len(&self) -> usize {
     168            1 :         self.program.borrow().len()
     169            1 :     }
     170              : 
     171              :     /// Is the program empty (only the empty document)?
     172            1 :     pub fn is_empty(&self) -> bool {
     173            1 :         self.len() <= 1
     174            1 :     }
     175              : 
     176              :     /// A copy of an operation
     177            2 :     pub fn get(&self, reg: FmtReg) -> FormattingOperation<'a> {
     178            2 :         self.program.borrow()[reg as usize].clone()
     179            2 :     }
     180              : 
     181        10684 :     pub fn empty(&self) -> FmtReg {
     182        10684 :         0
     183        10684 :     }
     184              : 
     185       472027 :     pub fn text(&self, text: &'a str) -> FmtReg {
     186       472027 :         self.push(FormattingOperation { code: FormattingOpCode::Text, text, ..Default::default() })
     187       472027 :     }
     188              : 
     189       124187 :     pub fn verbatim(&self, text: &'a str) -> FmtReg {
     190       124187 :         self.push(FormattingOperation { code: FormattingOpCode::Text, text, verbatim: true, ..Default::default() })
     191       124187 :     }
     192              : 
     193       180427 :     pub fn break_(&self) -> FmtReg {
     194       180427 :         self.push(FormattingOperation { code: FormattingOpCode::Break, ..Default::default() })
     195       180427 :     }
     196              : 
     197              :     /// Concatenate documents, skipping empty ones
     198       260116 :     pub fn concat(&self, parts: &[FmtReg]) -> FmtReg {
     199       596892 :         let parts: Vec<FmtReg> = parts.iter().copied().filter(|&p| p != 0).collect();
     200       260116 :         match parts.len() {
     201          301 :             0 => self.empty(),
     202        11656 :             1 => parts[0],
     203              :             _ => {
     204       248159 :                 self.push(FormattingOperation { code: FormattingOpCode::Concat, children: parts, ..Default::default() })
     205              :             }
     206              :         }
     207       260116 :     }
     208              : 
     209          331 :     pub fn indented(&self, child: FmtReg) -> FmtReg {
     210          331 :         if child == 0 {
     211            1 :             return self.empty();
     212          330 :         }
     213          330 :         self.push(FormattingOperation { code: FormattingOpCode::Indent, children: vec![child], ..Default::default() })
     214          331 :     }
     215              : 
     216        16144 :     pub fn parenthesized(&self, child: FmtReg) -> FmtReg {
     217        16144 :         if child == 0 {
     218            1 :             return self.empty();
     219        16143 :         }
     220        16143 :         self.push(FormattingOperation {
     221        16143 :             code: FormattingOpCode::Parenthesis,
     222        16143 :             children: vec![child],
     223        16143 :             ..Default::default()
     224        16143 :         })
     225        16144 :     }
     226              : 
     227           90 :     pub fn bracketed(&self, child: FmtReg) -> FmtReg {
     228           90 :         if child == 0 {
     229            0 :             return self.empty();
     230           90 :         }
     231           90 :         self.push(FormattingOperation { code: FormattingOpCode::Brackets, children: vec![child], ..Default::default() })
     232           90 :     }
     233              : 
     234              :     /// A list in parentheses that has one item per line if the parentheses break
     235          181 :     pub fn parenthesized_list(&self, list: FmtReg) -> FmtReg {
     236          181 :         if list == 0 {
     237            0 :             return self.empty();
     238          181 :         }
     239          181 :         self.push(FormattingOperation {
     240          181 :             code: FormattingOpCode::Parenthesis,
     241          181 :             children: vec![list],
     242          181 :             break_list: true,
     243          181 :             ..Default::default()
     244          181 :         })
     245          181 :     }
     246              : 
     247              :     /// A list in brackets that has one item per line if the brackets break
     248           24 :     pub fn bracketed_list(&self, list: FmtReg) -> FmtReg {
     249           24 :         if list == 0 {
     250            0 :             return self.empty();
     251           24 :         }
     252           24 :         self.push(FormattingOperation {
     253           24 :             code: FormattingOpCode::Brackets,
     254           24 :             children: vec![list],
     255           24 :             break_list: true,
     256           24 :             ..Default::default()
     257           24 :         })
     258           24 :     }
     259              : 
     260              :     /// Join documents with separators, skipping empty ones.
     261              :     ///
     262              :     /// Without a policy, joins break all or none in pretty mode and on overflow otherwise.
     263       179286 :     pub fn join(
     264       179286 :         &self,
     265       179286 :         items: &[FmtReg],
     266       179286 :         inline_separator: FmtReg,
     267       179286 :         break_separator: FmtReg,
     268       179286 :         join_policy: Option<FormattingJoinPolicy>,
     269       179286 :         indent_after_breaks: bool,
     270       179286 :     ) -> FmtReg {
     271       321588 :         let filtered: Vec<FmtReg> = items.iter().copied().filter(|&i| i != 0).collect();
     272       179286 :         match filtered.len() {
     273          165 :             0 => return self.empty(),
     274        92232 :             1 => return filtered[0],
     275        86889 :             _ => {}
     276              :         }
     277        86889 :         let selected_policy = join_policy.unwrap_or(if self.config.mode == FormattingMode::PRETTY {
     278        29232 :             FormattingJoinPolicy::BreakAllOrNone
     279              :         } else {
     280        57657 :             FormattingJoinPolicy::BreakOnOverflow
     281              :         });
     282        86889 :         self.push(FormattingOperation {
     283        86889 :             code: FormattingOpCode::Join,
     284        86889 :             children: filtered,
     285        86889 :             inline_separator,
     286        86889 :             break_separator,
     287        86889 :             indent_after_breaks,
     288        86889 :             join_policy: selected_policy,
     289        86889 :             ..Default::default()
     290        86889 :         })
     291       179286 :     }
     292              : 
     293      1128457 :     fn push(&self, doc: FormattingOperation<'a>) -> FmtReg {
     294      1128457 :         let mut program = self.program.borrow_mut();
     295      1128457 :         program.push(doc);
     296      1128457 :         (program.len() - 1) as FmtReg
     297      1128457 :     }
     298              : 
     299              :     /// Render a document
     300         6200 :     pub fn render(&self, root: FmtReg, options: &FormattingRenderOptions) -> String {
     301         6200 :         if root == 0 {
     302            1 :             return String::new();
     303         6199 :         }
     304         6199 :         let program = self.program.borrow();
     305         6199 :         render(&program, root, options)
     306         6200 :     }
     307              : }
     308              : 
     309              : const FORMATTING_INLINE_WIDTH_SAFETY_BUFFER: isize = 2;
     310              : 
     311        54080 : fn remaining_inline_width(max_width: usize, current_line_width: usize) -> isize {
     312        54080 :     let remaining = max_width as isize - current_line_width as isize;
     313        54080 :     remaining - FORMATTING_INLINE_WIDTH_SAFETY_BUFFER
     314        54080 : }
     315              : 
     316              : #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     317              : enum RendererOpCode {
     318              :     Format,
     319              :     JoinNextBreakOnOverflow,
     320              :     /// Close a parenthesis or brackets on the same line
     321              :     Close(char),
     322              :     /// Close a parenthesis or brackets on a new line
     323              :     CloseAfterBreak(char),
     324              : }
     325              : 
     326              : /// A pending document of an inline dry run
     327              : #[derive(Clone, Copy, Debug)]
     328              : struct InlineRenderCommand {
     329              :     /// The document to measure
     330              :     doc: FmtReg,
     331              :     /// The indentation of the document
     332              :     indentation: usize,
     333              : }
     334              : 
     335              : /// A pending step of the renderer
     336              : #[derive(Clone, Copy, Debug)]
     337              : struct RenderCommand {
     338              :     /// The step kind
     339              :     kind: RendererOpCode,
     340              :     /// The document to render, or the join that continues
     341              :     reg: FmtReg,
     342              :     /// The indentation of the document
     343              :     indentation: usize,
     344              :     /// The index of the next join child to render on overflow
     345              :     next_index: usize,
     346              : }
     347              : 
     348              : impl RenderCommand {
     349       547422 :     fn format(reg: FmtReg, indentation: usize) -> Self {
     350       547422 :         Self { kind: RendererOpCode::Format, reg, indentation, next_index: 0 }
     351       547422 :     }
     352              : }
     353              : 
     354        43637 : fn get_only_child(doc: &FormattingOperation) -> FmtReg {
     355        43637 :     debug_assert_eq!(doc.children.len(), 1);
     356        43637 :     doc.children[0]
     357        43637 : }
     358              : 
     359        94266 : fn push_inline_children(stack: &mut Vec<InlineRenderCommand>, children: &[FmtReg], indentation: usize) {
     360       269332 :     for &child in children.iter().rev() {
     361       269332 :         stack.push(InlineRenderCommand { doc: child, indentation });
     362       269332 :     }
     363        94266 : }
     364              : 
     365       101807 : fn push_docs(stack: &mut Vec<RenderCommand>, children: &[FmtReg], indentation: usize) {
     366       275498 :     for &child in children.iter().rev() {
     367       275498 :         stack.push(RenderCommand::format(child, indentation));
     368       275498 :     }
     369       101807 : }
     370              : 
     371        86198 : fn push_inline_join(stack: &mut Vec<InlineRenderCommand>, doc: &FormattingOperation, indentation: usize) {
     372       218850 :     for i in (1..=doc.children.len()).rev() {
     373       218850 :         stack.push(InlineRenderCommand { doc: doc.children[i - 1], indentation });
     374       218850 :         if i > 1 && doc.inline_separator != 0 {
     375       132252 :             stack.push(InlineRenderCommand { doc: doc.inline_separator, indentation });
     376       132252 :         }
     377              :     }
     378        86198 : }
     379              : 
     380        54634 : fn push_rendered_join(
     381        54634 :     stack: &mut Vec<RenderCommand>,
     382        54634 :     doc: &FormattingOperation,
     383        54634 :     indentation: usize,
     384        54634 :     separator: FmtReg,
     385        54634 : ) {
     386       142054 :     for i in (1..=doc.children.len()).rev() {
     387       142054 :         stack.push(RenderCommand::format(doc.children[i - 1], indentation));
     388       142054 :         if i > 1 && separator != 0 {
     389        87250 :             stack.push(RenderCommand::format(separator, indentation));
     390        87250 :         }
     391              :     }
     392        54634 : }
     393              : 
     394              : /// The width of the text that a document starts with, up to its first possible line break.
     395              : /// Returns the width and whether a line break could follow.
     396       115523 : fn leading_inline_width(reg: FmtReg, program: &[FormattingOperation]) -> (usize, bool) {
     397       115523 :     let doc = &program[reg as usize];
     398       115523 :     match doc.code {
     399            0 :         FormattingOpCode::Empty => (0, false),
     400        65239 :         FormattingOpCode::Text => (doc.text.len(), false),
     401        21002 :         FormattingOpCode::Break => (0, true),
     402              :         FormattingOpCode::Concat => {
     403        19636 :             let mut width = 0;
     404        42111 :             for &child in doc.children.iter() {
     405        42111 :                 let (child_width, breaks) = leading_inline_width(child, program);
     406        42111 :                 width += child_width;
     407        42111 :                 if breaks {
     408        10954 :                     return (width, true);
     409        31157 :                 }
     410              :             }
     411         8682 :             (width, false)
     412              :         }
     413           11 :         FormattingOpCode::Indent => doc.children.first().map_or((0, false), |&child| leading_inline_width(child, program)),
     414              :         // A join may break after its first item, parentheses after the opening one
     415         6688 :         FormattingOpCode::Join => match doc.children.first() {
     416         6688 :             Some(&first) => {
     417         6688 :                 let (width, breaks) = leading_inline_width(first, program);
     418         6688 :                 (width, breaks || doc.children.len() > 1)
     419              :             }
     420            0 :             None => (0, false),
     421              :         },
     422         2947 :         FormattingOpCode::Parenthesis | FormattingOpCode::Brackets => (1, true),
     423              :     }
     424       115523 : }
     425              : 
     426              : /// The width of the text that follows on the current line, up to the first possible line break of the pending steps.
     427              : /// A document fits inline only if this text fits behind it, e.g. the alias after a parenthesized window.
     428        46939 : fn trailing_inline_width(stack: &[RenderCommand], program: &[FormattingOperation]) -> usize {
     429        46939 :     let mut width = 0;
     430        88606 :     for command in stack.iter().rev() {
     431        88593 :         let (command_width, breaks) = match command.kind {
     432        66713 :             RendererOpCode::Format => leading_inline_width(command.reg, program),
     433         6689 :             RendererOpCode::Close(_) => (1, false),
     434         4256 :             RendererOpCode::CloseAfterBreak(_) => (0, true),
     435              :             // The next item of a join follows its separator, which may break. The safety buffer of the remaining
     436              :             // width covers a separator that stays on the line.
     437              :             RendererOpCode::JoinNextBreakOnOverflow => {
     438        10935 :                 let doc = &program[command.reg as usize];
     439        10935 :                 (0, command.next_index < doc.children.len())
     440              :             }
     441              :         };
     442        88593 :         width += command_width;
     443        88593 :         if breaks {
     444        41963 :             break;
     445        46630 :         }
     446              :     }
     447        46939 :     width
     448        46939 : }
     449              : 
     450              : /// Does the stack render inline within the remaining width?
     451        54074 : fn dry_run_inline(
     452        54074 :     mut remaining: isize,
     453        54074 :     mut stack: Vec<InlineRenderCommand>,
     454        54074 :     program: &[FormattingOperation],
     455        54074 :     options: &FormattingRenderOptions,
     456        54074 : ) -> bool {
     457       647538 :     while remaining >= 0 {
     458       639385 :         let Some(command) = stack.pop() else { break };
     459       595128 :         let doc = &program[command.doc as usize];
     460       595128 :         match doc.code {
     461            0 :             FormattingOpCode::Empty => {}
     462       431247 :             FormattingOpCode::Text => remaining -= doc.text.len() as isize,
     463            5 :             FormattingOpCode::Break => return false,
     464        94266 :             FormattingOpCode::Concat => push_inline_children(&mut stack, &doc.children, command.indentation),
     465              :             FormattingOpCode::Join => {
     466        53576 :                 if doc.join_policy == FormattingJoinPolicy::ForceBreak && doc.children.len() > 1 {
     467         1659 :                     return false;
     468        51917 :                 }
     469        51917 :                 push_inline_join(&mut stack, doc, command.indentation);
     470              :             }
     471              :             FormattingOpCode::Indent => {
     472            5 :                 if !doc.children.is_empty() {
     473            5 :                     stack.push(InlineRenderCommand {
     474            5 :                         doc: get_only_child(doc),
     475            5 :                         indentation: command.indentation + options.indentation_width,
     476            5 :                     });
     477            5 :                 }
     478              :             }
     479              :             FormattingOpCode::Parenthesis | FormattingOpCode::Brackets => {
     480        16029 :                 remaining -= 2;
     481        16029 :                 if !doc.children.is_empty() {
     482        16029 :                     stack.push(InlineRenderCommand { doc: get_only_child(doc), indentation: command.indentation });
     483        16029 :                 }
     484              :             }
     485              :         }
     486              :     }
     487        52410 :     remaining >= 0
     488        54074 : }
     489              : 
     490        34281 : fn join_fits_inline(
     491        34281 :     remaining: isize,
     492        34281 :     doc: &FormattingOperation,
     493        34281 :     indentation: usize,
     494        34281 :     program: &[FormattingOperation],
     495        34281 :     options: &FormattingRenderOptions,
     496        34281 : ) -> bool {
     497        34281 :     let mut stack = Vec::new();
     498        34281 :     push_inline_join(&mut stack, doc, indentation);
     499        34281 :     dry_run_inline(remaining, stack, program, options)
     500        34281 : }
     501              : 
     502         8948 : fn can_inline_join_step(
     503         8948 :     remaining: isize,
     504         8948 :     doc: &FormattingOperation,
     505         8948 :     next_index: usize,
     506         8948 :     indentation: usize,
     507         8948 :     program: &[FormattingOperation],
     508         8948 :     options: &FormattingRenderOptions,
     509         8948 : ) -> bool {
     510         8948 :     if next_index >= doc.children.len() {
     511            0 :         return true;
     512         8948 :     }
     513         8948 :     let mut stack = vec![InlineRenderCommand { doc: doc.children[next_index], indentation }];
     514         8948 :     if doc.inline_separator != 0 {
     515         8908 :         stack.push(InlineRenderCommand { doc: doc.inline_separator, indentation });
     516         8908 :     }
     517         8948 :     dry_run_inline(remaining, stack, program, options)
     518         8948 : }
     519              : 
     520        10851 : fn parenthesis_fits_inline(
     521        10851 :     remaining: isize,
     522        10851 :     doc: &FormattingOperation,
     523        10851 :     indentation: usize,
     524        10851 :     program: &[FormattingOperation],
     525        10851 :     options: &FormattingRenderOptions,
     526        10851 : ) -> bool {
     527        10851 :     if remaining < 2 {
     528            6 :         return false;
     529        10845 :     }
     530        10845 :     let mut stack = Vec::new();
     531        10845 :     if !doc.children.is_empty() {
     532        10845 :         stack.push(InlineRenderCommand { doc: get_only_child(doc), indentation });
     533        10845 :     }
     534        10845 :     dry_run_inline(remaining - 2, stack, program, options)
     535        10851 : }
     536              : 
     537        26151 : fn append_line_break(output: &mut String, current_line_width: &mut usize, indentation: usize, debug_mode: bool) {
     538        26151 :     if debug_mode {
     539            1 :         output.push_str(" /*");
     540            1 :         output.push_str(&current_line_width.to_string());
     541            1 :         output.push_str("*/");
     542        26150 :     }
     543        26151 :     output.push('\n');
     544        26151 :     output.extend(std::iter::repeat(' ').take(indentation));
     545        26151 :     *current_line_width = indentation;
     546        26151 : }
     547              : 
     548              : /// Append generated text, upper-casing the words that are keywords
     549            3 : fn append_generated_text(output: &mut String, text: &str) {
     550           72 :     let is_word = |ch: u8| ch.is_ascii_alphanumeric() || ch == b'_';
     551            3 :     let bytes = text.as_bytes();
     552            3 :     let mut pos = 0;
     553           17 :     while pos < bytes.len() {
     554           14 :         let start = pos;
     555           14 :         if !is_word(bytes[pos]) {
     556              :             // Words are ASCII, so runs of other bytes end at char boundaries
     557           16 :             while pos < bytes.len() && !is_word(bytes[pos]) {
     558            9 :                 pos += 1;
     559            9 :             }
     560            7 :             output.push_str(&text[start..pos]);
     561            7 :             continue;
     562            7 :         }
     563           45 :         while pos < bytes.len() && is_word(bytes[pos]) {
     564           38 :             pos += 1;
     565           38 :         }
     566            7 :         let word = &text[start..pos];
     567            7 :         if Keyword::find(word.as_bytes()).is_none() {
     568            4 :             output.push_str(word);
     569            4 :         } else {
     570            3 :             output.push_str(&word.to_ascii_uppercase());
     571            3 :         }
     572              :     }
     573            3 : }
     574              : 
     575         6199 : fn render(program: &[FormattingOperation], root: FmtReg, options: &FormattingRenderOptions) -> String {
     576         6199 :     let mut output = String::new();
     577         6199 :     let mut current_line_width = 0usize;
     578         6199 :     let force_inline = options.mode == FormattingMode::INLINE;
     579              : 
     580         6199 :     let mut stack = vec![RenderCommand::format(root, 0)];
     581       580803 :     while let Some(command) = stack.pop() {
     582       574604 :         match command.kind {
     583        14327 :             RendererOpCode::Close(close) => {
     584        14327 :                 output.push(close);
     585        14327 :                 current_line_width += 1;
     586        14327 :                 continue;
     587              :             }
     588         2100 :             RendererOpCode::CloseAfterBreak(close) => {
     589         2100 :                 append_line_break(&mut output, &mut current_line_width, command.indentation, options.debug_mode);
     590         2100 :                 output.push(close);
     591         2100 :                 current_line_width += 1;
     592         2100 :                 continue;
     593              :             }
     594              :             RendererOpCode::JoinNextBreakOnOverflow => {
     595        10755 :                 let doc = &program[command.reg as usize];
     596        10755 :                 if command.next_index >= doc.children.len() {
     597         1807 :                     continue;
     598         8948 :                 }
     599         8948 :                 let next = RenderCommand {
     600         8948 :                     kind: RendererOpCode::JoinNextBreakOnOverflow,
     601         8948 :                     reg: command.reg,
     602         8948 :                     indentation: command.indentation,
     603         8948 :                     next_index: command.next_index + 1,
     604         8948 :                 };
     605         8948 :                 let child = doc.children[command.next_index];
     606              :                 // The last item is followed by the text after the join
     607         8948 :                 let trailing = if command.next_index + 1 >= doc.children.len() {
     608         1807 :                     trailing_inline_width(&stack, program)
     609              :                 } else {
     610         7141 :                     0
     611              :                 };
     612         8948 :                 if can_inline_join_step(
     613         8948 :                     remaining_inline_width(options.max_width, current_line_width) - trailing as isize,
     614         8948 :                     doc,
     615         8948 :                     command.next_index,
     616         8948 :                     command.indentation,
     617         8948 :                     program,
     618         8948 :                     options,
     619              :                 ) {
     620         6048 :                     stack.push(next);
     621         6048 :                     stack.push(RenderCommand::format(child, command.indentation));
     622         6048 :                     if doc.inline_separator != 0 {
     623         6018 :                         stack.push(RenderCommand::format(doc.inline_separator, command.indentation));
     624         6018 :                     }
     625              :                 } else {
     626         2900 :                     let next_is_parenthesis = (child as usize) < program.len()
     627         2900 :                         && program[child as usize].code == FormattingOpCode::Parenthesis;
     628         2900 :                     let use_inline_separator = next_is_parenthesis && doc.inline_separator != 0;
     629         2900 :                     stack.push(next);
     630         2900 :                     stack.push(RenderCommand::format(child, command.indentation));
     631         2900 :                     if doc.break_separator != 0 {
     632         2900 :                         let separator = if use_inline_separator { doc.inline_separator } else { doc.break_separator };
     633         2900 :                         stack.push(RenderCommand::format(separator, command.indentation));
     634            0 :                     }
     635              :                 }
     636         8948 :                 continue;
     637              :             }
     638       547422 :             RendererOpCode::Format => {}
     639              :         }
     640              : 
     641       547422 :         let doc = &program[command.reg as usize];
     642       547422 :         match doc.code {
     643            0 :             FormattingOpCode::Empty => {}
     644              :             FormattingOpCode::Text => {
     645       350475 :                 if options.keyword_case == KeywordCase::UPPER && !doc.verbatim {
     646            3 :                     append_generated_text(&mut output, doc.text);
     647       350472 :                 } else {
     648       350472 :                     output.push_str(doc.text);
     649       350472 :                 }
     650       350475 :                 current_line_width += doc.text.len();
     651              :             }
     652        21951 :             FormattingOpCode::Break => {
     653        21951 :                 append_line_break(&mut output, &mut current_line_width, command.indentation, options.debug_mode);
     654        21951 :             }
     655       101807 :             FormattingOpCode::Concat => push_docs(&mut stack, &doc.children, command.indentation),
     656              :             FormattingOpCode::Join => {
     657        56431 :                 if force_inline
     658        37747 :                     || (doc.join_policy != FormattingJoinPolicy::ForceBreak
     659        34281 :                         && join_fits_inline(
     660        34281 :                             remaining_inline_width(options.max_width, current_line_width)
     661        34281 :                                 - trailing_inline_width(&stack, program) as isize,
     662        34281 :                             doc,
     663        34281 :                             command.indentation,
     664        34281 :                             program,
     665        34281 :                             options,
     666              :                         ))
     667        48142 :                 {
     668        48142 :                     push_rendered_join(&mut stack, doc, command.indentation, doc.inline_separator);
     669        48142 :                 } else {
     670         8289 :                     let indent =
     671         8289 :                         command.indentation + if doc.indent_after_breaks { options.indentation_width } else { 0 };
     672         8289 :                     match doc.join_policy {
     673         6482 :                         FormattingJoinPolicy::BreakAllOrNone | FormattingJoinPolicy::ForceBreak => {
     674         6482 :                             push_rendered_join(&mut stack, doc, indent, doc.break_separator);
     675         6482 :                         }
     676              :                         FormattingJoinPolicy::BreakOnOverflow => {
     677         1807 :                             if !doc.children.is_empty() {
     678         1807 :                                 if doc.children.len() > 1 {
     679         1807 :                                     stack.push(RenderCommand {
     680         1807 :                                         kind: RendererOpCode::JoinNextBreakOnOverflow,
     681         1807 :                                         reg: command.reg,
     682         1807 :                                         indentation: indent,
     683         1807 :                                         next_index: 1,
     684         1807 :                                     });
     685         1807 :                                 }
     686         1807 :                                 stack.push(RenderCommand::format(doc.children[0], indent));
     687            0 :                             }
     688              :                         }
     689              :                     }
     690              :                 }
     691              :             }
     692              :             FormattingOpCode::Indent => {
     693          331 :                 if !doc.children.is_empty() {
     694          331 :                     stack.push(RenderCommand::format(
     695          331 :                         get_only_child(doc),
     696          331 :                         command.indentation + options.indentation_width,
     697          331 :                     ));
     698          331 :                 }
     699              :             }
     700              :             FormattingOpCode::Parenthesis | FormattingOpCode::Brackets => {
     701        16427 :                 let (open, close) = if doc.code == FormattingOpCode::Brackets { ('[', ']') } else { ('(', ')') };
     702        16427 :                 let render_flat = force_inline
     703        10851 :                     || parenthesis_fits_inline(
     704        10851 :                         remaining_inline_width(options.max_width, current_line_width)
     705        10851 :                             - trailing_inline_width(&stack, program) as isize,
     706        10851 :                         doc,
     707        10851 :                         command.indentation,
     708        10851 :                         program,
     709        10851 :                         options,
     710              :                     );
     711        16427 :                 output.push(open);
     712        16427 :                 current_line_width += 1;
     713        16427 :                 let close = if render_flat {
     714        14327 :                     RendererOpCode::Close(close)
     715              :                 } else {
     716         2100 :                     append_line_break(
     717         2100 :                         &mut output,
     718         2100 :                         &mut current_line_width,
     719         2100 :                         command.indentation + options.indentation_width,
     720         2100 :                         options.debug_mode,
     721              :                     );
     722         2100 :                     RendererOpCode::CloseAfterBreak(close)
     723              :                 };
     724        16427 :                 stack.push(RenderCommand { kind: close, reg: 0, indentation: command.indentation, next_index: 0 });
     725        16427 :                 if !doc.children.is_empty() {
     726        16427 :                     let child = get_only_child(doc);
     727        16427 :                     let indentation = command.indentation + options.indentation_width;
     728        16427 :                     let list = &program[child as usize];
     729        16427 :                     if doc.break_list
     730          205 :                         && !render_flat
     731           25 :                         && list.code == FormattingOpCode::Join
     732           19 :                         && list.join_policy == FormattingJoinPolicy::BreakAllOrNone
     733              :                     {
     734           10 :                         let indentation = indentation + if list.indent_after_breaks { options.indentation_width } else { 0 };
     735           10 :                         push_rendered_join(&mut stack, list, indentation, list.break_separator);
     736        16417 :                     } else {
     737        16417 :                         stack.push(RenderCommand::format(child, indentation));
     738        16417 :                     }
     739            0 :                 }
     740              :             }
     741              :         }
     742              :     }
     743         6199 :     output
     744         6199 : }
     745              : 
     746              : #[cfg(test)]
     747              : mod tests {
     748              :     use super::*;
     749              : 
     750           15 :     fn options(mode: FormattingMode, max_width: usize) -> FormattingRenderOptions {
     751           15 :         FormattingRenderOptions { mode, max_width, ..Default::default() }
     752           15 :     }
     753              : 
     754            5 :     fn comma_list<'a>(f: &FormattingProgram<'a>, items: &[&'a str], policy: Option<FormattingJoinPolicy>) -> FmtReg {
     755           14 :         let regs: Vec<FmtReg> = items.iter().map(|i| f.text(i)).collect();
     756            5 :         let break_separator = f.concat(&[f.text(","), f.break_()]);
     757            5 :         f.join(&regs, f.text(", "), break_separator, policy, true)
     758            5 :     }
     759              : 
     760              :     #[test]
     761            1 :     fn concat_skips_empty_documents() {
     762            1 :         let f = FormattingProgram::new();
     763            1 :         let a = f.text("a");
     764            1 :         assert_eq!(f.concat(&[]), 0);
     765            1 :         assert_eq!(f.concat(&[0, a, 0]), a);
     766            1 :         assert_eq!(f.indented(0), 0);
     767            1 :         assert_eq!(f.parenthesized(0), 0);
     768            1 :         assert_eq!(f.join(&[0, a], 0, 0, None, false), a);
     769            1 :         let both = f.concat(&[a, f.text("b")]);
     770            1 :         assert_eq!(f.render(both, &options(FormattingMode::COMPACT, 120)), "ab");
     771            1 :         assert_eq!(f.render(0, &options(FormattingMode::COMPACT, 120)), "");
     772            1 :     }
     773              : 
     774              :     #[test]
     775            1 :     fn join_breaks_all_or_none() {
     776            1 :         let f = FormattingProgram::new();
     777            1 :         let list = comma_list(&f, &["aaaa", "bbbb", "cccc"], Some(FormattingJoinPolicy::BreakAllOrNone));
     778            1 :         let root = f.concat(&[f.text("x "), list]);
     779            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 120)), "x aaaa, bbbb, cccc");
     780              :         // The safety buffer of 2 counts: 18 characters need a width of 20.
     781            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 20)), "x aaaa, bbbb, cccc");
     782            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 19)), "x aaaa,\n  bbbb,\n  cccc");
     783            1 :         assert_eq!(f.render(root, &options(FormattingMode::INLINE, 1)), "x aaaa, bbbb, cccc");
     784            1 :     }
     785              : 
     786              :     #[test]
     787            1 :     fn join_breaks_on_overflow() {
     788            1 :         let f = FormattingProgram::new();
     789            1 :         let list = comma_list(&f, &["aaaa", "bbbb", "cccc"], Some(FormattingJoinPolicy::BreakOnOverflow));
     790            1 :         assert_eq!(f.render(list, &options(FormattingMode::COMPACT, 14)), "aaaa, bbbb,\n  cccc");
     791            1 :     }
     792              : 
     793              :     #[test]
     794            1 :     fn join_force_break() {
     795            1 :         let f = FormattingProgram::new();
     796            1 :         let list = comma_list(&f, &["a", "b"], Some(FormattingJoinPolicy::ForceBreak));
     797            1 :         assert_eq!(f.render(list, &options(FormattingMode::COMPACT, 120)), "a,\n  b");
     798            1 :         assert_eq!(f.render(list, &options(FormattingMode::INLINE, 120)), "a, b");
     799              :         // A forced break inside a parenthesis breaks the parenthesis as well
     800            1 :         let parens = f.parenthesized(list);
     801            1 :         assert_eq!(f.render(parens, &options(FormattingMode::COMPACT, 120)), "(\n  a,\n    b\n)");
     802            1 :     }
     803              : 
     804              :     #[test]
     805            1 :     fn default_join_policy_depends_on_mode() {
     806            1 :         let mut f = FormattingProgram::new();
     807            1 :         let overflow = comma_list(&f, &["a"; 3], None);
     808            1 :         let mut config = FormattingConfigT::default();
     809            1 :         config.mode = FormattingMode::PRETTY;
     810            1 :         f.set_config(&config);
     811            1 :         let all_or_none = comma_list(&f, &["a"; 3], None);
     812            1 :         assert_eq!(f.get(overflow).join_policy, FormattingJoinPolicy::BreakOnOverflow);
     813            1 :         assert_eq!(f.get(all_or_none).join_policy, FormattingJoinPolicy::BreakAllOrNone);
     814            1 :         f.reset();
     815            1 :         assert!(f.is_empty());
     816            1 :     }
     817              : 
     818              :     #[test]
     819            1 :     fn parenthesis_breaks_when_too_wide() {
     820            1 :         let f = FormattingProgram::new();
     821            1 :         let inner = f.text("0123456789");
     822            1 :         let root = f.concat(&[f.text("f"), f.parenthesized(inner)]);
     823            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 15)), "f(0123456789)");
     824            1 :         assert_eq!(f.render(root, &options(FormattingMode::COMPACT, 14)), "f(\n  0123456789\n)");
     825            1 :     }
     826              : 
     827              :     #[test]
     828            1 :     fn overflowing_parenthesis_keeps_inline_separator() {
     829            1 :         let f = FormattingProgram::new();
     830            1 :         let items = [f.text("aaaa"), f.parenthesized(f.text("bbbbbbbb"))];
     831            1 :         let join = f.join(&items, f.text(" "), f.break_(), Some(FormattingJoinPolicy::BreakOnOverflow), false);
     832            1 :         assert_eq!(f.render(join, &options(FormattingMode::COMPACT, 12)), "aaaa (\n  bbbbbbbb\n)");
     833            1 :     }
     834              : 
     835              :     #[test]
     836            1 :     fn indentation_and_debug_mode() {
     837            1 :         let f = FormattingProgram::new();
     838            1 :         let root = f.concat(&[f.text("a"), f.indented(f.concat(&[f.break_(), f.text("b")]))]);
     839            1 :         let mut opts = options(FormattingMode::COMPACT, 120);
     840            1 :         opts.indentation_width = 4;
     841            1 :         assert_eq!(f.render(root, &opts), "a\n    b");
     842            1 :         opts.debug_mode = true;
     843            1 :         assert_eq!(f.render(root, &opts), "a /*1*/\n    b");
     844            1 :     }
     845              : 
     846              :     #[test]
     847            1 :     fn upper_cases_keywords_of_generated_text() {
     848            1 :         let f = FormattingProgram::new();
     849            1 :         let root = f.concat(&[f.text("select foo_bar, y1"), f.text(" from "), f.verbatim("select")]);
     850            1 :         let mut opts = options(FormattingMode::COMPACT, 120);
     851            1 :         opts.keyword_case = KeywordCase::UPPER;
     852            1 :         assert_eq!(f.render(root, &opts), "SELECT foo_bar, y1 FROM select");
     853              :         // Only lower-case words are keywords
     854            1 :         let mixed = f.text("Select order1 'between'");
     855            1 :         assert_eq!(f.render(mixed, &opts), "Select order1 'BETWEEN'");
     856            1 :     }
     857              : }
        

Generated by: LCOV version 2.0-1