///|
/// Where a pattern is: this decides its parentheses (see
/// `pattern_parens`).
priv enum PatternAt {
AnyPattern // case branch, let, tuple and list item, inside parentheses
ConsLeft // left of `::`: `::` and `as` patterns need parentheses
ConsTail // the last pattern right of `::`
AsInner // the pattern of an `as`: `::` and `as` patterns need parentheses
PatternArg // argument or destructuring pattern: also a constructor with arguments
}
///|
/// Whether the printer writes `p` at `at` in parentheses. A `::` or `as`
/// pattern needs them left of `::`, in an `as` and as an argument; a
/// constructor with arguments needs them as an argument. elm-format also
/// writes a constructor with arguments in parentheses before and after
/// `::` and in an `as` (Box.hs `formatPattern`: those places are
/// `SpaceSeparated`). elm-syntax reads `a :: b as c` as `a :: (b as c)`,
/// so an `as` after `::` has none.
fn pattern_parens(p : @ast.Pattern, at : PatternAt) -> Bool {
match (p, at) {
(NamedPattern(_, args), PatternArg | ConsLeft | ConsTail | AsInner) =>
!args.is_empty()
(UnConsPattern(_, _) | AsPattern(_, _), PatternArg | ConsLeft | AsInner) =>
true
_ => false
}
}
///|
/// The pattern at `path` with its comments. It runs on the work stack
/// (`Ctx::run`), so nested patterns do not recurse: a pattern of any depth
/// prints on every target.
fn Ctx::pattern_doc(
self : Ctx,
path : @syntax.NodePath,
p : @ast.Node[@ast.Pattern],
at : PatternAt,
) -> @pretty.Doc raise PrintError {
self.run(Pat(path, p, at))
}
///|
/// The plan of a pattern: its leading comments, its body and its trailing
/// comments, in that order.
fn Ctx::pattern_plan(
self : Ctx,
path : @syntax.NodePath,
p : @ast.Node[@ast.Pattern],
at : PatternAt,
) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) raise PrintError {
let (items, combine) = self.pattern_body_plan(path, p, at)
(
[
Leaf(() => self.leading(p.range)),
Plan(items, combine),
Leaf(() => self.trailing(p.range)),
],
docs => docs[0] + docs[1] + docs[2],
)
}
///|
/// A plan with one leaf.
fn leaf(
f : () -> @pretty.Doc raise PrintError,
) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) {
([Leaf(f)], docs => docs[0])
}
///|
fn join_docs(docs : Array[@pretty.Doc]) -> @pretty.Doc {
let mut d = @pretty.empty()
for x in docs {
d = d + x
}
d
}
///|
/// The plan of a pattern without its own comments. Patterns are on one
/// line, so a line comment in a pattern is followed by a line break
/// indented by 4 (`Ctx::after`). The leaves take comments in source
/// order; the items of a nested pattern are `Pat` work items.
fn Ctx::pattern_body_plan(
self : Ctx,
path : @syntax.NodePath,
p : @ast.Node[@ast.Pattern],
at : PatternAt,
) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) raise PrintError {
let parens = pattern_parens(p.value, at)
let wrap = (d : @pretty.Doc) => if parens { flat_parens(d) } else { d }
match p.value {
AllPattern => done(@pretty.text("_"))
UnitPattern =>
leaf(() => {
@pretty.text("(") + self.inner_alone(p.range) + @pretty.text(")")
})
CharPattern(c) => done(@pretty.text(char_literal(c)))
StringPattern(s) => done(self.string_doc(p.range, s))
IntPattern(v) => {
guard v >= 0L else { raise PrintError(path~, problem=NegativePattern) }
done(@pretty.text(self.integer_text(p.range, v)))
}
HexPattern(v) => {
guard v >= 0L else { raise PrintError(path~, problem=NegativePattern) }
done(@pretty.text(self.hex_literal_text(p.range, v)))
}
FloatPattern(x) => {
if self.float_lexeme_text(p.range, x) is Some(text) {
return done(@pretty.text(text))
}
guard !x.is_nan() && !x.is_inf() else {
raise PrintError(path~, problem=NonFiniteFloat(x))
}
guard !is_negative(x) else {
raise PrintError(path~, problem=NegativePattern)
}
done(@pretty.text(float_text(x)))
}
TuplePattern(items) => {
guard items.length() >= 2 else {
raise PrintError(path~, problem=ShortTuple)
}
self.flat_items_plan("(", ")", path, p.range, items)
}
ListPattern(items) => self.flat_items_plan("[", "]", path, p.range, items)
RecordPattern(names) =>
leaf(() => {
let docs = []
for i, x in names {
let name = self.lower_name(path.child("value", i), x.value)
docs.push((self.with_comments(x.range, name), x.range))
}
self.flat_sequence("{", "}", p.range, docs)
})
VarPattern(name) => done(self.lower_name(path, name))
NamedPattern(name_ref, args) => {
let head = qualified_upper(path, name_ref.module_name, name_ref.name)
guard !args.is_empty() else { return done(head) }
let items : Array[Work] = []
for i, a in args {
if i > 0 {
let previous = args[i - 1].range
items.push(Leaf(() => self.after(previous, " ")))
}
items.push(Pat(path.child("patterns", i), a, PatternArg))
}
if parens {
let last = args[args.length() - 1].range
items.push(Leaf(() => self.after(last, "")))
}
(items, docs => wrap(head + @pretty.text(" ") + join_docs(docs)))
}
UnConsPattern(_, _) => {
let items : Array[Work] = []
// The comments after each `::` that trail the pattern before it,
// taken before that pattern prints (`Ctx::after_token`).
let moved : Array[Array[@ast.Node[String]]] = []
let mut node = p
let mut q = path
let mut step = 0
while node.value is UnConsPattern(left, right) {
if node.range != p.range {
let r = node.range
items.push(Leaf(() => self.leading(r)))
}
let k = step
items.push(
Leaf(() => {
moved.push(self.after_token(left.range))
@pretty.empty()
}),
)
items.push(Pat(q.child("left", 0), left, ConsLeft))
items.push(
Leaf(() => {
self.separator(left.range, " ::", moved[k]) + @pretty.text(" ")
}),
)
q = q.child("right", 0)
node = right
step += 1
}
items.push(Pat(q, node, ConsTail))
(items, docs => wrap(join_docs(docs)))
}
AsPattern(inner, name) => {
let moved : Array[Array[@ast.Node[String]]] = []
let items : Array[Work] = [
Leaf(() => {
moved.push(self.after_token(inner.range))
@pretty.empty()
}),
Pat(path.child("pattern", 0), inner, AsInner),
Leaf(() => {
self.separator(inner.range, " as", moved[0]) +
@pretty.text(" ") +
self.with_comments(
name.range,
self.lower_name(path.child("name", 0), name.value),
)
}),
]
(items, docs => wrap(join_docs(docs)))
}
ParenthesizedPattern(x) =>
(
[
Pat(path.child("value", 0), x, AnyPattern),
Leaf(() => self.after(x.range, "") + self.inline_inner(p.range)),
],
docs => flat_parens(docs[0] + docs[1]),
)
}
}
///|
/// The plan of `( a, b )` or `[ a, b ]`, always on one line (see
/// `flat_sequence`): each item, the comments after it, and the comments of
/// the pattern at `owner` before the closing token.
fn Ctx::flat_items_plan(
self : Ctx,
open : String,
close : String,
path : @syntax.NodePath,
owner : @ast.Range,
items : ArrayView[@ast.Node[@ast.Pattern]],
) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) {
guard !items.is_empty() else {
return leaf(() => {
@pretty.text(open) +
self.inner_alone(owner, brace=open == "{") +
@pretty.text(close)
})
}
let count = items.length()
let work : Array[Work] = []
for i, x in items {
work.push(Pat(path.child("value", i), x, AnyPattern))
if i + 1 < count {
let r = x.range
work.push(Leaf(() => self.after(r, "")))
}
}
let last = items[count - 1].range
// The break after a line comment comes before the inner comments.
work.push(Leaf(() => self.after(last, "") + self.inline_inner(owner)))
(
work,
docs => {
let ds = []
for i in 0.. @pretty.Doc {
guard !items.is_empty() else {
return @pretty.text(open) +
self.inner_alone(owner, brace=open == "{") +
@pretty.text(close)
}
let docs = []
for i, item in items {
let (d, r) = item
docs.push(if i + 1 < items.length() { d + self.after(r, "") } else { d })
}
let (_, last) = items[items.length() - 1]
// The break after a line comment comes before the inner comments.
let end = self.after(last, "") + self.inline_inner(owner)
flat_sequence(open, close, docs, inner=end)
}
///|
/// The inner comments of the node at `owner` on the line: ` {- a -}`. A
/// line comment is followed by a line break indented by 4.
fn Ctx::inline_inner(self : Ctx, owner : @ast.Range) -> @pretty.Doc {
let mut d = @pretty.empty()
for c in self.take(Inner, owner) {
d = d + @pretty.text(" ") + comment_doc(c)
if is_line_comment(c) {
d = d + @pretty.nest(4, @pretty.hardline())
}
}
d
}