@@ -170,8 +170,6 @@ object Scanners {
170
170
/** A switch whether operators at the start of lines can be infix operators */
171
171
private [Scanners ] var allowLeadingInfixOperators = true
172
172
173
- var skipping = false
174
-
175
173
var debugTokenStream = false
176
174
val showLookAheadOnDebug = false
177
175
@@ -274,7 +272,35 @@ object Scanners {
274
272
private val prev = newTokenData
275
273
276
274
/** 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
278
304
279
305
// Get next token ------------------------------------------------------------
280
306
@@ -305,27 +331,34 @@ object Scanners {
305
331
nextToken()
306
332
result
307
333
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
+
308
344
def adjustSepRegions (lastToken : Token ): Unit = (lastToken : @ switch) match {
309
345
case LPAREN | LBRACKET =>
310
346
currentRegion = InParens (lastToken, currentRegion)
311
347
case LBRACE =>
312
348
currentRegion = InBraces (currentRegion)
313
349
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 ])
324
351
case RPAREN | RBRACKET =>
325
352
currentRegion match {
326
353
case InParens (prefix, outer) if prefix + 1 == lastToken => currentRegion = outer
327
354
case _ =>
328
355
}
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 ])
329
362
case STRINGLIT =>
330
363
currentRegion match {
331
364
case InString (_, outer) => currentRegion = outer
@@ -413,8 +446,8 @@ object Scanners {
413
446
|| {
414
447
r.outer match
415
448
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)
418
451
case outer =>
419
452
outer.indentWidth < nextWidth
420
453
}
@@ -520,6 +553,15 @@ object Scanners {
520
553
var lastWidth = IndentWidth .Zero
521
554
var indentPrefix = EMPTY
522
555
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
+
523
565
currentRegion match
524
566
case r : Indented =>
525
567
indentIsSignificant = indentSyntax
@@ -548,32 +590,30 @@ object Scanners {
548
590
else if ! isLeadingInfixOperator(nextWidth) && ! statCtdTokens.contains(lastToken) && lastToken != INDENT then
549
591
currentRegion match
550
592
case r : Indented =>
551
- currentRegion = r.enclosing
552
593
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 =>
554
601
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)
556
605
557
606
else if lastWidth < nextWidth
558
607
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH ) && token == CASE then
559
608
if canStartIndentTokens.contains(lastToken) then
560
- currentRegion = Indented (nextWidth, Set (), lastToken, currentRegion)
609
+ currentRegion = Indented (nextWidth, lastToken, currentRegion)
561
610
insert(INDENT , offset)
562
611
else if lastToken == SELFARROW then
563
612
currentRegion.knownWidth = nextWidth
564
613
else if (lastWidth != nextWidth)
565
614
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)
577
617
end handleNewLine
578
618
579
619
def spaceTabMismatchMsg (lastWidth : IndentWidth , nextWidth : IndentWidth ) =
@@ -593,7 +633,7 @@ object Scanners {
593
633
val nextWidth = indentWidth(next.offset)
594
634
val lastWidth = currentRegion.indentWidth
595
635
if lastWidth < nextWidth then
596
- currentRegion = Indented (nextWidth, Set (), COLONEOL , currentRegion)
636
+ currentRegion = Indented (nextWidth, COLONEOL , currentRegion)
597
637
offset = next.offset
598
638
token = INDENT
599
639
end observeIndented
@@ -608,7 +648,6 @@ object Scanners {
608
648
&& ! (token == CASE && r.prefix == MATCH )
609
649
&& next.token == EMPTY // can be violated for ill-formed programs, e.g. neg/i12605.sala
610
650
=>
611
- currentRegion = r.enclosing
612
651
insert(OUTDENT , offset)
613
652
case _ =>
614
653
@@ -623,9 +662,7 @@ object Scanners {
623
662
}
624
663
625
664
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)
629
666
case _ =>
630
667
631
668
/** - Join CASE + CLASS => CASECLASS, CASE + OBJECT => CASEOBJECT
@@ -656,7 +693,6 @@ object Scanners {
656
693
currentRegion match
657
694
case r : Indented if isEnclosedInParens(r.outer) =>
658
695
insert(OUTDENT , offset)
659
- currentRegion = r.outer
660
696
case _ =>
661
697
lookAhead()
662
698
if isAfterLineEnd
@@ -1518,6 +1554,26 @@ object Scanners {
1518
1554
if enclosing.knownWidth == null then enclosing.useOuterWidth()
1519
1555
knownWidth = enclosing.knownWidth
1520
1556
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
+
1521
1577
private def delimiter = this match
1522
1578
case _ : InString => " }(in string)"
1523
1579
case InParens (LPAREN , _) => " )"
@@ -1528,11 +1584,10 @@ object Scanners {
1528
1584
1529
1585
/** Show open regions as list of lines with decreasing indentations */
1530
1586
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 " )
1536
1591
end Region
1537
1592
1538
1593
case class InString (multiLine : Boolean , outer : Region ) extends Region
@@ -1542,13 +1597,18 @@ object Scanners {
1542
1597
1543
1598
/** A class describing an indentation region.
1544
1599
* @param width The principal indendation width
1545
- * @param others Other indendation widths > width of lines in the same region
1546
1600
* @param prefix The token before the initial <indent> of the region
1547
1601
*/
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 :
1549
1603
knownWidth = width
1550
1604
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 )
1552
1612
1553
1613
enum IndentWidth {
1554
1614
case Run (ch : Char , n : Int )
0 commit comments