@@ -170,8 +170,6 @@ object Scanners {
170170 /** A switch whether operators at the start of lines can be infix operators */
171171 private [Scanners ] var allowLeadingInfixOperators = true
172172
173- var skipping = false
174-
175173 var debugTokenStream = false
176174 val showLookAheadOnDebug = false
177175
@@ -274,7 +272,35 @@ object Scanners {
274272 private val prev = newTokenData
275273
276274 /** The current region. This is initially an Indented region with zero indentation width. */
277- var currentRegion : Region = Indented (IndentWidth .Zero , Set (), EMPTY , null )
275+ var currentRegion : Region = Indented (IndentWidth .Zero , EMPTY , null )
276+
277+ // Error recovery ------------------------------------------------------------
278+
279+ private def lastKnownIndentWidth : IndentWidth =
280+ def recur (r : Region ): IndentWidth =
281+ if r.knownWidth == null then recur(r.enclosing) else r.knownWidth
282+ recur(currentRegion)
283+
284+ private var skipping = false
285+
286+ /** Skip on error to next safe point.
287+ */
288+ def skip (): Unit =
289+ val lastRegion = currentRegion
290+ skipping = true
291+ def atStop =
292+ token == EOF
293+ || (currentRegion eq lastRegion)
294+ && (skipStopTokens.contains(token)
295+ || token == OUTDENT && indentWidth(offset) < lastKnownIndentWidth)
296+ // stop at OUTDENT if the new indentwidth is smaller than the indent width of
297+ // currentRegion. This corrects for the problem that sometimes we don't see an INDENT
298+ // when skipping and therefore might erroneously end up syncing on a nested OUTDENT.
299+ // println(s"\nSTART SKIP AT ${sourcePos().line + 1}, $this in $currentRegion")
300+ while ! atStop do
301+ nextToken()
302+ // println(s"\nSTOP SKIP AT ${sourcePos().line + 1}, $this in $currentRegion")
303+ skipping = false
278304
279305// Get next token ------------------------------------------------------------
280306
@@ -305,27 +331,34 @@ object Scanners {
305331 nextToken()
306332 result
307333
334+ private inline def dropUntil (inline matches : Region => Boolean ): Unit =
335+ while
336+ ! currentRegion.isOutermost
337+ && {
338+ val isLast = matches(currentRegion)
339+ currentRegion = currentRegion.enclosing
340+ ! isLast
341+ }
342+ do ()
343+
308344 def adjustSepRegions (lastToken : Token ): Unit = (lastToken : @ switch) match {
309345 case LPAREN | LBRACKET =>
310346 currentRegion = InParens (lastToken, currentRegion)
311347 case LBRACE =>
312348 currentRegion = InBraces (currentRegion)
313349 case RBRACE =>
314- def dropBraces (): Unit = currentRegion match {
315- case r : InBraces =>
316- currentRegion = r.enclosing
317- case _ =>
318- if (! currentRegion.isOutermost) {
319- currentRegion = currentRegion.enclosing
320- dropBraces()
321- }
322- }
323- dropBraces()
350+ dropUntil(_.isInstanceOf [InBraces ])
324351 case RPAREN | RBRACKET =>
325352 currentRegion match {
326353 case InParens (prefix, outer) if prefix + 1 == lastToken => currentRegion = outer
327354 case _ =>
328355 }
356+ case OUTDENT =>
357+ currentRegion match
358+ case r : Indented => currentRegion = r.enclosing
359+ case r =>
360+ if skipping && r.enclosing.isClosedByUndentAt(indentWidth(offset)) then
361+ dropUntil(_.isInstanceOf [Indented ])
329362 case STRINGLIT =>
330363 currentRegion match {
331364 case InString (_, outer) => currentRegion = outer
@@ -413,8 +446,8 @@ object Scanners {
413446 || {
414447 r.outer match
415448 case null => true
416- case Indented (outerWidth, others , _, _) =>
417- outerWidth < nextWidth && ! others .contains(nextWidth)
449+ case ro @ Indented (outerWidth, _, _) =>
450+ outerWidth < nextWidth && ! ro.otherIndentWidths .contains(nextWidth)
418451 case outer =>
419452 outer.indentWidth < nextWidth
420453 }
@@ -520,6 +553,15 @@ object Scanners {
520553 var lastWidth = IndentWidth .Zero
521554 var indentPrefix = EMPTY
522555 val nextWidth = indentWidth(offset)
556+
557+ // If nextWidth is an indentation level not yet seen by enclosing indentation
558+ // region, invoke `handler`.
559+ def handleNewIndentWidth (r : Region , handler : Indented => Unit ): Unit = r match
560+ case r @ Indented (curWidth, prefix, outer)
561+ if curWidth < nextWidth && ! r.otherIndentWidths.contains(nextWidth) && nextWidth != lastWidth =>
562+ handler(r)
563+ case _ =>
564+
523565 currentRegion match
524566 case r : Indented =>
525567 indentIsSignificant = indentSyntax
@@ -548,32 +590,30 @@ object Scanners {
548590 else if ! isLeadingInfixOperator(nextWidth) && ! statCtdTokens.contains(lastToken) && lastToken != INDENT then
549591 currentRegion match
550592 case r : Indented =>
551- currentRegion = r.enclosing
552593 insert(OUTDENT , offset)
553- case r : InBraces if ! closingRegionTokens.contains(token) =>
594+ if next.token != COLON then
595+ handleNewIndentWidth(r.enclosing, ir =>
596+ errorButContinue(
597+ i """ The start of this line does not match any of the previous indentation widths.
598+ |Indentation width of current line : $nextWidth
599+ |This falls between previous widths: ${ir.width} and $lastWidth""" ))
600+ case r : InBraces if ! closingRegionTokens.contains(token) && ! skipping =>
554601 report.warning(" Line is indented too far to the left, or a `}` is missing" , sourcePos())
555- case _ =>
602+ case r =>
603+ if skipping && r.enclosing.isClosedByUndentAt(nextWidth) then
604+ insert(OUTDENT , offset)
556605
557606 else if lastWidth < nextWidth
558607 || lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH ) && token == CASE then
559608 if canStartIndentTokens.contains(lastToken) then
560- currentRegion = Indented (nextWidth, Set (), lastToken, currentRegion)
609+ currentRegion = Indented (nextWidth, lastToken, currentRegion)
561610 insert(INDENT , offset)
562611 else if lastToken == SELFARROW then
563612 currentRegion.knownWidth = nextWidth
564613 else if (lastWidth != nextWidth)
565614 errorButContinue(spaceTabMismatchMsg(lastWidth, nextWidth))
566- currentRegion match
567- case Indented (curWidth, others, prefix, outer)
568- if curWidth < nextWidth && ! others.contains(nextWidth) && nextWidth != lastWidth =>
569- if token == OUTDENT && next.token != COLON then
570- errorButContinue(
571- i """ The start of this line does not match any of the previous indentation widths.
572- |Indentation width of current line : $nextWidth
573- |This falls between previous widths: $curWidth and $lastWidth""" )
574- else
575- currentRegion = Indented (curWidth, others + nextWidth, prefix, outer)
576- case _ =>
615+ if token != OUTDENT || next.token == COLON then
616+ handleNewIndentWidth(currentRegion, _.otherIndentWidths += nextWidth)
577617 end handleNewLine
578618
579619 def spaceTabMismatchMsg (lastWidth : IndentWidth , nextWidth : IndentWidth ) =
@@ -593,7 +633,7 @@ object Scanners {
593633 val nextWidth = indentWidth(next.offset)
594634 val lastWidth = currentRegion.indentWidth
595635 if lastWidth < nextWidth then
596- currentRegion = Indented (nextWidth, Set (), COLONEOL , currentRegion)
636+ currentRegion = Indented (nextWidth, COLONEOL , currentRegion)
597637 offset = next.offset
598638 token = INDENT
599639 end observeIndented
@@ -608,7 +648,6 @@ object Scanners {
608648 && ! (token == CASE && r.prefix == MATCH )
609649 && next.token == EMPTY // can be violated for ill-formed programs, e.g. neg/i12605.sala
610650 =>
611- currentRegion = r.enclosing
612651 insert(OUTDENT , offset)
613652 case _ =>
614653
@@ -623,9 +662,7 @@ object Scanners {
623662 }
624663
625664 def closeIndented () = currentRegion match
626- case r : Indented if ! r.isOutermost =>
627- insert(OUTDENT , offset)
628- currentRegion = r.outer
665+ case r : Indented if ! r.isOutermost => insert(OUTDENT , offset)
629666 case _ =>
630667
631668 /** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT
@@ -656,7 +693,6 @@ object Scanners {
656693 currentRegion match
657694 case r : Indented if isEnclosedInParens(r.outer) =>
658695 insert(OUTDENT , offset)
659- currentRegion = r.outer
660696 case _ =>
661697 lookAhead()
662698 if isAfterLineEnd
@@ -1518,6 +1554,26 @@ object Scanners {
15181554 if enclosing.knownWidth == null then enclosing.useOuterWidth()
15191555 knownWidth = enclosing.knownWidth
15201556
1557+ /** Does `width` represent an undent of an enclosing indentation region?
1558+ * This is the case if there is an indentation region that goes deeper than `width`
1559+ * and that is enclosed in a region that contains `width` as an indentation width.
1560+ */
1561+ def isClosedByUndentAt (width : IndentWidth ): Boolean = this match
1562+ case _ : Indented =>
1563+ ! isOutermost && width <= indentWidth && enclosing.coversIndent(width)
1564+ case _ =>
1565+ enclosing.isClosedByUndentAt(width)
1566+
1567+ /** A region "covers" an indentation with `width` if it has `width` as known
1568+ * indentation width (either as primary, or in case of an Indent region as
1569+ * alternate width).
1570+ */
1571+ protected def coversIndent (w : IndentWidth ): Boolean =
1572+ knownWidth != null && w == indentWidth
1573+
1574+ def toList : List [Region ] =
1575+ this :: (if outer == null then Nil else outer.toList)
1576+
15211577 private def delimiter = this match
15221578 case _ : InString => " }(in string)"
15231579 case InParens (LPAREN , _) => " )"
@@ -1528,11 +1584,10 @@ object Scanners {
15281584
15291585 /** Show open regions as list of lines with decreasing indentations */
15301586 def visualize : String =
1531- indentWidth.toPrefix
1532- + delimiter
1533- + outer.match
1534- case null => " "
1535- case next : Region => " \n " + next.visualize
1587+ toList.map(r => s " ${r.indentWidth.toPrefix}${r.delimiter}" ).mkString(" \n " )
1588+
1589+ override def toString : String =
1590+ toList.map(r => s " ( ${r.indentWidth}, ${r.delimiter}) " ).mkString(" in " )
15361591 end Region
15371592
15381593 case class InString (multiLine : Boolean , outer : Region ) extends Region
@@ -1542,13 +1597,18 @@ object Scanners {
15421597
15431598 /** A class describing an indentation region.
15441599 * @param width The principal indendation width
1545- * @param others Other indendation widths > width of lines in the same region
15461600 * @param prefix The token before the initial <indent> of the region
15471601 */
1548- case class Indented (width : IndentWidth , others : Set [ IndentWidth ], prefix : Token , outer : Region | Null ) extends Region :
1602+ case class Indented (width : IndentWidth , prefix : Token , outer : Region | Null ) extends Region :
15491603 knownWidth = width
15501604
1551- def topLevelRegion (width : IndentWidth ) = Indented (width, Set (), EMPTY , null )
1605+ /** Other indendation widths > width of lines in the same region */
1606+ var otherIndentWidths : Set [IndentWidth ] = Set ()
1607+
1608+ override def coversIndent (w : IndentWidth ) = width == w || otherIndentWidths.contains(w)
1609+ end Indented
1610+
1611+ def topLevelRegion (width : IndentWidth ) = Indented (width, EMPTY , null )
15521612
15531613 enum IndentWidth {
15541614 case Run (ch : Char , n : Int )
0 commit comments