// Portions adapted from google/go-jsonnet v0.21.0 (Apache-2.0).
// https://github.com/google/go-jsonnet/tree/67968688d9952f50662e979fab1bef889d0d3e57
// Source: internal/formatter/strip.go
// Source: internal/parser/lexer.go
// Source: ast/fodder.go
// Copyright 2019 Google Inc. All rights reserved.
// Copyright 2016 Google Inc. All rights reserved.
// Copyright 2018 Google Inc. All rights reserved.
// Modifications: Adapted stripping and fodder classification to local token/trivia containers.
// License and retained attribution: LICENSE and NOTICE at the module root.
// Fodder classification and stripping follow go-jsonnet v0.21.0 lexer.go,
// ast/fodder.go and internal/formatter/strip.go. Apache-2.0; see NOTICE.
///|
priv enum FormatStripFodder {
RetainedLineEnd(Int, Int)
RemovedParagraph
RemovedInterstitial
}
///|
fn format_whitespace(raw : String) -> (Int, Int) {
let mut lines = 0
let mut indent = 0
for c in raw.to_array() {
match c {
'\n' => {
lines += 1
indent = 0
}
' ' => indent += 1
'\t' => indent += 8
_ => ()
}
}
(lines, indent)
}
///|
fn format_stripped_lines(
token : Token,
first : Bool,
ensure_newline : Bool,
max_blank_lines : Int,
) -> Array[(Int, Int)] {
let fodder : Array[FormatStripFodder] = []
let mut fresh_line = first
let trivia = token.leading
for i = 0; i < trivia.length(); {
let part = trivia[i]
if part.kind == Whitespace {
let (lines, indent) = format_whitespace(part.raw)
// Whitespace directly before EOF is discarded by Go's outer lexer loop.
if lines > 0 && !(token.text == "" && i + 1 == trivia.length()) {
fodder.push(RetainedLineEnd(lines, indent))
}
if lines > 0 {
fresh_line = true
}
continue i + 1
}
let next = trivia.get(i + 1)
let (lines, indent, advance) = match next {
Some({ kind: Whitespace, raw, .. }) => {
let (lines, indent) = format_whitespace(raw)
(lines, indent, 2)
}
_ => (0, 0, 1)
}
if part.kind == LineComment {
if fresh_line {
fodder.push(RemovedParagraph)
} else {
fodder.push(RetainedLineEnd(if lines > 0 { lines } else { 1 }, indent))
}
} else if part.kind == BlockComment {
if part.raw.contains("\n") {
if !(fodder.last() is Some(RetainedLineEnd(_, _) | RemovedParagraph)) {
fodder.push(RetainedLineEnd(1, if lines > 0 { indent } else { 0 }))
}
fodder.push(RemovedParagraph)
} else {
fodder.push(RemovedInterstitial)
if lines > 0 {
fodder.push(RetainedLineEnd(lines, indent))
}
}
}
fresh_line = lines > 0
continue i + advance
}
let mut start = 0
if first {
while start < fodder.length() && fodder[start] is RetainedLineEnd(_, _) {
start += 1
}
}
// FixNewlines runs before StripComments. Paragraphs already have a clean
// endline; an interstitial needs a distinct line-end element.
if ensure_newline &&
!(fodder.last() is Some(RetainedLineEnd(_, _) | RemovedParagraph)) {
fodder.push(RetainedLineEnd(1, 0))
}
let lines : Array[(Int, Int)] = []
for element in fodder[start:] {
if element is RetainedLineEnd(count, indent) {
lines.push(
(
if max_blank_lines > 0 && count - 1 > max_blank_lines {
max_blank_lines + 1
} else {
count
},
indent,
),
)
}
}
if token.text == "" && !lines.is_empty() {
let last = lines.length() - 1
lines[last] = (1, lines[last].1)
}
lines
}