Skip to content

Commit ac9d9fb

Browse files
now uses separate macros for different data types
- after a lot of researching and testing, this is unfortunately the best way to support multiple returned value types for the compiled output - The proper implementation, if it ever becomes supported, would be to check the generic parameter type returned during macro expansion - I am limiting it to just the `html` element for now due to current limitations (`swift-syntax` limitation & unhandled interpolation when converting the data to bytes)
1 parent 2770ac2 commit ac9d9fb

File tree

4 files changed

+82
-89
lines changed

4 files changed

+82
-89
lines changed

Sources/HTMLKit/HTMLKit.swift

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,53 @@
77

88
import HTMLKitUtilities
99

10+
#if canImport(Foundation)
11+
import struct Foundation.Data
12+
#endif
13+
14+
15+
#if canImport(NIOCore)
16+
import struct Foundation.ByteBuffer
17+
#endif
18+
1019
// MARK: StaticString equality
1120
public extension StaticString {
1221
static func == (left: Self, right: Self) -> Bool { left.description == right.description }
1322
static func != (left: Self, right: Self) -> Bool { left.description != right.description }
1423
}
15-
// MARK: StaticString == String equality
16-
public extension String {
24+
// MARK: StaticString and StringProtocol equality
25+
public extension StringProtocol {
1726
static func == (left: Self, right: StaticString) -> Bool { left == right.description }
1827
static func == (left: StaticString, right: Self) -> Bool { left.description == right }
1928
}
2029

2130
@freestanding(expression)
2231
public macro escapeHTML<T: ExpressibleByStringLiteral>(_ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
2332

24-
// MARK: Elements
33+
// MARK: HTML Representation
34+
@freestanding(expression)
35+
public macro html<T: ExpressibleByStringLiteral>(attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
36+
37+
@freestanding(expression)
38+
public macro htmlUTF8Bytes<T: ExpressibleByStringLiteral>(attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> [UInt8] = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
39+
2540
@freestanding(expression)
26-
public macro html<V>(representation: HTMLDataRepresentation = .string, attributes: [HTMLElementAttribute] = [], xmlns: (any ExpressibleByStringLiteral)? = nil, _ innerHTML: any ExpressibleByStringLiteral...) -> V = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
41+
public macro htmlUTF16Bytes<T: ExpressibleByStringLiteral>(attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> [UInt16] = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
42+
43+
@freestanding(expression)
44+
public macro htmlUTF8CString<T: ExpressibleByStringLiteral>(attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> ContiguousArray<CChar> = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
45+
46+
#if canImport(Foundation)
47+
@freestanding(expression)
48+
public macro htmlData<T: ExpressibleByStringLiteral>(attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> Data = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
49+
#endif
50+
51+
#if canImport(NIOCore)
52+
@freestanding(expression)
53+
public macro htmlByteBuffer<T: ExpressibleByStringLiteral>(attributes: [HTMLElementAttribute] = [], xmlns: T? = nil, _ innerHTML: T...) -> ByteBuffer = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")
54+
#endif
55+
56+
// MARK: Elements
2757

2858
@freestanding(expression)
2959
public macro custom<T: ExpressibleByStringLiteral>(tag: String, isVoid: Bool, attributes: [HTMLElementAttribute] = [], _ innerHTML: T...) -> T = #externalMacro(module: "HTMLKitMacros", type: "HTMLElement")

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,62 +18,29 @@ import struct Foundation.Data
1818
import struct NIOCore.ByteBuffer
1919
#endif
2020

21-
struct HTMLElement : ExpressionMacro {
21+
enum HTMLElement : ExpressionMacro {
2222
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
23-
var representation:HTMLDataRepresentation
24-
if let declared:String = node.arguments.children(viewMode: .all).first(where: { $0.labeled?.label?.text == "representation" })?.labeled?.expression.as(MemberAccessExprSyntax.self)?.declName.baseName.text {
25-
//context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "test", message: context.lexicalContext.last!.debugDescription)))
26-
representation = HTMLDataRepresentation(rawValue: declared) ?? .string
27-
} else {
28-
representation = .string
29-
//context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "test", message: context.lexicalContext.last!.debugDescription)))
30-
if let returnClause:ReturnClauseSyntax = context.lexicalContext.first?.as(FunctionDeclSyntax.self)?.signature.returnClause {
31-
if let array_type:String = returnClause.type.as(ArrayTypeSyntax.self)?.element.as(IdentifierTypeSyntax.self)?.name.text {
32-
switch array_type {
33-
case "UInt8":
34-
representation = .uint8Array
35-
break
36-
case "UInt16":
37-
representation = .uint16Array
38-
break
39-
default:
40-
break
41-
}
42-
} else if let id:String = returnClause.type.as(IdentifierTypeSyntax.self)?.name.text {
43-
switch id {
44-
case "Data":
45-
#if canImport(Foundation)
46-
representation = .data
47-
#endif
48-
break
49-
case "ByteBuffer":
50-
#if canImport(NIOCore)
51-
representation = .byteBuffer
52-
#endif
53-
break
54-
default:
55-
break
56-
}
57-
}
58-
} else {
59-
//context.diagnose(Diagnostic(node: node, message: DiagnosticMsg(id: "test", message: context.lexicalContext.first!.debugDescription)))
60-
}
61-
}
6223
let string:String = parse_macro(context: context, expression: node.as(MacroExpansionExprSyntax.self)!)
6324
// TODO: check for interpolation
6425
func bytes<T: FixedWidthInteger>(_ bytes: [T]) -> String {
6526
return "[" + bytes.map({ "\($0)" }).joined(separator: ",") + "]"
6627
}
67-
switch representation {
68-
case .uint8Array: return "\(raw: bytes([UInt8](string.utf8)))"
69-
case .uint16Array: return "\(raw: bytes([UInt16](string.utf16)))"
28+
switch HTMLElementType(rawValue: node.macroName.text) {
29+
case .htmlUTF8Bytes:
30+
return "\(raw: bytes([UInt8](string.utf8)))"
31+
case .htmlUTF16Bytes:
32+
return "\(raw: bytes([UInt16](string.utf16)))"
33+
case .htmlUTF8CString:
34+
return "\(raw: string.utf8CString)"
7035

7136
#if canImport(Foundation)
72-
case .data: return "Data(\(raw: bytes([UInt8](string.utf8))))"
37+
case .htmlData:
38+
return "Data(\(raw: bytes([UInt8](string.utf8))))"
7339
#endif
7440

7541
#if canImport(NIOCore)
76-
case .byteBuffer: return "ByteBuffer(string: \"\(raw: string)\")"
42+
case .htmlByteBuffer:
43+
return "ByteBuffer(bytes: \(raw: bytes([UInt8](string.utf8))))"
7744
#endif
7845

7946
default: return "\"\(raw: string)\""
@@ -99,12 +66,12 @@ private extension HTMLElement {
9966
children = childs.dropFirst() // tag
10067
children.removeFirst() // isVoid
10168
} else {
102-
tag = elementType.rawValue
69+
tag = elementType.rawValue.starts(with: "html") ? "html" : elementType.rawValue
10370
isVoid = elementType.isVoid
10471
children = childs.prefix(childs.count)
10572
}
10673
let data:ElementData = parse_arguments(context: context, elementType: elementType, children: children)
107-
var string:String = (elementType == .html ? "<!DOCTYPE html>" : "") + "<" + tag + data.attributes + ">" + data.innerHTML
74+
var string:String = (tag == "html" ? "<!DOCTYPE html>" : "") + "<" + tag + data.attributes + ">" + data.innerHTML
10875
if !isVoid {
10976
string += "</" + tag + ">"
11077
}
@@ -115,7 +82,7 @@ private extension HTMLElement {
11582
for element in children {
11683
if let child:LabeledExprSyntax = element.labeled {
11784
if var key:String = child.label?.text {
118-
if key == "representation" { // we don't care
85+
if key == "dataType" { // HTMLDataRepresentation; we don't care
11986
} else if key == "attributes" {
12087
attributes.append(contentsOf: parse_global_attributes(context: context, elementType: elementType, array: child.expression.array!))
12188
} else {
@@ -387,8 +354,17 @@ enum LiteralReturnType {
387354
// MARK: HTMLElementType
388355
enum HTMLElementType : String, CaseIterable {
389356
case escapeHTML
390-
case html
391357
case custom
358+
359+
case html, htmlUTF8Bytes, htmlUTF16Bytes, htmlUTF8CString
360+
361+
#if canImport(Foundation)
362+
case htmlData
363+
#endif
364+
365+
#if canImport(NIOCore)
366+
case htmlByteBuffer
367+
#endif
392368

393369
case a
394370
case abbr

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,6 @@
55
// Created by Evan Anderson on 9/19/24.
66
//
77

8-
// MARK: Data Representation
9-
/// Determines what value type the HTML is compiled to.
10-
public enum HTMLDataRepresentation : String {
11-
// native Swift
12-
/// the raw compiled `StaticString`/`String`
13-
case string
14-
/// converts the compiled output to `[UInt8]` using utf8 encoding
15-
case uint8Array
16-
/// converts the compiled output to `[UInt16]` using utf16 encoding
17-
case uint16Array
18-
19-
// Foundation
20-
#if canImport(Foundation)
21-
/// converts the compiled output to `Data` using utf8 encoding
22-
case data
23-
#endif
24-
25-
// NIOCore
26-
#if canImport(NIOCore)
27-
/// converts the compiled output to `ByteBuffer` using utf8 encoding
28-
case byteBuffer
29-
#endif
30-
}
31-
328
// MARK: Escape HTML
339
public extension String {
3410
/// Escapes all occurrences of source-breaking HTML characters

Tests/HTMLKitTests/HTMLKitTests.swift

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import Testing
99
import HTMLKit
1010

1111
#if canImport(Foundation)
12-
import Foundation
12+
import struct Foundation.Data
13+
#endif
14+
15+
#if canImport(NIOCore)
16+
import struct NIOCore.ByteBuffer
1317
#endif
1418

1519
struct HTMLKitTests {
@@ -44,15 +48,16 @@ struct HTMLKitTests {
4448

4549
extension HTMLKitTests {
4650
func representations() {
47-
let _:StaticString = #html(representation: .string)
48-
let _:String = #html(representation: .string)
49-
let _:[UInt8] = #html(representation: .uint8Array)
50-
let _:[UInt16] = #html(representation: .uint16Array)
51+
let _:StaticString = #html()
52+
let _:String = #html()
53+
let _:[UInt8] = #htmlUTF8Bytes("")
54+
let _:[UInt16] = #htmlUTF16Bytes("")
55+
let _:ContiguousArray<CChar> = #htmlUTF8CString("")
5156
#if canImport(Foundation)
52-
let _:Data = #html(representation: .data)
57+
let _:Data = #htmlData("")
5358
#endif
5459
#if canImport(NIOCore)
55-
let _:ByteBuffer = #html(representation: .byteBuffer)
60+
let _:ByteBuffer = #htmlByteBuffer("")
5661
#endif
5762
}
5863
func representation1() -> StaticString {
@@ -62,16 +67,22 @@ extension HTMLKitTests {
6267
#html()
6368
}
6469
func representation3() -> [UInt8] {
65-
#html()
70+
#htmlUTF8Bytes("")
71+
}
72+
func representation4() -> [UInt16] {
73+
#htmlUTF16Bytes("")
74+
}
75+
func representation5() -> ContiguousArray<CChar> {
76+
#htmlUTF8CString("")
6677
}
6778
#if canImport(Foundation)
68-
func representation4() -> Data {
69-
#html()
79+
func representation5() -> Data {
80+
#htmlData("")
7081
}
7182
#endif
7283
#if canImport(NIOCore)
73-
func representation5() -> ByteBuffer {
74-
#html()
84+
func representation6() -> ByteBuffer {
85+
#htmlByteBuffer("")
7586
}
7687
#endif
7788
}

0 commit comments

Comments
 (0)