Skip to content

Commit 7d93964

Browse files
redid the macro implementation
1 parent cee22f3 commit 7d93964

File tree

2 files changed

+59
-214
lines changed

2 files changed

+59
-214
lines changed

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 47 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -13,238 +13,72 @@ import SwiftDiagnostics
1313

1414
struct HTMLElement : ExpressionMacro {
1515
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> ExprSyntax {
16-
let arguments:LabeledExprListSyntax = node.arguments
16+
return "\(raw: parse_element(node: node, context: context))"
17+
}
18+
}
19+
20+
extension HTMLElement {
21+
static func parse_element(node: some FreestandingMacroExpansionSyntax, context: some MacroExpansionContext) -> String {
1722
let macroName:String = node.macroName.text
18-
var type:HTMLElementType = HTMLElementType(rawValue: macroName) ?? HTMLElementType.a
19-
var attributes:[String] = [], extra_attributes:[String] = []
20-
var items:[Item] = []
23+
let type:HTMLElementType = HTMLElementType(rawValue: macroName) ?? HTMLElementType.a
24+
let data:ElementData = parse_arguments(arguments: node.arguments)
25+
var string:String = (type == .html ? "<!DOCTYPE html>" : "") + "<" + type.rawValue + data.attributes + ">" + data.innerHTML
26+
if !type.isVoid {
27+
string += "</" + type.rawValue + ">"
28+
}
29+
return "\"" + string + "\""
30+
}
31+
static func parse_arguments(arguments: LabeledExprListSyntax) -> ElementData {
32+
var attributes:[String] = []
33+
var innerHTML:[String] = []
2134
for element in arguments.children(viewMode: .all) {
2235
if let child:LabeledExprSyntax = element.as(LabeledExprSyntax.self) {
2336
if let key:String = child.label?.text {
2437
switch key {
25-
case "elementType":
26-
type = HTMLElementType(rawValue: child.expression.as(MemberAccessExprSyntax.self)!.declName.baseName.text) ?? HTMLElementType.div
27-
break
2838
case "attributes":
2939
attributes = parse_attributes(child: child)
3040
break
31-
case "accept",
32-
"alt",
33-
"as",
34-
"attributionsrc",
35-
"autocomplete",
36-
"autoplay",
37-
"blocking",
38-
"browsingtopics",
39-
"capture",
40-
"charset",
41-
"checked",
42-
"cite",
43-
"cols",
44-
"coords",
45-
"content",
46-
"controls",
47-
"controlslist",
48-
"credentialless",
49-
"crossorigin",
50-
"csp",
51-
"datetime",
52-
"decoding",
53-
"default",
54-
"defer",
55-
"dirname",
56-
"disabled",
57-
"disablepictureinpicture",
58-
"disableremoteplayback",
59-
"download",
60-
"elementtiming",
61-
"fetchpriority",
62-
"for",
63-
"form",
64-
"formaction",
65-
"formenctype",
66-
"formmethod",
67-
"formnovalidate",
68-
"formtarget",
69-
"height",
70-
"high",
71-
"href",
72-
"hreflang",
73-
"httpEquiv",
74-
"imagesizes",
75-
"integrity",
76-
"ismap",
77-
"kind",
78-
"label",
79-
"list",
80-
"loading",
81-
"loop",
82-
"low",
83-
"max",
84-
"maxlength",
85-
"media",
86-
"min",
87-
"minlength",
88-
"multiple",
89-
"muted",
90-
"name",
91-
"nomodule",
92-
"onafterprint",
93-
"onbeforeprint",
94-
"onbeforeunload",
95-
"onblur",
96-
"onerror",
97-
"onfocus",
98-
"onhashchange",
99-
"onlanguagechange",
100-
"onload",
101-
"onmessage",
102-
"onoffline",
103-
"ononline",
104-
"onpopstate",
105-
"onresize",
106-
"onstorange",
107-
"onunload",
108-
"open",
109-
"optimum",
110-
"pattern",
111-
"ping",
112-
"placeholder",
113-
"playsinline",
114-
"popovertarget",
115-
"popovertargetaction",
116-
"poster",
117-
"preload",
118-
"readonly",
119-
"referrerpolicy",
120-
"rel",
121-
"reversed",
122-
"required",
123-
"rows",
124-
"sandbox",
125-
"scope",
126-
"selected",
127-
"shadowrootclonable",
128-
"shadowrootdelegatesfocus",
129-
"shadowrootmode",
130-
"shadowrootserializable",
131-
"shape",
132-
"size",
133-
"sizes",
134-
"src",
135-
"srcdoc",
136-
"srclang",
137-
"srcset",
138-
"start",
139-
"step",
140-
"target",
141-
"template",
142-
"type",
143-
"usemap",
144-
"value",
145-
"width",
146-
"wrap",
147-
"xmlns":
148-
let string:String = parse_extra_attribute(child: child)
149-
if !string.isEmpty {
150-
extra_attributes.append(string)
151-
}
152-
break
15341
case "innerHTML":
154-
let array:ArrayElementListSyntax = child.expression.as(ArrayExprSyntax.self)!.elements
155-
parse_body(node: node, context: context, macroName: macroName, array: array, items: &items)
42+
innerHTML = parse_inner_html(child: child)
15643
break
157-
default:
44+
default: // extra attribute
45+
if let string:String = parse_extra_attribute(child: child) {
46+
attributes.append(string)
47+
}
15848
break
15949
}
160-
} else if let array:ArrayElementListSyntax = child.expression.as(ArrayExprSyntax.self)?.elements {
161-
parse_body(node: node, context: context, macroName: macroName, array: array, items: &items)
16250
}
16351
}
16452
}
165-
let attributes_string:String = attributes.isEmpty ? "" : " " + attributes.joined(separator: " ")
166-
let extra_attributes_string:String = extra_attributes.isEmpty ? "" : " " + extra_attributes.joined(separator: " ")
167-
var string:String = (type == .html ? "\"<!DOCTYPE html>" : "\"") + "<" + type.rawValue + attributes_string + extra_attributes_string + ">"
168-
if !items.isEmpty {
169-
string += "\""
170-
}
171-
for item in items {
172-
string += "+" + item.string
173-
}
174-
if !type.isVoid {
175-
string += (items.isEmpty ? "" : " + \"") + "</" + type.rawValue + ">\""
176-
}
177-
return "\(raw: string)"
53+
return ElementData(attributes: attributes, innerHTML: innerHTML)
17854
}
179-
private static func parse_body(node: some FreestandingMacroExpansionSyntax, context: some MacroExpansionContext, macroName: String, array: ArrayElementListSyntax, items: inout [Item]) {
180-
for element in array {
181-
if let test:MacroExpansionExprSyntax = element.expression.as(MacroExpansionExprSyntax.self) {
182-
let key:String
183-
let recursive:Bool = macroName == test.macroName.text
184-
if recursive { // swift macro limitation
185-
key = "#HTMLElement(elementType: ." + test.macroName.text + ","
186-
} else {
187-
if test.macroName.text == "HTMLElement" {
188-
items.append(Item(isMacro: true, value: "\(test)"))
189-
break
190-
} else {
191-
key = "#" + test.macroName.text + "("
192-
}
193-
}
194-
var values:[String] = []
195-
for child in test.arguments.children(viewMode: .all) {
196-
let label:LabeledExprSyntax = child.as(LabeledExprSyntax.self)!, key:String = label.label!.text + ": ", expression:ExprSyntax = label.expression
197-
var string:String = ""
198-
if let array:ArrayElementListSyntax = expression.as(ArrayExprSyntax.self)?.elements {
199-
var body_items:[Item] = []
200-
parse_body(node: node, context: context, macroName: macroName, array: array, items: &body_items)
201-
string = key + "[" + body_items.map({ $0.string }).joined(separator: ", ") + "]"
202-
} else {
203-
string = key + "\(expression)"
204-
}
205-
if !string.isEmpty {
206-
values.append(string)
207-
}
208-
}
209-
if recursive && values.isEmpty {
210-
values.append("innerHTML: []")
211-
}
212-
items.append(Item(isMacro: true, value: key + values.joined(separator: ",") + ")"))
213-
} else if let text:String = element.expression.as(StringLiteralExprSyntax.self)?.string {
214-
items.append(Item(isMacro: false, value: text))
215-
} else {
216-
var string:String = "\(element)"
217-
while string[string.startIndex].isWhitespace {
218-
string.removeFirst()
219-
}
220-
while string[string.index(before: string.endIndex)].isWhitespace || string[string.index(before: string.endIndex)] == "," {
221-
string.removeLast()
222-
}
223-
var jugs:[Character] = [], offset:Int = 1
224-
while string[string.index(string.startIndex, offsetBy: offset)] != "(" {
225-
jugs.append(string[string.index(string.startIndex, offsetBy: offset)])
226-
offset += 1
227-
}
228-
if string[string.index(string.startIndex, offsetBy: offset + 1)] == "." {
229-
let ez:String = String(jugs)
230-
string.insert(contentsOf: "HTMLElementAttribute." + ez[ez.startIndex].uppercased() + ez[ez.index(after: ez.startIndex)...], at: string.index(string.startIndex, offsetBy: offset+1))
231-
}
232-
if !string.starts(with: "HTMLElementAttribute") {
233-
string.insert(contentsOf: "HTMLElementAttribute", at: string.startIndex)
55+
static func parse_element_macro(expression: MacroExpansionExprSyntax) -> String {
56+
guard let elementType:HTMLElementType = HTMLElementType(rawValue: expression.macroName.text) else { return "\(expression)" }
57+
let data:ElementData = parse_arguments(arguments: expression.arguments)
58+
return "<" + elementType.rawValue + data.attributes + ">" + data.innerHTML + (elementType.isVoid ? "" : "</" + elementType.rawValue + ">")
59+
}
60+
static func parse_inner_html(child: LabeledExprSyntax) -> [String] {
61+
var innerHTML:[String] = []
62+
if let array:ArrayElementListSyntax = child.expression.as(ArrayExprSyntax.self)?.elements {
63+
for yoink in array {
64+
if let macro:MacroExpansionExprSyntax = yoink.expression.as(MacroExpansionExprSyntax.self) {
65+
innerHTML.append(parse_element_macro(expression: macro))
66+
} else if let string:String = yoink.expression.as(StringLiteralExprSyntax.self)?.string {
67+
innerHTML.append(string)
23468
}
235-
items.append(Item(isMacro: true, value: string))
23669
}
23770
}
71+
return innerHTML
23872
}
23973
}
24074

241-
extension HTMLElement {
242-
struct Item {
243-
let string:String
75+
struct ElementData {
76+
let attributes:String
77+
let innerHTML:String
24478

245-
init(isMacro: Bool, value: String) {
246-
string = isMacro ? value : "\"" + value + "\""
247-
}
79+
init(attributes: [String], innerHTML: [String]) {
80+
self.attributes = attributes.isEmpty ? "" : " " + attributes.joined(separator: " ")
81+
self.innerHTML = innerHTML.joined()
24882
}
24983
}
25084

@@ -332,14 +166,14 @@ private extension HTMLElement {
332166
return function.arguments.first!.expression.as(ArrayExprSyntax.self)!.elements.map({ $0.expression.as(StringLiteralExprSyntax.self)!.string })
333167
}
334168

335-
static func parse_extra_attribute(child: LabeledExprSyntax) -> String {
169+
static func parse_extra_attribute(child: LabeledExprSyntax) -> String? {
336170
let key:String = child.label!.text
337171
func yup(_ value: String) -> String {
338172
return key + "=\\\"" + value + "\\\""
339173
}
340174
let expression:ExprSyntax = child.expression
341175
if let boolean:String = expression.as(BooleanLiteralExprSyntax.self)?.literal.text {
342-
return boolean.elementsEqual("true") ? key : ""
176+
return boolean.elementsEqual("true") ? key : nil
343177
}
344178
if let string:String = expression.as(StringLiteralExprSyntax.self)?.string {
345179
return yup(string)
@@ -348,15 +182,15 @@ private extension HTMLElement {
348182
return yup("\\(HTMLElementAttribute." + key[key.startIndex].uppercased() + key[key.index(after: key.startIndex)...] + "." + member + ")")
349183
}
350184
if let _:NilLiteralExprSyntax = expression.as(NilLiteralExprSyntax.self) {
351-
return ""
185+
return nil
352186
}
353187
if let integer:String = expression.as(IntegerLiteralExprSyntax.self)?.literal.text {
354188
return yup(integer)
355189
}
356190
if let float:String = expression.as(FloatLiteralExprSyntax.self)?.literal.text {
357191
return yup(float)
358192
}
359-
return ""
193+
return nil
360194
}
361195
}
362196

Tests/HTMLKitTests/HTMLKitTests.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,18 @@ extension HTMLKitTests {
2121
}
2222

2323
extension HTMLKitTests {
24-
func test_example1() {
24+
func test_recursive() {
25+
let string:String = #div(innerHTML: [
26+
#div(),
27+
#div(innerHTML: [#div(), #div(), #div()]),
28+
#div()
29+
])
30+
XCTAssertEqual(string, "<div><div></div><div><div></div><div></div><div></div></div><div></div></div>")
31+
}
32+
}
33+
34+
extension HTMLKitTests {
35+
func testExample1() {
2536
let test:String = #html(innerHTML: [
2637
#body(innerHTML: [
2738
#div(

0 commit comments

Comments
 (0)