Skip to content

Commit fc54a05

Browse files
committed
Implement StringLiteralExprSyntax/contentValue.
1 parent ac4f8f3 commit fc54a05

File tree

2 files changed

+420
-0
lines changed

2 files changed

+420
-0
lines changed

Sources/SwiftParser/StringLiterals.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,105 @@ public extension RawTriviaPiece {
598598
}
599599
}
600600
}
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

Comments
 (0)