Skip to content

Commit

Permalink
Modification: add SpaceOrNoSplit, handle State etc
Browse files Browse the repository at this point in the history
This new modification would serve as a space unless we encounter, on the
same line, a terminating token, in which case it will become a NoSplit.

This will allow us to re-implement conversion of braces to parens for
one-line apply without having to worry about possibly non-idempotent
additional formatting rules.
  • Loading branch information
kitbellew committed Oct 20, 2024
1 parent bf3b152 commit b625e06
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,30 @@ class FormatWriter(formatOps: FormatOps) {
}

@tailrec
def iter(cur: State, lineId: Int, gapId: Int): Unit = {
def iter(
cur: State,
lineId: Int,
gapId: Int,
prevEol: FormatToken,
): Unit = {
val prev = cur.prev
val idx = prev.depth
val ft = toks(idx)
val mod = cur.mod
val nextEol = if (mod.isNL) ft else prevEol
cur.updatedWithSubsequentEOL(nextEol)
if (useCRLF == 0 && ft.hasCRLF) useCRLF = 1
if (idx == 0) // done
result(idx) = FormatLocation(ft, cur, initStyle, lineId, gapId)
else {
val nl = cur.mod.newlines
val nl = mod.newlines
val nLineId = lineId + nl + ft.meta.left.countNL
val nGapId = gapId + (if (nl > 1) 1 else 0)
result(idx) = FormatLocation(ft, cur, styleMap.at(ft), nLineId, nGapId)
iter(prev, nLineId, nGapId)
iter(prev, nLineId, nGapId, nextEol)
}
}
if (depth != 0) iter(state, 0, 0)
if (depth != 0) iter(state, 0, 0, toks.last)

if (depth == toks.length) { // format completed
val initStyle = styleMap.init
Expand Down Expand Up @@ -163,47 +171,30 @@ class FormatWriter(formatOps: FormatOps) {
val state = bloc.state
val inParentheses = style.spaces.inParentheses
// remove space before "{"
val prevBegState =
if (0 == beg || (state.prev.mod ne Space)) state.prev
else {
val prevloc = locations(beg - 1)
val prevState = state.prev
.copy(split = state.prev.split.withMod(NoSplit))
locations(beg - 1) = prevloc
.copy(shift = prevloc.shift - 1, state = prevState)
prevState
}
if (0 != beg && state.prev.mod.length != 0) {
val prevState = state.prev
prevState.split = prevState.split.withMod(NoSplit)
locations(beg - 1).shift -= 1
}

// update "{"
locations(beg) =
if (inParentheses || (state.mod ne Space)) bloc
.copy(replace = "(", state = state.copy(prev = prevBegState))
else {
// remove space after "{"
val split = state.split.withMod(NoSplit)
bloc.copy(
replace = "(",
shift = bloc.shift - 1,
state = state.copy(prev = prevBegState, split = split),
)
}
bloc.replace = "("
if (!inParentheses && state.mod.length != 0) {
// remove space after "{"
state.split = state.split.withMod(NoSplit)
bloc.shift -= 1
}

val prevEndLoc = locations(idx - 1)
val prevEndState = prevEndLoc.state
val newPrevEndState =
if (inParentheses || (prevEndState.mod ne Space)) prevEndState
else {
// remove space before "}"
val split = prevEndState.split.withMod(NoSplit)
val newState = prevEndState.copy(split = split)
locations(idx - 1) = prevEndLoc
.copy(shift = prevEndLoc.shift - 1, state = newState)
newState
}
if (!inParentheses && prevEndState.mod.length != 0) {
// remove space before "}"
prevEndState.split = prevEndState.split.withMod(NoSplit)
prevEndLoc.shift -= 1
}

// update "}"
locations(idx) = loc
.copy(replace = ")", state = loc.state.copy(prev = newPrevEndState))
loc.replace = ")"
}
case _ =>
}
Expand Down Expand Up @@ -1378,8 +1369,7 @@ class FormatWriter(formatOps: FormatOps) {
val floc = locations(idx)
if (indent.notExpiredBy(floc.formatToken)) {
val state = floc.state
if (state.split.isNL) locations(idx) = floc
.copy(state = state.copy(indentation = state.indentation + offset))
if (state.split.isNL) floc.state.indentation += offset
val nextIdx = idx + 1
if (nextIdx < locations.length) updateLocation(nextIdx)
}
Expand Down Expand Up @@ -1617,12 +1607,12 @@ object FormatWriter {
style: ScalafmtConfig,
leftLineId: Int, // counts back from the end of the file
leftBlankGapId: Int, // accumulates number of blank gaps, also from end
shift: Int = 0,
var shift: Int = 0,
optionalBraces: Map[Int, Tree] = Map.empty,
// if indent is empty, indicates open; otherwise, whether to tuck
missingBracesOpenOrTuck: Boolean = false,
missingBracesIndent: Set[Int] = Set.empty,
replace: String = null,
var replace: String = null,
) {
def hasBreakAfter: Boolean = state.split.isNL
def hasBreakBefore: Boolean =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,14 @@ case class ModExt(mod: Modification, indents: Seq[Indent] = Seq.empty) {
* for {
* a <- new Integer {
* value = 1
* }
* }
* x <- if (variable) doSomething
* else doAnything
* else doAnything
* }
* ```
*/
def getActualIndents(offset: Int): Seq[ActualIndent] = {
val adjustedOffset = if (mod eq Space) offset + 1 else offset
indents.flatMap(_.withStateOffset(adjustedOffset))
}
def getActualIndents(offset: Int): Seq[ActualIndent] = indents
.flatMap(_.withStateOffset(offset + mod.length))

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ object Space extends Modification {
else Newline2x(FormatToken.hasBlankLine(nl))
def orNL(ft: FormatToken): Modification = orNL(ft.newlinesBetween)
}

case class SpaceOrNoSplit(policy: Policy.End.WithPos) extends Modification {
override val newlines: Int = 0
override val length: Int = 1

override def toString: String = "SPorNS"
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ import scala.annotation.tailrec

/** A partial formatting solution up to splits.length number of tokens.
*/
final case class State(
cost: Int,
policy: PolicySummary,
split: Split,
depth: Int,
prev: State,
indentation: Int,
final class State(
val cost: Int,
val policy: PolicySummary,
var split: Split,
val depth: Int,
val prev: State,
var indentation: Int,
pushes: Seq[ActualIndent],
column: Int,
appliedPenalty: Int, // penalty applied from overflow
delayedPenalty: Int, // apply if positive, ignore otherwise
lineId: Int,
var column: Int,
var pendingSpaces: List[Policy.End.WithPos],
val appliedPenalty: Int, // penalty applied from overflow
val delayedPenalty: Int, // apply if positive, ignore otherwise
val lineId: Int,
) {

override def toString = s"State($cost, $depth)"
Expand All @@ -34,6 +35,17 @@ final case class State(
@inline
def mod: Modification = modExt.mod

def updatedWithSubsequentEOL(ft: FormatToken): Unit = {
column += pendingSpaces.count(_.notExpiredBy(ft))
val mod = split.mod
val updatedMod = mod match {
case SpaceOrNoSplit(p) => if (p.notExpiredBy(ft)) Space else NoSplit
case m => m
}
pendingSpaces = Nil
split = split.withMod(updatedMod)
}

def possiblyBetter(other: State): Boolean = this.cost < other.cost ||
this.indentation < other.indentation

Expand Down Expand Up @@ -77,13 +89,23 @@ final case class State(
}
}

val (prevPendingSpaces, confirmedSpaces) =
if (nextSplit.isNL) Nil -> pendingSpaces.count(_.notExpiredBy(tok))
else pendingSpaces.filter(_.notExpiredBy(tok)) -> 0
val (nextPendingSpaces, modLength) = nextSplit.mod match {
case SpaceOrNoSplit(p) if p.notExpiredBy(tok) =>
(p :: prevPendingSpaces, 0)
case m => (prevPendingSpaces, m.length)
}

// Some tokens contain newline, like multiline strings/comments.
val startColumn = nextSplit.mod match {
case m: NewlineT => if (m.noIndent) 0 else nextIndent
case m => if (m.isNL) nextIndent else column + m.length
case m => if (m.isNL) nextIndent else column + modLength
}
val (columnOnCurrentLine, nextStateColumn) = State
val (pendingColumnOnCurrentLine, nextPendingColumn) = State
.getColumns(tok, nextIndent, startColumn)
val columnOnCurrentLine = pendingColumnOnCurrentLine + confirmedSpaces

val nextTok = tokens.next(tok)
val nextPolicy: PolicySummary = policy.combine(nextSplit, nextTok)
Expand Down Expand Up @@ -115,7 +137,7 @@ final case class State(

val splitWithPenalty = nextSplit.withPenalty(penalty)

State(
new State(
cost = cost + splitWithPenalty.costWithPenalty,
// TODO(olafur) expire policy, see #18.
policy = nextPolicy,
Expand All @@ -124,7 +146,8 @@ final case class State(
prev = this,
indentation = nextIndent,
pushes = nextIndents,
column = nextStateColumn,
column = nextPendingColumn,
pendingSpaces = nextPendingSpaces,
appliedPenalty = appliedPenalty + penalty,
delayedPenalty = nextDelayedPenalty,
lineId = lineId + (if (nextSplit.isNL) 1 else 0),
Expand Down Expand Up @@ -312,8 +335,20 @@ final case class State(

object State {

val start =
State(0, PolicySummary.empty, null, 0, null, 0, Seq.empty, 0, 0, 0, 0)
val start: State = new State(
cost = 0,
policy = PolicySummary.empty,
split = null,
depth = 0,
prev = null,
indentation = 0,
pushes = Nil,
column = 0,
pendingSpaces = Nil,
appliedPenalty = 0,
delayedPenalty = 0,
lineId = 0,
)

// this is not best state, it's higher priority for search
object Ordering {
Expand Down

0 comments on commit b625e06

Please sign in to comment.