@@ -1368,26 +1368,43 @@ object Parsers {
13681368      //  APIs behaves predictably in the presence of empty leading/trailing lines
13691369      if  (closingIndent ==  " " 
13701370      else  {
1371-         if  (closingIndent.contains('\t ' ) &&  closingIndent.contains(' ' )) {
1371+         //  Check for mixed tabs and spaces in closing indent
1372+ 
1373+         val  hasTabs  =  closingIndent.contains('\t ' )
1374+         val  hasSpaces  =  closingIndent.contains(' ' )
1375+         if  (hasTabs &&  hasSpaces) {
13721376          syntaxError(
13731377            em " dedented string literal cannot mix tabs and spaces in indentation " ,
13741378            offset
13751379          )
13761380          return  str
13771381        }
13781382
1383+         //  Split into lines
13791384        val  linesAndWithSeps  =  (str.linesIterator.zip(str.linesWithSeparators)).toSeq
1385+ 
13801386        var  lineOffset  =  offset
13811387
13821388        def  dedentLine (line : String , lineWithSep : String ) =  {
13831389          val  result  = 
13841390            if  (line.startsWith(closingIndent)) line.substring(closingIndent.length)
13851391            else  if  (line.trim.isEmpty) " " //  Empty or whitespace-only lines
13861392            else  {
1387-               syntaxError(
1388-                 em " line in dedented string literal must be indented at least as much as the closing delimiter with an identical prefix " ,
1389-                 lineOffset
1390-               )
1393+               //  Check if this line has mixed tabs/spaces that don't match closing indent
1394+               val  lineIndent  =  line.takeWhile(_.isWhitespace)
1395+               val  lineHasTabs  =  lineIndent.contains('\t ' )
1396+               val  lineHasSpaces  =  lineIndent.contains(' ' )
1397+               if  ((hasTabs &&  lineHasSpaces &&  ! lineHasTabs) ||  (hasSpaces &&  lineHasTabs &&  ! lineHasSpaces)) {
1398+                 syntaxError(
1399+                   em " dedented string literal cannot mix tabs and spaces in indentation " ,
1400+                   offset
1401+                 )
1402+               } else  {
1403+                 syntaxError(
1404+                   em " line in dedented string literal must be indented at least as much as the closing delimiter " ,
1405+                   lineOffset
1406+                 )
1407+               }
13911408              line
13921409            }
13931410          lineOffset +=  lineWithSep.length //  Make sure to include any \n, \r, \r\n, or \n\r
@@ -1534,15 +1551,14 @@ object Parsers {
15341551        in.charOffset +  1  <  in.buf.length && 
15351552        in.buf(in.charOffset) ==  '"'  && 
15361553        in.buf(in.charOffset +  1 ) ==  '"' 
1537- 
15381554      val  isDedented  = 
15391555        in.charOffset +  2  <  in.buf.length && 
15401556        in.buf(in.charOffset -  1 ) ==  '\' '  && 
15411557        in.buf(in.charOffset) ==  '\' '  && 
15421558        in.buf(in.charOffset +  1 ) ==  '\' ' 
1543- 
15441559      in.nextToken()
15451560
1561+       //  Collect all string parts and their offsets
15461562      val  stringParts  =  new  ListBuffer [(String , Offset )]
15471563      val  interpolatedExprs  =  new  ListBuffer [Tree ]
15481564
@@ -1553,6 +1569,7 @@ object Parsers {
15531569        offsetCorrection =  0 
15541570        in.nextToken()
15551571
1572+         //  Collect the interpolated expression
15561573        interpolatedExprs +=  atSpan(in.offset) {
15571574          if  (in.token ==  IDENTIFIER )
15581575            termIdent()
@@ -1574,6 +1591,7 @@ object Parsers {
15741591        }
15751592      }
15761593
1594+       //  Get the final STRINGLIT
15771595      val  finalLiteral  =  if  (in.token ==  STRINGLIT ) {
15781596        val  s  =  in.strVal
15791597        val  off  =  in.offset +  offsetCorrection
@@ -1593,6 +1611,7 @@ object Parsers {
15931611          }
15941612        }
15951613
1614+       //  Build the segments with dedented strings
15961615      for  ((str, expr) <-  dedentedParts.zip(interpolatedExprs)) {
15971616        val  (dedentedStr, offset) =  str
15981617        segmentBuf +=  Thicket (
@@ -1601,7 +1620,8 @@ object Parsers {
16011620        )
16021621      }
16031622
1604-       if  (finalLiteral) { //  Add the final literal if present
1623+       //  Add the final literal if present
1624+       if  (finalLiteral) {
16051625        val  (dedentedStr, offset) =  dedentedParts.last
16061626        segmentBuf +=  atSpan(offset, offset, offset +  dedentedStr.length) { Literal (Constant (dedentedStr)) }
16071627      }
0 commit comments