1+
2+ import HTMLKitUtilities
3+ import SwiftDiagnostics
4+ import SwiftSyntax
5+
6+ extension HTMLKitUtilities {
7+ public static func expandHTMLMacro( context: HTMLExpansionContext ) throws -> ExprSyntax {
8+ var context = context
9+ return try expandHTMLMacro ( context: & context)
10+ }
11+ public static func expandHTMLMacro( context: inout HTMLExpansionContext ) throws -> ExprSyntax {
12+ let ( string, encoding) = expandMacro ( context: & context)
13+ let encodingResult = encodingResult ( context: context, node: context. expansion, string: string, for: encoding)
14+ let expandedResult = representationResult ( encoding: encoding, encodedResult: encodingResult, representation: context. representation)
15+ return " \( raw: expandedResult) "
16+ }
17+
18+ static func expandMacro( context: inout HTMLExpansionContext ) -> ( String , HTMLEncoding ) {
19+ let data = HTMLKitUtilities . parseArguments ( context: & context)
20+ var string = " "
21+ for v in data. innerHTML {
22+ string += String ( describing: v)
23+ }
24+ string. replace ( HTMLKitUtilities . lineFeedPlaceholder, with: " \\ n " )
25+ return ( string, data. encoding)
26+ }
27+ }
28+
29+ // MARK: Encoding result
30+ extension HTMLKitUtilities {
31+ static func encodingResult(
32+ context: HTMLExpansionContext ,
33+ node: MacroExpansionExprSyntax ,
34+ string: String ,
35+ for encoding: HTMLEncoding
36+ ) -> String {
37+ switch encoding {
38+ case . utf8Bytes:
39+ guard hasNoInterpolation ( context, node, string) else { return " " }
40+ return bytes ( [ UInt8] ( string. utf8) )
41+ case . utf16Bytes:
42+ guard hasNoInterpolation ( context, node, string) else { return " " }
43+ return bytes ( [ UInt16] ( string. utf16) )
44+ case . utf8CString:
45+ guard hasNoInterpolation ( context, node, string) else { return " " }
46+ return " \( string. utf8CString) "
47+
48+ case . foundationData:
49+ guard hasNoInterpolation ( context, node, string) else { return " " }
50+ return " Data( \( bytes ( [ UInt8] ( string. utf8) ) ) ) "
51+
52+ case . byteBuffer:
53+ guard hasNoInterpolation ( context, node, string) else { return " " }
54+ return " ByteBuffer(bytes: \( bytes ( [ UInt8] ( string. utf8) ) ) ) "
55+
56+ case . string:
57+ return " \" \( string) \" "
58+ case . custom( let encoded, _) :
59+ return encoded. replacingOccurrences ( of: " $0 " , with: string)
60+ }
61+ }
62+ private static func bytes< T: FixedWidthInteger > ( _ bytes: [ T ] ) -> String {
63+ var string = " [ "
64+ for b in bytes {
65+ string += " \( b) , "
66+ }
67+ string. removeLast ( )
68+ return string. isEmpty ? " [] " : string + " ] "
69+ }
70+ private static func hasNoInterpolation( _ context: HTMLExpansionContext , _ node: MacroExpansionExprSyntax , _ string: String ) -> Bool {
71+ guard string. firstRange ( of: try ! Regex ( " \\ ((.*) \\ ) " ) ) == nil else {
72+ if !context. ignoresCompilerWarnings {
73+ context. context. diagnose ( Diagnostic ( node: node, message: DiagnosticMsg ( id: " interpolationNotAllowedForDataType " , message: " String Interpolation is not allowed for this data type. Runtime values get converted to raw text, which is not the intended result. " ) ) )
74+ }
75+ return false
76+ }
77+ return true
78+ }
79+ }
80+
81+ // MARK: Representation results
82+ extension HTMLKitUtilities {
83+ static func representationResult(
84+ encoding: HTMLEncoding ,
85+ encodedResult: String ,
86+ representation: HTMLResultRepresentation
87+ ) -> String {
88+ switch representation {
89+ case . literal:
90+ break
91+ case . literalOptimized:
92+ if encoding == . string {
93+ // TODO: implement
94+ } else {
95+ // TODO: show compiler diagnostic
96+ }
97+ case . chunked( let optimized, let chunkSize) :
98+ return " [ " + chunks( encoding: encoding, encodedResult: encodedResult, async : false , optimized: optimized, chunkSize: chunkSize) . joined ( separator: " , " ) + " ] "
99+ #if compiler(>=6.2)
100+ case . chunkedInline( let optimized, let chunkSize) :
101+ let typeAnnotation : String = " String " // TODO: fix
102+ let chunks = chunks ( encoding: encoding, encodedResult: encodedResult, async : false , optimized: optimized, chunkSize: chunkSize) . joined ( separator: " , " )
103+ return " InlineArray< \( chunks. count) , \( typeAnnotation) >([ \( chunks) ]) "
104+ #endif
105+ case . streamed( let optimized, let chunkSize) :
106+ return streamedRepresentation ( encoding: encoding, encodedResult: encodedResult, async : false , optimized: optimized, chunkSize: chunkSize, suspendDuration: nil )
107+ case . streamedAsync( let optimized, let chunkSize, let suspendDuration) :
108+ return streamedRepresentation ( encoding: encoding, encodedResult: encodedResult, async : true , optimized: optimized, chunkSize: chunkSize, suspendDuration: suspendDuration)
109+ default :
110+ break
111+ }
112+ return encodedResult
113+ }
114+
115+ static func chunks(
116+ encoding: HTMLEncoding ,
117+ encodedResult: String ,
118+ async : Bool ,
119+ optimized: Bool ,
120+ chunkSize: Int ,
121+ ) -> [ String ] {
122+ var chunks = [ String] ( )
123+ let delimiter : ( Character ) -> String ? = encoding == . string ? { $0 != " \" " ? " \" " : nil } : { _ in nil }
124+ let count = encodedResult. count
125+ var i = 0
126+ while i < count {
127+ var endingIndex = i + chunkSize
128+ if i == 0 && encoding == . string {
129+ endingIndex += 1
130+ }
131+ let endIndex = encodedResult. index ( encodedResult. startIndex, offsetBy: endingIndex, limitedBy: encodedResult. endIndex) ?? encodedResult. endIndex
132+ let slice = encodedResult [ encodedResult. index ( encodedResult. startIndex, offsetBy: i) ..< endIndex]
133+ i += chunkSize + ( i == 0 && encoding == . string ? 1 : 0 )
134+ if slice. isEmpty || encoding == . string && slice. count == 1 && slice. first == " \" " {
135+ continue
136+ }
137+ var string = " "
138+ if let f = slice. first, let d = delimiter ( f) {
139+ string += d
140+ }
141+ string += slice
142+ if let l = slice. last, let d = delimiter ( l) {
143+ string += d
144+ }
145+ chunks. append ( string)
146+ }
147+ return chunks
148+ }
149+
150+ static func streamedRepresentation(
151+ encoding: HTMLEncoding ,
152+ encodedResult: String ,
153+ async : Bool ,
154+ optimized: Bool ,
155+ chunkSize: Int ,
156+ suspendDuration: Duration ?
157+ ) -> String {
158+ var string = " AsyncStream { continuation in \n "
159+ if async {
160+ string += " Task { \n "
161+ }
162+ let chunks = chunks ( encoding: encoding, encodedResult: encodedResult, async : async , optimized: optimized, chunkSize: chunkSize)
163+ for chunk in chunks {
164+ string += " continuation.yield( " + chunk + " ) \n "
165+ if let suspendDuration {
166+ string += " try await Task.sleep(for: \( suspendDuration) ) \n "
167+ }
168+ }
169+ string += " continuation.finish() \n } "
170+ if async {
171+ string += " \n } "
172+ }
173+ return string
174+ }
175+ }
0 commit comments