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(¤t_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(®s, 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 : }
|