@@ -598,3 +598,105 @@ public extension RawTriviaPiece {
598
598
}
599
599
}
600
600
}
601
+
602
+ // MARK: - StringLiteralExprSyntax/contentValue
603
+
604
+ extension StringLiteralExprSyntax {
605
+
606
+ /// Returns the string value of the literal as the parsed program would see
607
+ /// it: Multiline strings are combined into one string, escape sequences are
608
+ /// resolved.
609
+ ///
610
+ /// Returns nil if the literal contains interpolation segments.
611
+
612
+ public var contentValue : String ? {
613
+ // Currently the implementation relies on properly parsed literals.
614
+ guard !hasError else { return nil }
615
+
616
+ // Concatenate unescaped string literal segments. For example multiline
617
+ // strings consist of multiple segments. Abort on finding string
618
+ // interpolation.
619
+ var result = " "
620
+ for segment in segments {
621
+ switch segment {
622
+ case . stringSegment( let stringSegmentSyntax) :
623
+ stringSegmentSyntax. appendUnescapedLiteralValue (
624
+ stringLiteralKind: stringLiteralKind,
625
+ delimiterLength: delimiterLength,
626
+ to: & result
627
+ )
628
+ case . expressionSegment:
629
+ // Bail out if there are any interpolation segments.
630
+ return nil
631
+ }
632
+ }
633
+
634
+ return result
635
+ }
636
+
637
+ fileprivate var stringLiteralKind : StringLiteralKind {
638
+ switch openQuote. tokenKind {
639
+ case . stringQuote:
640
+ return . singleLine
641
+ case . multilineStringQuote:
642
+ return . multiLine
643
+ case . singleQuote:
644
+ return . singleQuote
645
+ default :
646
+ fatalError ( )
647
+ }
648
+ }
649
+
650
+ fileprivate var delimiterLength : Int {
651
+ openDelimiter? . text. count ?? 0
652
+ }
653
+ }
654
+
655
+ extension StringSegmentSyntax {
656
+ fileprivate func appendUnescapedLiteralValue(
657
+ stringLiteralKind: StringLiteralKind ,
658
+ delimiterLength: Int ,
659
+ to output: inout String
660
+ ) {
661
+ precondition ( !hasError, " impl. relies on properly parsed literals " )
662
+
663
+ var text = content. text
664
+
665
+ // Fast path for literals without escape sequences.
666
+ // TODO: Check if this isn't actually slower.
667
+ guard text. contains ( " \\ " ) else {
668
+ output. append ( text)
669
+ return
670
+ }
671
+
672
+ text. withUTF8 { buffer in
673
+ var cursor = Lexer . Cursor ( input: buffer, previous: 0 )
674
+
675
+ // Put the cursor in the string literal lexing state. This is just
676
+ // defensive as it's currently not used by `lexCharacterInStringLiteral`.
677
+ let state = Lexer . Cursor. State. inStringLiteral ( kind: stringLiteralKind, delimiterLength: delimiterLength)
678
+ let transition = Lexer . StateTransition. push ( newState: state)
679
+ cursor. perform ( stateTransition: transition, stateAllocator: BumpPtrAllocator ( slabSize: 256 ) )
680
+
681
+ while true {
682
+ let lex = cursor. lexCharacterInStringLiteral (
683
+ stringLiteralKind: stringLiteralKind,
684
+ delimiterLength: delimiterLength
685
+ )
686
+
687
+ switch lex {
688
+ case . success( let scalar) :
689
+ output. append ( Character ( scalar) )
690
+ case . validatedEscapeSequence( let character) :
691
+ output. append ( character)
692
+ case . endOfString, . error:
693
+ // We get an error at the end of the string because
694
+ // `lexCharacterInStringLiteral` expects the closing quote.
695
+ // We can assume the error just signals the end of string
696
+ // because we made sure the token lexed fine before.
697
+ return
698
+ }
699
+ }
700
+ }
701
+ }
702
+ }
0 commit comments