///|
/// Where an expression is: this decides its parentheses (see the table in
/// the plan, and spec section 4.3).
priv enum ExprAt {
AnyExpr
/// An operand of an operator chain, other than the last.
Operand
/// The last operand of a chain, after an operator other than `<|`.
LastOperand
/// The last operand of a chain, after `<|`.
Piped
ExprArg
Head
Negated
Target
}
///|
fn is_negative_literal(e : @ast.Expression) -> Bool {
match e {
Integer(x) | Hex(x) => x < 0L
Floatable(x) => is_negative(x)
_ => false
}
}
///|
fn is_atom(e : @ast.Expression) -> Bool {
match e {
Application(_)
| OperatorApplication(_, _, _, _)
| Negation(_)
| IfBlock(_, _, _)
| CaseExpression(_)
| LetExpression(_)
| LambdaExpression(_) => false
_ => !is_negative_literal(e)
}
}
///|
fn needs_parens(e : @ast.Expression, at : ExprAt) -> Bool {
match at {
AnyExpr | LastOperand | Piped => false
Operand =>
e
is (LambdaExpression(_)
| IfBlock(_, _, _)
| CaseExpression(_)
| LetExpression(_))
ExprArg => !is_atom(e) && !(e is Negation(_)) && !is_negative_literal(e)
Head => !is_atom(e)
Negated => !is_atom(e) || e is RecordAccessFunction(_)
// The parser reads `.f` after a lower-case name, a record, a record
// update or a closing parenthesis as a record access; after anything
// else (`"s".f`, `[].f`, `( a, b ).f`, `1.f`, `Just.f`) it reads a
// record access function or rejects the text.
Target =>
match e {
FunctionOrValue(_, name) => is_upper(name)
RecordExpr(_)
| RecordUpdateExpression(_, _)
| ParenthesizedExpression(_)
| RecordAccess(_, _) => false
_ => true
}
}
}
///|
/// elm-format's syntax context (Box.hs `SyntaxContext`): what an
/// expression is, and what its place allows without parentheses.
priv enum SyntaxContext {
SyntaxSeparated
SpaceSeparated
InfixSeparated
AmbiguousEnd
}
///|
/// The context of an expression (the first part of elm-format's
/// `formatExpression` result): an application is `SpaceSeparated`, an
/// operator chain `InfixSeparated`, a lambda, `if`, `case` or `let`
/// `AmbiguousEnd` (it ends at the end of the text), anything else
/// `SyntaxSeparated`.
fn syntax_context(e : @ast.Expression) -> SyntaxContext {
match e {
Application(_) => SpaceSeparated
OperatorApplication(_, _, _, _) => InfixSeparated
LambdaExpression(_)
| IfBlock(_, _, _)
| CaseExpression(_)
| LetExpression(_) => AmbiguousEnd
_ => SyntaxSeparated
}
}
///|
/// The context that a place gives (the `syntaxParens` calls of
/// elm-format's `formatExpression` and `formatBinops`).
fn place_context(at : ExprAt) -> SyntaxContext {
match at {
AnyExpr => SyntaxSeparated
Operand | LastOperand | Head => InfixSeparated
Piped => AmbiguousEnd
ExprArg | Negated | Target => SpaceSeparated
}
}
///|
/// Whether elm-format writes `e` at `at` in parentheses (Box.hs
/// `needsParensInContext`).
fn elm_format_parens(e : @ast.Expression, at : ExprAt) -> Bool {
match (syntax_context(e), place_context(at)) {
(SpaceSeparated, SpaceSeparated)
| (InfixSeparated, SpaceSeparated)
| (InfixSeparated, InfixSeparated)
| (AmbiguousEnd, SpaceSeparated)
| (AmbiguousEnd, InfixSeparated)
| (InfixSeparated, AmbiguousEnd) => true
_ => false
}
}
///|
/// The parentheses around `e` at `at`: the ones that the parser needs
/// (`needs_parens`) and the ones that elm-format writes. elm-format writes
/// a lambda, `if`, `case` or `let` at the end of a chain in parentheses,
/// except after `<|`; the parser does not need them.
fn parens_at(e : @ast.Expression, at : ExprAt) -> Bool {
needs_parens(e, at) || elm_format_parens(e, at)
}
///|
/// An item of the expression printer's work stack. The printer does not
/// recurse per nesting level: `Ctx::run` pops items until the stack is
/// empty, so deep ASTs do not overflow the call stack (wasm overflows at a
/// few hundred frames).
priv enum Work {
/// Print this expression: check its level, then push its plan.
Expr(@syntax.NodePath, Int, @ast.Node[@ast.Expression], ExprAt)
/// Make a doc that has no nested expression (a name, a pattern, a type).
/// It runs when it is popped, so errors come in source order.
Leaf(() -> @pretty.Doc raise PrintError)
/// Run these items in order, then combine their docs into one.
Plan(Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc)
/// Pop this many docs and push their combination.
Build(Int, (Array[@pretty.Doc]) -> @pretty.Doc)
}
///|
/// Runs the work stack from `start` and returns its one doc. Items give
/// their docs in order: each `Plan` and `Expr` pushes a `Build`, then its
/// items in reverse, so the first item runs first.
fn Ctx::run(self : Ctx, start : Work) -> @pretty.Doc raise PrintError {
let work : Array[Work] = [start]
let results : Array[@pretty.Doc] = []
fn push_plan(items : Array[Work], combine) {
work.push(Build(items.length(), combine))
for i = items.length() - 1; i >= 0; i = i - 1 {
work.push(items[i])
}
}
while work.pop() is Some(item) {
match item {
Expr(path, level, e, at) => {
check_level(path, level)
// The leading comments are taken now, before the parts; the
// trailing comments after them.
let lead = self.leading(e.range)
let wrap = (d : @pretty.Doc) => lead + d + self.trailing(e.range)
// elm-format Box.hs formatExpression `Parens`: parentheses with
// no comments inside go, and the expression in them takes their
// place (see `normalize_file`).
if e.value is ParenthesizedExpression(x) &&
!parens_at(x.value, at) &&
!self.comment_in_parens(e.range, x.range) {
push_plan([Expr(path.child("parenthesized", 0), level + 1, x, at)], docs => {
wrap(docs[0])
})
continue
}
let (items, combine) = self.expr_plan(path, level, e)
if parens_at(e.value, at) {
let split = self.join()
push_plan(items, docs => wrap(parens(split, combine(docs))))
} else {
push_plan(items, docs => wrap(combine(docs)))
}
}
Leaf(f) => results.push(f())
Plan(items, combine) => push_plan(items, combine)
Build(count, combine) => {
let start = results.length() - count
let docs = []
for i in start.. @pretty.Doc raise PrintError {
self.run(Expr(path, level, e, at))
}
///|
/// A plan with no items: the doc is already known.
fn done(d : @pretty.Doc) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) {
([], _ => d)
}
///|
/// The items of `label = value` for each setter, four per setter: the
/// comments before its `,` (see `Ctx::before_item`), the label, the value
/// and the comments after the setter.
fn Ctx::setter_items(
self : Ctx,
path : @syntax.NodePath,
field_name : String,
level : Int,
setters : ArrayView[@ast.Node[@ast.RecordSetter]],
) -> Array[Work] {
let items = []
for i, s in setters {
let p = path.child(field_name, i)
let name = s.value.field
let previous = if i > 0 { Some(setters[i - 1].range) } else { None }
let next = setters.get(i + 1).map(n => n.range)
items.push(
Leaf(() => {
match previous {
Some(r) => self.before_item(Some(r), s.range)
None => @pretty.empty()
}
}),
)
let value = s.value.expression
// The comments after the value go after the field, outside its group:
// taken before the value.
let value_trail = []
items.push(
Leaf(() => {
let label = self.lower_name(p.child("field", 0), name.value)
value_trail.append(self.take(Trailing, value.range))
let moved = self.after_token(name.range)
self.after_comma(previous, s.range) +
self.with_comments(name.range, label) +
self.separator(name.range, " =", moved)
}),
)
items.push(Expr(p.child("expression", 0), level + 1, value, AnyExpr))
items.push(
Leaf(() => {
comments_after(value_trail) + self.trailing_before_comma(s.range, next)
}),
)
}
items
}
///|
/// The setter docs from `docs[from]` on, four per setter (see
/// `Ctx::setter_items`), and the comments before each.
fn Ctx::setter_docs(
self : Ctx,
setters : ArrayView[@ast.Node[@ast.RecordSetter]],
docs : Array[@pretty.Doc],
from : Int,
) -> (Array[@pretty.Doc], Array[@pretty.Doc]) {
let fields = []
let leads = []
for k, s in setters {
let i = from + 4 * k
leads.push(docs[i])
// elm-format Parse/Common.hs pair (`checkMultiline`): a line break
// anywhere from the label to the end of the value puts the value on
// the next line.
let split = self.split_within({
start: s.value.field.range.start,
end: s.value.expression.range.end,
})
fields.push(
field(split, docs[i + 1], @pretty.empty(), docs[i + 2]) + docs[i + 3],
)
}
(fields, leads)
}
///|
/// The last setter's range, if any.
fn last_range(setters : ArrayView[@ast.Node[@ast.RecordSetter]]) -> @ast.Range? {
setters.last().map(s => s.range)
}
///|
/// A number literal: `digits(x)`, or a negation of it for a negative `x`.
fn number_doc(
path : @syntax.NodePath,
x : Int64,
digits : (Int64) -> String,
) -> @pretty.Doc raise PrintError {
if x >= 0L {
@pretty.text(digits(x))
} else if is_min_int(x) {
raise PrintError(path~, problem=UnrepresentableInt(x))
} else {
@pretty.text("-" + digits(-x))
}
}
///|
/// The work items of an expression's direct parts, in source order, and how
/// their docs combine. It checks the expression itself (its shape, its
/// literal text) before any part; nested expressions are `Expr` items.
fn Ctx::expr_plan(
self : Ctx,
path : @syntax.NodePath,
level : Int,
e : @ast.Node[@ast.Expression],
) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) raise PrintError {
match e.value {
UnitExpr =>
done(@pretty.text("(") + self.inner_alone(e.range) + @pretty.text(")"))
Application(items) => {
guard items.length() >= 2 else {
raise PrintError(path~, problem=ShortApplication)
}
// elm-format Parse/Expression.hs appExpr: the comments after an item
// are the comments before the next argument (`formatPreCommented`).
// Three docs per item: the comments it takes from the item before it
// (taken before that item takes them as its own), its comments and
// the item.
let moved : Array[Array[@ast.Node[String]]] = Array::makei(
items.length(),
_ => [],
)
let parts = []
for i, x in items {
parts.push(
Leaf(() => {
if i + 1 < items.length() {
moved[i + 1] = self.take(Trailing, x.range)
}
@pretty.empty()
}),
)
parts.push(Leaf(() => comments_before(moved[i], x.range.start)))
parts.push(
Expr(
path.child("application", i),
level + 1,
x,
if i == 0 {
Head
} else {
ExprArg
},
),
)
}
// elm-format Parse/Expression.hs appExpr: `FASplitFirst` when the
// source has a line break in the function or in or before the first
// argument; `FAJoinFirst SplitAll` when it has one in or before a
// later argument.
let first = self.split_within({
start: items[0].range.start,
end: items[1].range.end,
})
// With one argument there are no later arguments: `rest` is not used.
let rest = if items.length() > 2 {
self.split_from({ start: items[2].range.start, end: e.range.end, })
} else {
self.join()
}
(
parts,
docs => {
let item_docs = []
for i = 0; i + 2 < docs.length(); i = i + 3 {
item_docs.push(docs[i + 1] + docs[i + 2])
}
application(first, rest, item_docs[0], item_docs[1:].to_owned())
},
)
}
OperatorApplication(_, _, _, _) => self.chain_plan(path, level, e)
FunctionOrValue(module_, name) =>
done(self.qualified_value(path, module_, name))
IfBlock(c, t, f) => self.if_plan(path, level, c, t, f)
PrefixOperator(symbol) =>
done(@pretty.text("(" + self.operator_symbol(path, symbol) + ")"))
Operator(symbol) => done(@pretty.text(self.operator_symbol(path, symbol)))
Integer(x) => done(number_doc(path, x, n => self.integer_text(e.range, n)))
Hex(x) => done(number_doc(path, x, n => self.hex_literal_text(e.range, n)))
Floatable(x) => {
if self.float_lexeme_text(e.range, x) is Some(text) {
return done(@pretty.text(text))
}
guard !x.is_nan() && !x.is_inf() else {
raise PrintError(path~, problem=NonFiniteFloat(x))
}
if is_negative(x) {
done(@pretty.text("-" + float_text(-x)))
} else {
done(@pretty.text(float_text(x)))
}
}
Negation(x) =>
(
[Expr(path.child("negation", 0), level + 1, x, Negated)],
docs => @pretty.text("-") + docs[0],
)
Literal(s) => done(self.string_doc(e.range, s))
CharLiteral(c) => done(@pretty.text(char_literal(c)))
TupledExpression(items) => {
guard items.length() >= 2 else {
raise PrintError(path~, problem=ShortTuple)
}
let after : Array[Array[@ast.Node[String]]] = Array::makei(
items.length(),
_ => [],
)
(
self.sequence_items(path, "tupled", level, items, after=Some(after)),
docs => {
let (items, leads) = item_docs(docs)
// elm-format Box.hs Tuple (`formatC2Eol`): the comments before a
// `,` go under the item before it.
for i, cs in after {
if !cs.is_empty() {
items[i] = items[i] + @pretty.hardline() + comment_box(cs)
}
}
// elm-format Parse/Helpers.hs surround (`parens`): a line break
// anywhere inside the parentheses.
// A comment before the `)` goes in the column of the items.
sequence(
self.split_within(e.range),
"(",
")",
items,
leads~,
inner=@pretty.nest(2, self.inner_close(e.range)),
)
},
)
}
ParenthesizedExpression(x) =>
(
[Expr(path.child("parenthesized", 0), level + 1, x, AnyExpr)],
// elm-format Box.hs `Parens` has no source rule: the parentheses
// break only when the expression in them is multi-line.
// A comment before the `)` goes in the column after the `(`.
docs => parens(self.join(), docs[0], inner=self.inner_close(e.range)),
)
LetExpression(b) => {
guard !b.declarations.is_empty() else {
raise PrintError(path~, problem=EmptyLet)
}
// Two docs per declaration: its comments, then the declaration. The
// comments after a declaration go before the next one (elm-format).
let parts = []
for i, dn in b.declarations {
let p = path.child("declarations", i)
let previous = if i > 0 {
Some(b.declarations[i - 1].range)
} else {
None
}
parts.push(Leaf(() => self.moved_then_leading(previous, dn.range)))
match dn.value {
LetFunction(f) =>
parts.push(self.function_plan(p, level + 1, f, in_let=true))
LetDestructuring(pattern, value) =>
parts.push(
Plan(
[
Leaf(() => {
let moved = self.after_token(pattern.range)
// Elm reads a destructuring pattern as a term: `(Wrap w) =`.
self.pattern_doc(
p.child("pattern", 0),
level + 1,
pattern,
PatternArg,
) +
self.separator(pattern.range, " =", moved)
}),
Expr(p.child("expression", 0), level + 1, value, AnyExpr),
],
docs => {
@pretty.nest(4, docs[0]) +
@pretty.nest(4, @pretty.hardline() + docs[1])
},
),
)
}
}
// The comments before `in`: after the last declaration, and before
// the body when the source has them before `in`.
let last_decl = b.declarations[b.declarations.length() - 1].range
let body = b.expression
parts.push(
Leaf(() => {
let cs = self.take(Trailing, last_decl)
if self.token_before(body.range.start) is Some(in_at) {
cs.append(
self.take_if(Leading, body.range, c => {
at_or_before(c.range.end, in_at)
}),
)
}
if cs.is_empty() {
@pretty.empty()
} else {
@pretty.hardline() + comment_block(cs, before=true)
}
}),
)
parts.push(Expr(path.child("expression", 0), level + 1, body, AnyExpr))
(
parts,
docs => {
let count = b.declarations.length()
let mut decls = @pretty.empty()
for i in 0.. {
guard !b.cases.is_empty() else {
raise PrintError(path~, problem=EmptyCase)
}
// Three docs per branch: its comments, the pattern with ` ->`, the
// body. The comments after a branch go before the next one
// (elm-format).
let parts = [
Expr(path.child("expression", 0), level + 1, b.expression, AnyExpr),
]
for i, c in b.cases {
let p = path.child("cases", i)
let previous = if i > 0 {
Some(case_range(b.cases[i - 1]))
} else {
None
}
parts.push(Leaf(() => self.moved_then_leading(previous, case_range(c))))
parts.push(
Leaf(() => {
let moved = self.after_token(c.pattern.range)
@pretty.nest(
4,
self.pattern_doc(
p.child("pattern", 0),
level + 1,
c.pattern,
AnyPattern,
) +
self.separator(c.pattern.range, " ->", moved),
)
}),
)
parts.push(
Expr(p.child("expression", 0), level + 1, c.expression, AnyExpr),
)
}
// elm-format Parse/Expression.hs caseExpr (`trackNewline` around the
// padded subject): a line break after `case`, in the subject or
// before `of`. The range ends at the first pattern, after `of`.
let subject_split = self.split_from({
start: b.expression.range.start,
end: b.cases[0].pattern.range.start,
})
(
parts,
docs => {
let head = @pretty.group(
@pretty.text("case") +
@pretty.tab(4, split_line(subject_split) + docs[0]) +
split_line(subject_split) +
@pretty.text("of"),
)
let mut branches = @pretty.empty()
for i = 1; i + 2 < docs.length(); i = i + 3 {
branches = branches +
(if i == 1 {
@pretty.hardline()
} else {
@pretty.hardline() + @pretty.hardline()
}) +
docs[i] +
docs[i + 1] +
@pretty.nest(4, @pretty.hardline() + docs[i + 2])
}
@pretty.align(head + @pretty.tab(4, branches))
},
)
}
LambdaExpression(l) => {
guard !l.args.is_empty() else {
raise PrintError(path~, problem=NoLambdaArguments)
}
let parts = []
let last = l.args.length() - 1
for i, a in l.args {
let p = path.child("patterns", i)
parts.push(
Leaf(() => {
let moved = if i == last { self.after_token(a.range) } else { [] }
@pretty.nest(
4,
self.pattern_doc(p, level + 1, a, PatternArg) +
self.separator(
a.range,
if i == last {
" ->"
} else {
" "
},
moved,
),
)
}),
)
}
parts.push(
Expr(path.child("expression", 0), level + 1, l.expression, AnyExpr),
)
let split = self.split_within(e.range)
(
parts,
docs => {
let last = docs.length() - 1
let mut head = @pretty.text("\\")
for i in 0..
(
self.setter_items(path, "record", level, setters),
docs => {
let (fields, leads) = self.setter_docs(setters, docs, 0)
let inner = if setters.is_empty() {
self.inner_alone(e.range, brace=true)
} else {
self.inner(e.range, last=last_range(setters))
}
// elm-format Parse/Expression.hs recordTerm (`checkMultiline`):
// a line break anywhere inside the braces.
sequence(self.split_within(e.range), "{", "}", fields, leads~, inner~)
},
)
ListExpr(items) =>
(
self.sequence_items(path, "list", level, items),
docs => {
let (items, leads) = item_docs(docs)
// elm-format Parse/Expression.hs listTerm (`checkMultiline`): a
// line break anywhere inside the brackets.
sequence(
self.split_within(e.range),
"[",
"]",
items,
leads~,
inner=self.inner_for(e.range, items.length()),
)
},
)
RecordAccess(_, _) => {
let names : Array[(@syntax.NodePath, @ast.Node[String])] = []
let mut node = e
let mut p = path
while node.value is RecordAccess(target, name) {
names.push((p.child("name", 0), name))
p = p.child("expression", 0)
node = target
}
let parts = [Expr(p, level + 1, node, Target)]
for i = names.length() - 1; i >= 0; i = i - 1 {
let (np, name) = names[i]
parts.push(
Leaf(() => {
self.with_comments(name.range, self.lower_name(np, name.value))
}),
)
}
(
parts,
docs => {
let mut d = docs[0]
for i in 1.. {
// elm-syntax keeps the dot: ".name".
let name = StringBuilder()
for i, c in s {
if i > 0 {
name.write_char(c)
}
}
guard s.has_prefix(".") && self.is_lower(name.to_string()) else {
raise PrintError(path~, problem=InvalidName(Lower, s))
}
done(@pretty.text(s))
}
RecordUpdateExpression(name, setters) => {
guard !setters.is_empty() else {
raise PrintError(path~, problem=NoFields)
}
let parts = [
Leaf(() => {
self.with_comments(
name.range,
self.lower_name(path.child("name", 0), name.value),
)
}),
]
parts.append(self.setter_items(path, "updates", level, setters))
(
parts,
docs => {
let (fields, leads) = self.setter_docs(setters, docs, 1)
// elm-format Parse/Expression.hs recordTerm (`checkMultiline`).
extension(
self.split_within(e.range),
docs[0],
fields,
leads~,
inner=self.inner(e.range, last=last_range(setters)),
)
},
)
}
GLSLExpression(s) => {
guard !s.contains("|]") else {
raise PrintError(path~, problem=InvalidGlsl)
}
done(@pretty.verbatim("[glsl|" + s + "|]"))
}
}
}
///|
/// Four items for each item of a list or tuple, at
/// `path.child(field_name, i)`: the comments that the source has after the
/// next `,` among the item's trailing comments (taken before the item, so
/// that they go after that `,`; the doc is empty), the comments before its
/// `,` (see `Ctx::before_item`), those moved comments after its `,`, then
/// the item.
fn Ctx::sequence_items(
self : Ctx,
path : @syntax.NodePath,
field_name : String,
level : Int,
items : ArrayView[@ast.Node[@ast.Expression]],
after? : Array[Array[@ast.Node[String]]]? = None,
) -> Array[Work] {
let moved : Array[Array[@ast.Node[String]]] = Array::makei(
items.length() + 1,
_ => [],
)
let parts = []
for i, x in items {
let next = items.get(i + 1).map(n => n.range.start)
parts.push(
Leaf(() => {
if next is Some(start) {
let comma = self.token_before(start)
moved[i + 1] = self.take_if(Trailing, x.range, c => {
!ends_before(c, comma)
})
}
@pretty.empty()
}),
)
parts.push(
Leaf(() => {
match (i, after) {
(0, _) => @pretty.empty()
// A tuple: the comments before the `,` go to `after` of the item
// before it.
(_, Some(a)) => {
let comma = self.token_before(x.range.start)
a[i - 1] = self.take_if(Leading, x.range, c => ends_before(c, comma))
@pretty.empty()
}
(_, None) => self.before_item(None, x.range)
}
}),
)
parts.push(Leaf(() => comments_before(moved[i], x.range.start)))
parts.push(Expr(path.child(field_name, i), level + 1, x, AnyExpr))
}
parts
}
///|
/// The items and the comments before their `,`, from the docs of
/// `Ctx::sequence_items`.
fn item_docs(
docs : Array[@pretty.Doc],
) -> (Array[@pretty.Doc], Array[@pretty.Doc]) {
let items = []
let leads = []
for i = 0; i + 3 < docs.length(); i = i + 4 {
leads.push(docs[i + 1])
items.push(docs[i] + docs[i + 2] + docs[i + 3])
}
(items, leads)
}
///|
/// `if c then a else if d then b else e`, the `else if` chain flattened
/// without recursion: a clause and a branch per `if`, then the last `else`.
/// Each `else if` after the first `if` has one more doc first: the comments
/// before its `if`.
fn Ctx::if_plan(
self : Ctx,
path : @syntax.NodePath,
level : Int,
c : @ast.Node[@ast.Expression],
t : @ast.Node[@ast.Expression],
f : @ast.Node[@ast.Expression],
) -> (Array[Work], (Array[@pretty.Doc]) -> @pretty.Doc) raise PrintError {
let parts = []
// Per clause: the comments before `then` and those before `else`.
let before_then : Array[Array[@ast.Node[String]]] = []
let before_else : Array[Array[@ast.Node[String]]] = []
let mut p = path
let mut cond = c
let mut then_ = t
let mut else_ = f
let mut more = true
while more {
// elm-format Parse/Expression.hs ifClause: the comments before `then`
// follow the condition, and the comments before `else` follow the
// branch. Take them before the condition and the branch take theirs.
let (cr, tr, er) = (cond.range, then_.range, else_.range)
parts.push(
Leaf(() => {
let then_at = self.token_before(tr.start)
let cs = self.take(Trailing, cr)
cs.append(self.take_if(Leading, tr, c => ends_before(c, then_at)))
before_then.push(cs)
let else_at = self.token_back_to(er.start, "else", 1)
let es = self.take(Trailing, tr)
es.append(self.take_if(Leading, er, c => ends_before(c, else_at)))
before_else.push(es)
@pretty.empty()
}),
)
parts.push(Expr(p.child("clause", 0), level + 1, cond, AnyExpr))
parts.push(Expr(p.child("then", 0), level + 1, then_, AnyExpr))
match else_.value {
IfBlock(c2, t2, f2) => {
p = p.child("else", 0)
check_level(p, level)
let r = else_.range
parts.push(Leaf(() => self.leading(r)))
cond = c2
then_ = t2
else_ = f2
}
_ => {
parts.push(Expr(p.child("else", 0), level + 1, else_, AnyExpr))
more = false
}
}
}
(
parts,
docs => {
// Per clause: an empty doc (the comments are in `before_then` and
// `before_else`), the condition and the branch; before a nested
// `if`, its leading comments; at the end, the last branch.
let last = docs.length() - 1
let mut d = @pretty.empty()
let mut i = 0
let mut k = 0
while i + 1 < last {
if i > 0 {
d = d + @pretty.text(" ") + docs[i]
i = i + 1
}
let (cond_doc, _) = commented([], docs[i + 1], before_then[k])
let after_branch = before_else[k]
d = d +
@pretty.group(
@pretty.text("if") +
@pretty.tab(4, @pretty.line() + cond_doc) +
@pretty.line() +
@pretty.text("then"),
) +
@pretty.tab(
4,
@pretty.hardline() +
docs[i + 2] +
(if after_branch.is_empty() {
@pretty.empty()
} else {
@pretty.hardline() + comment_box(after_branch)
}),
) +
@pretty.hardline() +
@pretty.hardline() +
@pretty.text("else")
i = i + 3
k = k + 1
}
@pretty.align(d + @pretty.tab(4, @pretty.hardline() + docs[last]))
},
)
}
///|
/// The range of a case branch, as the syntax tree gives it: from the
/// pattern to the end of the body.
fn case_range(c : @ast.Case) -> @ast.Range {
{ start: c.pattern.range.start, end: c.expression.range.end, }
}