Skip to content

Commit da4add8

Browse files
authored
Merge pull request #1508 from NikolaiRuhe/string-literal-value
Implement StringLiteralExprSyntax/contentValue.
2 parents e321b69 + 19c7184 commit da4add8

File tree

2 files changed

+423
-0
lines changed

2 files changed

+423
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@_spi(RawSyntax) import SwiftSyntax
14+
15+
extension StringLiteralExprSyntax {
16+
17+
/// Returns the string value of the literal as the parsed program would see
18+
/// it: Multiline strings are combined into one string, escape sequences are
19+
/// resolved.
20+
///
21+
/// Returns nil if the literal contains interpolation segments.
22+
public var representedLiteralValue: String? {
23+
// Currently the implementation relies on properly parsed literals.
24+
guard !hasError else { return nil }
25+
guard let stringLiteralKind else { return nil }
26+
27+
// Concatenate unescaped string literal segments. For example multiline
28+
// strings consist of multiple segments. Abort on finding string
29+
// interpolation.
30+
var result = ""
31+
for segment in segments {
32+
switch segment {
33+
case .stringSegment(let stringSegmentSyntax):
34+
stringSegmentSyntax.appendUnescapedLiteralValue(
35+
stringLiteralKind: stringLiteralKind,
36+
delimiterLength: delimiterLength,
37+
to: &result
38+
)
39+
case .expressionSegment:
40+
// Bail out if there are any interpolation segments.
41+
return nil
42+
}
43+
}
44+
45+
return result
46+
}
47+
48+
fileprivate var stringLiteralKind: StringLiteralKind? {
49+
switch openQuote.tokenKind {
50+
case .stringQuote:
51+
return .singleLine
52+
case .multilineStringQuote:
53+
return .multiLine
54+
case .singleQuote:
55+
return .singleQuote
56+
default:
57+
return nil
58+
}
59+
}
60+
61+
fileprivate var delimiterLength: Int {
62+
openDelimiter?.text.count ?? 0
63+
}
64+
}
65+
66+
extension StringSegmentSyntax {
67+
fileprivate func appendUnescapedLiteralValue(
68+
stringLiteralKind: StringLiteralKind,
69+
delimiterLength: Int,
70+
to output: inout String
71+
) {
72+
precondition(!hasError, "appendUnescapedLiteralValue relies on properly parsed literals")
73+
74+
var text = content.text
75+
text.withUTF8 { buffer in
76+
var cursor = Lexer.Cursor(input: buffer, previous: 0)
77+
78+
// Put the cursor in the string literal lexing state. This is just
79+
// defensive as it's currently not used by `lexCharacterInStringLiteral`.
80+
let state = Lexer.Cursor.State.inStringLiteral(kind: stringLiteralKind, delimiterLength: delimiterLength)
81+
let transition = Lexer.StateTransition.push(newState: state)
82+
cursor.perform(stateTransition: transition, stateAllocator: BumpPtrAllocator(slabSize: 256))
83+
84+
while true {
85+
let lex = cursor.lexCharacterInStringLiteral(
86+
stringLiteralKind: stringLiteralKind,
87+
delimiterLength: delimiterLength
88+
)
89+
90+
switch lex {
91+
case .success(let scalar):
92+
output.append(Character(scalar))
93+
case .validatedEscapeSequence(let character):
94+
output.append(character)
95+
case .endOfString, .error:
96+
// We get an error at the end of the string because
97+
// `lexCharacterInStringLiteral` expects the closing quote.
98+
// We can assume the error just signals the end of string
99+
// because we made sure the token lexed fine before.
100+
return
101+
}
102+
}
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)