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