您知道 -0xF_ep-0_2
在 Swift 中是一个有效的浮点字面量吗? 您可能不知道(它等于 -63.5),而且作为宏的作者,您甚至不必关心! 在众多功能中,Macro 工具包可以保护您免受极端情况的影响,以便用户可以以任何奇怪(但正确)的方式使用您的宏。
您不需要深入了解 Swift 的语法即可制作出强大的宏,您只需要一个想法即可。
如果您觉得 Swift Macro 工具包有用,请考虑成为赞助者来支持我。 我将大部分空闲时间用于开源项目,每一笔赞助都帮助我花更多时间为社区制作高质量的工具和库。
亲自看看;
-0xF_ep-0_2
看起来像是您想要为其实现解析的浮点字面量类型吗? 不像; 但您不必这样做。
return literal.value
let string = _syntax.floatingDigits.text
let isHexadecimal: Bool
let stringWithoutPrefix: String
switch string.prefix(2) {
case "0x":
isHexadecimal = true
stringWithoutPrefix = String(string.dropFirst(2))
default:
isHexadecimal = false
stringWithoutPrefix = string
}
let exponentSeparator: Character = isHexadecimal ? "p" : "e"
let parts = stringWithoutPrefix.lowercased().split(separator: exponentSeparator)
guard parts.count <= 2 else {
fatalError("Float literal cannot contain more than one exponent separator")
}
let exponentValue: Int
if parts.count == 2 {
// The exponent part is always decimal
let exponentPart = parts[1]
let exponentPartWithoutUnderscores = exponentPart.replacingOccurrences(of: "_", with: "")
guard
exponentPart.first != "_",
!exponentPart.starts(with: "-_"),
let exponent = Int(exponentPartWithoutUnderscores)
else {
fatalError("Float literal has invalid exponent part: \(string)")
}
exponentValue = exponent
} else {
exponentValue = 0
}
let partsBeforeExponent = parts[0].split(separator: ".")
guard partsBeforeExponent.count <= 2 else {
fatalError("Float literal cannot contain more than one decimal point: \(string)")
}
// The integer part can contain underscores anywhere except for the first character (which must be a digit).
let radix = isHexadecimal ? 16 : 10
let integerPart = partsBeforeExponent[0]
let integerPartWithoutUnderscores = integerPart.replacingOccurrences(of: "_", with: "")
guard
integerPart.first != "_",
let integerPartValue = Int(integerPartWithoutUnderscores, radix: radix).map(Double.init)
else {
fatalError("Float literal has invalid integer part: \(string)")
}
let fractionalPartValue: Double
if partsBeforeExponent.count == 2 {
// The fractional part can contain underscores anywhere except for the first character (which must be a digit).
let fractionalPart = partsBeforeExponent[1]
let fractionalPartWithoutUnderscores = fractionalPart.replacingOccurrences(of: "_", with: "")
guard
fractionalPart.first != "_",
let fractionalPartDigitsValue = Int(fractionalPartWithoutUnderscores, radix: radix)
else {
fatalError("Float literal has invalid fractional part: \(string)")
}
fractionalPartValue = Double(fractionalPartDigitsValue) / pow(Double(radix), Double(fractionalPart.count - 1))
} else {
fractionalPartValue = 0
}
let base: Double = isHexadecimal ? 2 : 10
let multiplier = pow(base, Double(exponentValue))
let sign: Double = _negationSyntax == nil ? 1 : -1
return (integerPartValue + fractionalPartValue) * multiplier * sign
guard case let .simple("Result", (successType, failureType))? = destructure(returnType) else {
throw MacroError("Invalid return type")
}
guard
let simpleReturnType = returnType.as(SimpleTypeIdentifierSyntax.self),
simpleReturnType.name.description == "Result",
let genericArguments = (simpleReturnType.genericArgumentClause?.arguments).map(Array.init),
genericArguments.count == 2
else {
throw MacroError("Invalid return type")
}
let successType = genericArguments[0]
let failureType = genericArguments[1]
Swift 有许多不同的方式来表达单个类型。 举几个这样的例子; () == Void
、Int? == Optional<Int>
和 [Int] == Array<Int>
。 Swift Macro 工具包致力于向您隐藏这些细节,因此您不必处理所有极端情况。
function.returnsVoid
func returnsVoid(_ function: FunctionDeclSyntax) -> Bool {
// Function can either have no return type annotation, `()`, `Void`, or a nested single
// element tuple with a Void-like inner type (e.g. `((((()))))` or `(((((Void)))))`)
func isVoid(_ type: TypeSyntax) -> Bool {
if type.description == "Void" || type.description == "()" {
return true
}
guard let tuple = type.as(TupleTypeSyntax.self) else {
return false
}
if let element = tuple.elements.first, tuple.elements.count == 1 {
let isUnlabeled = element.name == nil && element.secondName == nil
return isUnlabeled && isVoid(TypeSyntax(element.type))
}
return false
}
guard let returnType = function.output?.returnType else {
return false
}
return isVoid(returnType)
}
如果您想以正确的方式获取字符串字面量的值(不带插值),可能会非常繁琐。 您必须自己评估所有转义序列(unicode 序列尤其令人讨厌,例如 \u{2020}
)。 然后,如果用户想要使用原始字符串字面量(例如 #"This isn't a newline \n"#
),事情会变得更加难以处理。 不过不用担心,Swift Macro 工具包已为您考虑周全。
return literal.value
let segments = _syntax.segments.compactMap { (segment) -> String? in
guard case let .stringSegment(segment) = segment else {
return nil
}
return segment.content.text
}
guard segments.count == _syntax.segments.count else {
return nil
}
let map: [Character: Character] = [
"\\": "\\",
"n": "\n",
"r": "\r",
"t": "\t",
"0": "\0",
"\"": "\"",
"'": "'"
]
let hexadecimalCharacters = "0123456789abcdefABCDEF"
// The length of the `\###...` sequence that starts an escape sequence (zero hashes if not a raw string)
let escapeSequenceDelimiterLength = (_syntax.openDelimiter?.text.count ?? 0) + 1
// Evaluate backslash escape sequences within each segment before joining them together
let transformedSegments = segments.map { segment in
var characters: [Character] = []
var inEscapeSequence = false
var iterator = segment.makeIterator()
var escapeSequenceDelimiterPosition = 0 // Tracks the current position in the delimiter if parsing one
while let c = iterator.next() {
if inEscapeSequence {
if let replacement = map[c] {
characters.append(replacement)
} else if c == "u" {
var count = 0
var digits: [Character] = []
var iteratorCopy = iterator
guard iterator.next() == "{" else {
fatalError("Expected '{' in unicode scalar escape sequence")
}
var foundClosingBrace = false
while let c = iterator.next() {
if c == "}" {
foundClosingBrace = true
break
}
guard hexadecimalCharacters.contains(c) else {
iterator = iteratorCopy
break
}
iteratorCopy = iterator
digits.append(c)
count += 1
}
guard foundClosingBrace else {
fatalError("Expected '}' in unicode scalar escape sequence")
}
if !(1...8).contains(count) {
fatalError("Invalid unicode character escape sequence (must be 1 to 8 digits)")
}
guard
let value = UInt32(digits.map(String.init).joined(separator: ""), radix: 16),
let scalar = Unicode.Scalar(value)
else {
fatalError("Invalid unicode scalar hexadecimal value literal")
}
characters.append(Character(scalar))
}
inEscapeSequence = false
} else if c == "\\" && escapeSequenceDelimiterPosition == 0 {
escapeSequenceDelimiterPosition += 1
} else if !inEscapeSequence && c == "#" && escapeSequenceDelimiterPosition != 0 {
escapeSequenceDelimiterPosition += 1
} else {
if escapeSequenceDelimiterPosition != 0 {
characters.append("\\")
for _ in 0..<(escapeSequenceDelimiterPosition - 1) {
characters.append("#")
}
escapeSequenceDelimiterPosition = 0
}
characters.append(c)
}
if escapeSequenceDelimiterPosition == escapeSequenceDelimiterLength {
inEscapeSequence = true
escapeSequenceDelimiterPosition = 0
}
}
return characters.map(String.init).joined(separator: "")
}
return transformedSegments.joined(separator: "")
let diagnostic = DiagnosticBuilder(for: function._syntax.funcKeyword)
.message("can only add a completion-handler variant to an 'async' function")
.messageID(domain: "AddCompletionHandlerMacro", id: "MissingAsync")
.suggestReplacement(
"add 'async'",
old: function._syntax.signature,
new: newSignature
)
.build()
let diagnostic = Diagnostic(
node: Syntax(funcDecl.funcKeyword),
message: SimpleDiagnosticMessage(
message: "can only add a completion-handler variant to an 'async' function",
diagnosticID: MessageID(domain: "AddCompletionHandlerMacro", id: "MissingAsync"),
severity: .error
),
fixIts: [
FixIt(
message: SimpleDiagnosticMessage(
message: "add 'async'",
diagnosticID: MessageID(domain: "AddCompletionHandlerMacro", id: "MissingAsync"),
severity: .error
),
changes: [
FixIt.Change.replace(
oldNode: Syntax(funcDecl.signature),
newNode: Syntax(newSignature)
)
]
),
]
)