A string literal split with ~ ("a" ~ "b") is the single literal "ab",
the way long values and namespace URIs are wrapped across lines. The fold
applies wherever a string literal is read: value literals and the URIs of
namespace/datatypes/include/external declarations.
123 lines
4.1 KiB
Swift
123 lines
4.1 KiB
Swift
/// Name-class and token-stream helpers for ``RNCParser``. Kept in a separate
|
|
/// file so the parser proper stays within the type/file length budget.
|
|
extension RNCParser {
|
|
// MARK: Name classes
|
|
|
|
func parseNameClass(attribute: Bool) -> NameClass {
|
|
var nameClass = parseSimpleNameClass(attribute: attribute)
|
|
while peek() == .symbol("|") {
|
|
advance()
|
|
nameClass = .choice(nameClass, parseSimpleNameClass(attribute: attribute))
|
|
}
|
|
return nameClass
|
|
}
|
|
|
|
private func parseSimpleNameClass(attribute: Bool) -> NameClass {
|
|
let base = parseNameClassPrimary(attribute: attribute)
|
|
// `*` and `ns:*` may be narrowed by `- nameClass` subtraction.
|
|
guard peek() == .symbol("-") else { return base }
|
|
advance()
|
|
return subtract(base, parseSimpleNameClass(attribute: attribute))
|
|
}
|
|
|
|
private func parseNameClassPrimary(attribute: Bool) -> NameClass {
|
|
switch peek() {
|
|
case .symbol("*"):
|
|
advance()
|
|
return .anyName
|
|
case .symbol("("):
|
|
advance()
|
|
let nameClass = parseNameClass(attribute: attribute)
|
|
expectSymbol(")")
|
|
return nameClass
|
|
// `prefix:*` lexes as the word `prefix:` (a `:` is a name character)
|
|
// followed by a `*` symbol: a namespace wildcard.
|
|
case let .word(word) where word.hasSuffix(":") && peek(1) == .symbol("*"):
|
|
advance()
|
|
advance()
|
|
return .nsName(namespaces[String(word.dropLast())] ?? "")
|
|
case let .word(word):
|
|
advance()
|
|
return qualifiedName(word, attribute: attribute)
|
|
default:
|
|
advance()
|
|
return .anyName
|
|
}
|
|
}
|
|
|
|
/// Applies name-class subtraction, which RELAX NG defines only for the open
|
|
/// classes `anyName` (`*`) and `nsName` (`prefix:*`).
|
|
private func subtract(_ base: NameClass, _ except: NameClass) -> NameClass {
|
|
switch base {
|
|
case .anyName: .anyNameExcept(except)
|
|
case let .nsName(namespace): .nsNameExcept(namespace: namespace, except: except)
|
|
default: base
|
|
}
|
|
}
|
|
|
|
private func qualifiedName(_ word: String, attribute: Bool) -> NameClass {
|
|
let parts = word.split(separator: ":", maxSplits: 1).map(String.init)
|
|
if parts.count == 2 {
|
|
if parts[1] == "*" { return .nsName(namespaces[parts[0]] ?? "") }
|
|
return .name(namespace: namespaces[parts[0]] ?? "", localName: parts[1])
|
|
}
|
|
return .name(namespace: attribute ? "" : defaultNamespace, localName: word)
|
|
}
|
|
|
|
// MARK: Token helpers
|
|
|
|
func peek(_ offset: Int = 0) -> PureXML.Schema.RNCToken? {
|
|
let index = position + offset
|
|
return index < tokens.count ? tokens[index] : nil
|
|
}
|
|
|
|
@discardableResult
|
|
func advance() -> PureXML.Schema.RNCToken? {
|
|
defer { position += 1 }
|
|
return peek()
|
|
}
|
|
|
|
func wordValue() -> String {
|
|
if case let .word(word) = peek() { advance()
|
|
return word
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func expectAssignment() -> String {
|
|
if case let .symbol(symbol) = peek(), symbol == "=" || symbol == "|=" || symbol == "&=" {
|
|
advance()
|
|
return symbol
|
|
}
|
|
return "="
|
|
}
|
|
|
|
func expectSymbol(_ symbol: String) {
|
|
if peek() == .symbol(symbol) { advance() }
|
|
}
|
|
|
|
func expectString() -> String {
|
|
if case let .string(value) = peek() { advance()
|
|
return concatenated(value)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
/// Folds RNC string concatenation: `"a" ~ "b"` is the single literal `"ab"`,
|
|
/// the way long strings are split across lines. The leading string has been
|
|
/// consumed; this appends each following `~ "…"`.
|
|
func concatenated(_ value: String) -> String {
|
|
var result = value
|
|
while peek() == .symbol("~"), case let .string(next)? = peek(1) {
|
|
advance()
|
|
advance()
|
|
result += next
|
|
}
|
|
return result
|
|
}
|
|
|
|
func strip(_ qualified: String) -> String {
|
|
qualified.split(separator: ":").last.map(String.init) ?? qualified
|
|
}
|
|
}
|