diff --git a/Package.swift b/Package.swift index a58a6187..4fe0ddbe 100644 --- a/Package.swift +++ b/Package.swift @@ -16,6 +16,7 @@ let cmarkPackageName = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DE let package = Package( name: "swift-markdown", + platforms: [.macOS(.v10_15)], products: [ .library( name: "Markdown", diff --git a/Sources/Markdown/Base/Document.swift b/Sources/Markdown/Base/Document.swift index ab84b4db..23a0ea2c 100644 --- a/Sources/Markdown/Base/Document.swift +++ b/Sources/Markdown/Base/Document.swift @@ -8,7 +8,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ +#if canImport(Foundation) import Foundation +#endif /// A markup element representing the top level of a whole document. /// @@ -41,7 +43,7 @@ public extension Document { /// - parameter options: options for parsing Markdown text. /// - parameter source: an explicit source URL from which the input `string` came for marking source locations. /// This need not be a file URL. - init(parsing string: String, source: URL? = nil, options: ParseOptions = []) { + init(parsing string: String, source: SourceIdentifier? = nil, options: ParseOptions = []) { if options.contains(.parseBlockDirectives) { self = BlockDirectiveParser.parse(string, source: source, options: options) @@ -50,6 +52,7 @@ public extension Document { } } + #if canImport(Foundation) /// Parse a file's contents into a `Document`. /// /// - parameter file: a file URL from which to load Markdown text to parse. @@ -63,6 +66,7 @@ public extension Document { self = MarkupParser.parseString(string, source: file, options: options) } } + #endif /// Create a document from a sequence of block markup elements. init(_ children: some Sequence) { @@ -77,7 +81,9 @@ public extension Document { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitDocument(self) } + #endif } diff --git a/Sources/Markdown/Base/Markup.swift b/Sources/Markdown/Base/Markup.swift index b19bb6b3..74c23a33 100644 --- a/Sources/Markdown/Base/Markup.swift +++ b/Sources/Markdown/Base/Markup.swift @@ -89,11 +89,13 @@ func makeMarkup(_ data: _MarkupData) -> Markup { /// > Note: All supported markup elements are already implemented in the framework. /// Use this protocol only as a generic constraint. public protocol Markup { + #if !hasFeature(Embedded) /// Accept a `MarkupVisitor` and call the specific visitation method for this element. /// /// - parameter visitor: The `MarkupVisitor` visiting the element. /// - returns: The result of the visit. func accept(_ visitor: inout V) -> V.Result + #endif /// The data backing the markup element. /// > Note: This property is an implementation detail; do not use it directly. @@ -122,7 +124,7 @@ extension Markup { /// /// - Complexity: `O(1)` var subtreeCount: Int { - return raw.markup.header.subtreeCount + return raw.markup.subtreeCount } /// Return this element without ``SoftBreak`` elements, or `nil` if this @@ -178,7 +180,7 @@ extension Markup { /// /// - Complexity: `O(1)` public var childCount: Int { - return raw.markup.header.childCount + return raw.markup.childCount } /// `true` if this element has no children. diff --git a/Sources/Markdown/Base/RawMarkup.swift b/Sources/Markdown/Base/RawMarkup.swift index a4dabe20..9e803b24 100644 --- a/Sources/Markdown/Base/RawMarkup.swift +++ b/Sources/Markdown/Base/RawMarkup.swift @@ -8,7 +8,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ +#if canImport(Foundation) import Foundation +#endif /// The data specific to a kind of markup element. /// @@ -93,6 +95,54 @@ public struct RawMarkupHeader { var parsedRange: SourceRange? } +#if hasFeature(Embedded) +/// Embedded Swift variant: uses a plain array for children storage instead of +/// tail-allocated ManagedBuffer elements, avoiding the self-referential generic +/// `ManagedBuffer` that causes the compiler's +/// generic specializer to hang (https://github.com/swiftlang/swift/pull/88296). +final class RawMarkup { + var header: RawMarkupHeader + private let _children: [RawMarkup] + + fileprivate static func create(data: RawMarkupData, parsedRange: SourceRange?, children: [RawMarkup]) -> RawMarkup { + return RawMarkup( + header: RawMarkupHeader(data: data, + childCount: children.count, + subtreeCount: 1 + children.subtreeCount, + parsedRange: parsedRange), + children: children) + } + + private init(header: RawMarkupHeader, children: [RawMarkup]) { + self.header = header + self._children = children + } + + var data: RawMarkupData { header.data } + var childCount: Int { header.childCount } + var subtreeCount: Int { header.subtreeCount } + var parsedRange: SourceRange? { header.parsedRange } + + func child(at index: Int) -> RawMarkup { + precondition(index < header.childCount) + return _children[index] + } + + func copyChildren() -> [RawMarkup] { _children } + + var children: some Sequence { _children } + + enum Error: Swift.Error, CustomStringConvertible { + case concreteConversionError(from: RawMarkup, to: Markup.Type) + var description: String { + switch self { + case let .concreteConversionError(raw, to: type): + return "Can't wrap a \(raw.data) in a \(type)" + } + } + } +} +#else final class RawMarkup: ManagedBuffer { enum Error: LocalizedError { case concreteConversionError(from: RawMarkup, to: Markup.Type) @@ -103,7 +153,8 @@ final class RawMarkup: ManagedBuffer { } } } - private static func create(data: RawMarkupData, parsedRange: SourceRange?, children: [RawMarkup]) -> RawMarkup { + + fileprivate static func create(data: RawMarkupData, parsedRange: SourceRange?, children: [RawMarkup]) -> RawMarkup { let buffer = self.create(minimumCapacity: children.count) { _ in RawMarkupHeader(data: data, childCount: children.count, @@ -155,10 +206,8 @@ final class RawMarkup: ManagedBuffer { } /// The children of this element. - var children: AnySequence { - return AnySequence((0.. RawMarkup in - self.child(at: 0) - }) + var children: some Sequence { + return (0.. { $0.deinitialize(count: header.childCount) } } +} +#endif + +// MARK: - Shared API + +extension RawMarkup { // MARK: Aspects @@ -174,11 +229,11 @@ final class RawMarkup: ManagedBuffer { if self === other { return true } - guard self.header.childCount == other.header.childCount, - self.header.data == other.header.data else { + guard self.childCount == other.childCount, + self.data == other.data else { return false } - for i in 0.. { let parsedRange: SourceRange? if preserveRange { - parsedRange = header.parsedRange + parsedRange = self.parsedRange } else { - parsedRange = newChild.header.parsedRange + parsedRange = newChild.parsedRange } - return RawMarkup.create(data: header.data, parsedRange: parsedRange, children: newChildren) + return RawMarkup.create(data: data, parsedRange: parsedRange, children: newChildren) } func withChildren(_ newChildren: Children) -> RawMarkup where Children.Element == RawMarkup { - return .create(data: header.data, parsedRange: header.parsedRange, children: Array(newChildren)) + return .create(data: data, parsedRange: parsedRange, children: Array(newChildren)) } // MARK: Block Creation diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/BlockDirective.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/BlockDirective.swift index d7bd9827..8ef2de7d 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/BlockDirective.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/BlockDirective.swift @@ -174,7 +174,9 @@ public extension BlockDirective { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitBlockDirective(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/BlockQuote.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/BlockQuote.swift index a24a72fd..ef606f65 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/BlockQuote.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/BlockQuote.swift @@ -41,7 +41,9 @@ public extension BlockQuote { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitBlockQuote(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/CustomBlock.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/CustomBlock.swift index 403cf5b0..e0a297c3 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/CustomBlock.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/CustomBlock.swift @@ -41,7 +41,9 @@ public extension CustomBlock { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitCustomBlock(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift index 96679bcd..bafd69da 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenAbstract.swift @@ -8,8 +8,6 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -import Foundation - /// A parsed Doxygen `\abstract` command. /// /// The Doxygen support in Swift-Markdown parses `\abstract` commands of the form @@ -34,9 +32,11 @@ public struct DoxygenAbstract: BlockContainer { self._data = data } + #if !hasFeature(Embedded) public func accept(_ visitor: inout V) -> V.Result { return visitor.visitDoxygenAbstract(self) } + #endif } public extension DoxygenAbstract { diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift index 6f72fdc4..b7b79f34 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenDiscussion.swift @@ -8,8 +8,6 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -import Foundation - /// A parsed Doxygen `\discussion` command. /// /// The Doxygen support in Swift-Markdown parses `\discussion` commands of the form @@ -34,9 +32,11 @@ public struct DoxygenDiscussion: BlockContainer { self._data = data } + #if !hasFeature(Embedded) public func accept(_ visitor: inout V) -> V.Result { return visitor.visitDoxygenDiscussion(self) } + #endif } public extension DoxygenDiscussion { diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift index df9e5eac..efaafd9b 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenNote.swift @@ -8,8 +8,6 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -import Foundation - /// A parsed Doxygen `\note` command. /// /// The Doxygen support in Swift-Markdown parses `\note` commands of the form @@ -34,9 +32,11 @@ public struct DoxygenNote: BlockContainer { self._data = data } + #if !hasFeature(Embedded) public func accept(_ visitor: inout V) -> V.Result { return visitor.visitDoxygenNote(self) } + #endif } public extension DoxygenNote { diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift index 3f4df5a7..43016dd6 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenParameter.swift @@ -8,8 +8,6 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -import Foundation - /// A parsed Doxygen `\param` command. /// /// The Doxygen support in Swift-Markdown parses `\param` commands of the form @@ -37,9 +35,11 @@ public struct DoxygenParameter: BlockContainer { self._data = data } + #if !hasFeature(Embedded) public func accept(_ visitor: inout V) -> V.Result { return visitor.visitDoxygenParameter(self) } + #endif } public extension DoxygenParameter { diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift index 2a014b3f..b391b8c8 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/Doxygen Commands/DoxygenReturns.swift @@ -8,8 +8,6 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -import Foundation - /// A parsed Doxygen `\returns`, `\return`, or `\result` command. /// /// The Doxygen support in Swift-Markdown parses `\returns` commands of the form @@ -34,9 +32,11 @@ public struct DoxygenReturns: BlockContainer { self._data = data } + #if !hasFeature(Embedded) public func accept(_ visitor: inout V) -> V.Result { return visitor.visitDoxygenReturns(self) } + #endif } public extension DoxygenReturns { diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/ListItem.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/ListItem.swift index 57f7b71e..3fd270b8 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/ListItem.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/ListItem.swift @@ -64,7 +64,9 @@ public extension ListItem { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitListItem(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/OrderedList.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/OrderedList.swift index e52243b8..31bb006f 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/OrderedList.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/OrderedList.swift @@ -54,7 +54,9 @@ public extension OrderedList { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitOrderedList(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Block Container Blocks/UnorderedList.swift b/Sources/Markdown/Block Nodes/Block Container Blocks/UnorderedList.swift index dac9ce20..b536fed7 100644 --- a/Sources/Markdown/Block Nodes/Block Container Blocks/UnorderedList.swift +++ b/Sources/Markdown/Block Nodes/Block Container Blocks/UnorderedList.swift @@ -35,7 +35,9 @@ public extension UnorderedList { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitUnorderedList(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Inline Container Blocks/Paragraph.swift b/Sources/Markdown/Block Nodes/Inline Container Blocks/Paragraph.swift index e6614213..7ef8dbdf 100644 --- a/Sources/Markdown/Block Nodes/Inline Container Blocks/Paragraph.swift +++ b/Sources/Markdown/Block Nodes/Inline Container Blocks/Paragraph.swift @@ -41,7 +41,9 @@ public extension Paragraph { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitParagraph(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Leaf Blocks/CodeBlock.swift b/Sources/Markdown/Block Nodes/Leaf Blocks/CodeBlock.swift index 07214fb9..3136505e 100644 --- a/Sources/Markdown/Block Nodes/Leaf Blocks/CodeBlock.swift +++ b/Sources/Markdown/Block Nodes/Leaf Blocks/CodeBlock.swift @@ -60,7 +60,9 @@ public extension CodeBlock { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitCodeBlock(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Leaf Blocks/HTMLBlock.swift b/Sources/Markdown/Block Nodes/Leaf Blocks/HTMLBlock.swift index c41c79e7..135fe785 100644 --- a/Sources/Markdown/Block Nodes/Leaf Blocks/HTMLBlock.swift +++ b/Sources/Markdown/Block Nodes/Leaf Blocks/HTMLBlock.swift @@ -46,7 +46,9 @@ public extension HTMLBlock { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitHTMLBlock(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Leaf Blocks/Heading.swift b/Sources/Markdown/Block Nodes/Leaf Blocks/Heading.swift index 55fe45c3..57eb9a19 100644 --- a/Sources/Markdown/Block Nodes/Leaf Blocks/Heading.swift +++ b/Sources/Markdown/Block Nodes/Leaf Blocks/Heading.swift @@ -61,7 +61,9 @@ public extension Heading { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitHeading(self) } + #endif } diff --git a/Sources/Markdown/Block Nodes/Leaf Blocks/ThematicBreak.swift b/Sources/Markdown/Block Nodes/Leaf Blocks/ThematicBreak.swift index 8beeb491..4bcbdc8d 100644 --- a/Sources/Markdown/Block Nodes/Leaf Blocks/ThematicBreak.swift +++ b/Sources/Markdown/Block Nodes/Leaf Blocks/ThematicBreak.swift @@ -33,7 +33,9 @@ public extension ThematicBreak { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitThematicBreak(self) } + #endif } diff --git a/Sources/Markdown/Infrastructure/SourceIdentifier.swift b/Sources/Markdown/Infrastructure/SourceIdentifier.swift new file mode 100644 index 00000000..6b246fb8 --- /dev/null +++ b/Sources/Markdown/Infrastructure/SourceIdentifier.swift @@ -0,0 +1,20 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2021 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See https://swift.org/LICENSE.txt for license information + See https://swift.org/CONTRIBUTORS.txt for Swift project authors +*/ + +#if canImport(Foundation) +import Foundation +public typealias SourceIdentifier = URL +#else +/// Lightweight replacement for URL in Embedded Swift contexts. +public struct SourceIdentifier: Hashable, Sendable { + public var path: String + public init(path: String) { self.path = path } +} +#endif diff --git a/Sources/Markdown/Infrastructure/SourceLocation.swift b/Sources/Markdown/Infrastructure/SourceLocation.swift index a7de4084..64bcdb71 100644 --- a/Sources/Markdown/Infrastructure/SourceLocation.swift +++ b/Sources/Markdown/Infrastructure/SourceLocation.swift @@ -8,7 +8,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ +#if canImport(Foundation) import Foundation +#endif /// A location in a source file. public struct SourceLocation: Hashable, CustomStringConvertible, Comparable, Sendable { @@ -29,7 +31,7 @@ public struct SourceLocation: Hashable, CustomStringConvertible, Comparable, Sen public var column: Int /// The source file for which this location applies, if it came from an accessible location. - public var source: URL? + public var source: SourceIdentifier? /// Create a source location with line, column, and optional source to which the location applies. /// @@ -37,7 +39,7 @@ public struct SourceLocation: Hashable, CustomStringConvertible, Comparable, Sen /// - parameter column: The column of the location, starting with 1. /// - parameter source: The URL in which the location resides, or `nil` if there is not a specific /// file or resource that needs to be identified. - public init(line: Int, column: Int, source: URL?) { + public init(line: Int, column: Int, source: SourceIdentifier?) { self.line = line self.column = column self.source = source diff --git a/Sources/Markdown/Inline Nodes/Inline Containers/Emphasis.swift b/Sources/Markdown/Inline Nodes/Inline Containers/Emphasis.swift index 4b28f51e..813e35a1 100644 --- a/Sources/Markdown/Inline Nodes/Inline Containers/Emphasis.swift +++ b/Sources/Markdown/Inline Nodes/Inline Containers/Emphasis.swift @@ -50,7 +50,9 @@ public extension Emphasis { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitEmphasis(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Containers/Image.swift b/Sources/Markdown/Inline Nodes/Inline Containers/Image.swift index cde5bf03..3eed62ca 100644 --- a/Sources/Markdown/Inline Nodes/Inline Containers/Image.swift +++ b/Sources/Markdown/Inline Nodes/Inline Containers/Image.swift @@ -95,7 +95,9 @@ public extension Image { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitImage(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Containers/InlineAttributes.swift b/Sources/Markdown/Inline Nodes/Inline Containers/InlineAttributes.swift index 5cffe1ff..3fa178fb 100644 --- a/Sources/Markdown/Inline Nodes/Inline Containers/InlineAttributes.swift +++ b/Sources/Markdown/Inline Nodes/Inline Containers/InlineAttributes.swift @@ -53,7 +53,9 @@ public extension InlineAttributes { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitInlineAttributes(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Containers/Link.swift b/Sources/Markdown/Inline Nodes/Inline Containers/Link.swift index e1143626..03fba0ac 100644 --- a/Sources/Markdown/Inline Nodes/Inline Containers/Link.swift +++ b/Sources/Markdown/Inline Nodes/Inline Containers/Link.swift @@ -98,7 +98,9 @@ public extension Link { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitLink(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Containers/Strikethrough.swift b/Sources/Markdown/Inline Nodes/Inline Containers/Strikethrough.swift index 7d5d0b11..3ff529c9 100644 --- a/Sources/Markdown/Inline Nodes/Inline Containers/Strikethrough.swift +++ b/Sources/Markdown/Inline Nodes/Inline Containers/Strikethrough.swift @@ -49,7 +49,9 @@ public extension Strikethrough { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitStrikethrough(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Containers/Strong.swift b/Sources/Markdown/Inline Nodes/Inline Containers/Strong.swift index d72d8550..fe61bfff 100644 --- a/Sources/Markdown/Inline Nodes/Inline Containers/Strong.swift +++ b/Sources/Markdown/Inline Nodes/Inline Containers/Strong.swift @@ -49,7 +49,9 @@ public extension Strong { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitStrong(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/CustomInline.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/CustomInline.swift index 0505bb93..8ed63c2f 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/CustomInline.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/CustomInline.swift @@ -48,7 +48,9 @@ public extension CustomInline { return text } + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitCustomInline(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/InlineCode.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/InlineCode.swift index c0000131..1f2337f4 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/InlineCode.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/InlineCode.swift @@ -54,7 +54,9 @@ public extension InlineCode { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitInlineCode(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/InlineHTML.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/InlineHTML.swift index 90334649..00aa425b 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/InlineHTML.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/InlineHTML.swift @@ -52,7 +52,9 @@ public extension InlineHTML { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitInlineHTML(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/LineBreak.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/LineBreak.swift index 60b5cfcb..6f456a83 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/LineBreak.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/LineBreak.swift @@ -41,7 +41,9 @@ public extension LineBreak { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitLineBreak(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/SoftBreak.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/SoftBreak.swift index 12774bc5..62010101 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/SoftBreak.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/SoftBreak.swift @@ -41,7 +41,9 @@ public extension SoftBreak { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitSoftBreak(self) } + #endif } diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/SymbolLink.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/SymbolLink.swift index 586652e2..000e145d 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/SymbolLink.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/SymbolLink.swift @@ -59,9 +59,11 @@ public extension SymbolLink { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitSymbolLink(self) } + #endif // MARK: PlainTextConvertibleMarkup diff --git a/Sources/Markdown/Inline Nodes/Inline Leaves/Text.swift b/Sources/Markdown/Inline Nodes/Inline Leaves/Text.swift index 7b90a660..f8a4b6c8 100644 --- a/Sources/Markdown/Inline Nodes/Inline Leaves/Text.swift +++ b/Sources/Markdown/Inline Nodes/Inline Leaves/Text.swift @@ -53,7 +53,9 @@ public extension Text { // MARK: Visitation + #if !hasFeature(Embedded) func accept(_ visitor: inout V) -> V.Result { return visitor.visitText(self) } + #endif } diff --git a/Sources/Markdown/Interpretive Nodes/Aside.swift b/Sources/Markdown/Interpretive Nodes/Aside.swift index a01a7a05..92e12a9a 100644 --- a/Sources/Markdown/Interpretive Nodes/Aside.swift +++ b/Sources/Markdown/Interpretive Nodes/Aside.swift @@ -8,8 +8,6 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ -import Foundation - /// An auxiliary aside element interpreted from a block quote. /// /// Asides are written as a block quote starting with a special plain-text tag, diff --git a/Sources/Markdown/Parser/BlockDirectiveParser.swift b/Sources/Markdown/Parser/BlockDirectiveParser.swift index c22f003b..c35c1189 100644 --- a/Sources/Markdown/Parser/BlockDirectiveParser.swift +++ b/Sources/Markdown/Parser/BlockDirectiveParser.swift @@ -8,7 +8,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ +#if canImport(Foundation) import Foundation +#endif struct PendingBlockDirective { enum ParseState { @@ -299,7 +301,7 @@ struct TrimmedLine { /// The source file or resource from which the line came, /// or `nil` if no such file or resource can be identified. - let source: URL? + let source: SourceIdentifier? /// `true` if this line is empty or consists of all space `" "` or tab /// `"\t"` characters. @@ -314,7 +316,7 @@ struct TrimmedLine { /// - source: The source file or resource from which the line came, or `nil` if no such file or resource can be identified. /// - lineNumber: The line number of this line in the source if known, starting with `0`. /// - parseIndex: The current index a parser is looking at on a line, or `nil` if a parser is looking at the start of the untrimmed text. - init(_ untrimmedText: Substring, source: URL?, lineNumber: Int?, parseIndex: Substring.Index? = nil) { + init(_ untrimmedText: Substring, source: SourceIdentifier?, lineNumber: Int?, parseIndex: Substring.Index? = nil) { self.untrimmedText = untrimmedText self.source = source self.parseIndex = parseIndex ?? untrimmedText.startIndex @@ -1132,7 +1134,7 @@ extension Document { /// Convert a ``ParseContainer` to a ``Document``. /// /// - Precondition: The `rootContainer` must be the `.root` case. - fileprivate init(converting rootContainer: ParseContainer, from source: URL?, + fileprivate init(converting rootContainer: ParseContainer, from source: SourceIdentifier?, options: ParseOptions) { guard case .root = rootContainer else { fatalError("Tried to convert a non-root container to a `Document`") @@ -1156,13 +1158,15 @@ extension Document { } struct BlockDirectiveParser { + #if canImport(Foundation) static func parse(_ input: URL, options: ParseOptions = []) throws -> Document { let string = try String(contentsOf: input, encoding: .utf8) return parse(string, source: input, options: options) } + #endif /// Parse the input. - static func parse(_ input: String, source: URL?, + static func parse(_ input: String, source: SourceIdentifier?, options: ParseOptions = []) -> Document { // Phase 0: Split the input into lines lazily, keeping track of // line numbers, consecutive blank lines, and start positions on each line where indentation ends. diff --git a/Sources/Markdown/Parser/CommonMarkConverter.swift b/Sources/Markdown/Parser/CommonMarkConverter.swift index 510bbfda..d0152e38 100644 --- a/Sources/Markdown/Parser/CommonMarkConverter.swift +++ b/Sources/Markdown/Parser/CommonMarkConverter.swift @@ -10,7 +10,9 @@ import cmark_gfm import cmark_gfm_extensions +#if canImport(Foundation) import Foundation +#endif /// String-based CommonMark node type identifiers. /// @@ -82,7 +84,7 @@ fileprivate struct MarkupConverterState { var range: SourceRange? } /// The original source whose conversion created this state. - let source: URL? + let source: SourceIdentifier? /// An opaque pointer to a `cmark_iter` used during parsing. let iterator: UnsafeMutablePointer? @@ -99,7 +101,7 @@ fileprivate struct MarkupConverterState { private(set) var headerSeen: Bool private(set) var pendingTableBody: PendingTableBody? - init(source: URL?, iterator: UnsafeMutablePointer?, event: cmark_event_type, node: UnsafeMutablePointer?, options: ParseOptions, headerSeen: Bool, pendingTableBody: PendingTableBody?) { + init(source: SourceIdentifier?, iterator: UnsafeMutablePointer?, event: cmark_event_type, node: UnsafeMutablePointer?, options: ParseOptions, headerSeen: Bool, pendingTableBody: PendingTableBody?) { self.source = source self.iterator = iterator self.event = event @@ -608,7 +610,7 @@ struct MarkupParser { return MarkupConversion(state: childConversion.state.next(), result: .inlineAttributes(attributes: attributes, parsedRange: parsedRange, childConversion.result)) } - static func parseString(_ string: String, source: URL?, options: ParseOptions) -> Document { + static func parseString(_ string: String, source: SourceIdentifier?, options: ParseOptions) -> Document { cmark_gfm_core_extensions_ensure_registered() var cmarkOptions = CMARK_OPT_TABLE_SPANS diff --git a/Sources/Markdown/Parser/LazySplitLines.swift b/Sources/Markdown/Parser/LazySplitLines.swift index 398cda89..f353cf66 100644 --- a/Sources/Markdown/Parser/LazySplitLines.swift +++ b/Sources/Markdown/Parser/LazySplitLines.swift @@ -8,7 +8,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ +#if canImport(Foundation) import Foundation +#endif /// A lazy sequence of split lines that keeps track of initial indentation and /// consecutive runs of empty lines. @@ -25,9 +27,9 @@ struct LazySplitLines: Sequence { /// The source file or resource from which the line came, /// or `nil` if no such file or resource can be identified. - private var source: URL? + private var source: SourceIdentifier? - init(_ input: S, source: URL?) where S.SubSequence == Substring { + init(_ input: S, source: SourceIdentifier?) where S.SubSequence == Substring { self.rawLines = input.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline) self.index = rawLines.startIndex self.source = source @@ -57,9 +59,9 @@ struct LazySplitLines: Sequence { /// The source file or resource from which the line came, /// or `nil` if no such file or resource can be identified. - private let source: URL? + private let source: SourceIdentifier? - init(_ input: Substring, source: URL?) { + init(_ input: Substring, source: SourceIdentifier?) { self.input = input self.source = source } diff --git a/Sources/Markdown/Parser/RangeAdjuster.swift b/Sources/Markdown/Parser/RangeAdjuster.swift index 88ab0e58..00190521 100644 --- a/Sources/Markdown/Parser/RangeAdjuster.swift +++ b/Sources/Markdown/Parser/RangeAdjuster.swift @@ -43,7 +43,11 @@ struct RangeAdjuster: MarkupWalker { markup.raw.markup.header.parsedRange = adjustedRange for child in markup.children { + #if hasFeature(Embedded) + visit(child) + #else child.accept(&self) + #endif } // End unsafe stuff. diff --git a/Sources/Markdown/Visitor/MarkupVisitor.swift b/Sources/Markdown/Visitor/MarkupVisitor.swift index 9c3e38a7..df2bba85 100644 --- a/Sources/Markdown/Visitor/MarkupVisitor.swift +++ b/Sources/Markdown/Visitor/MarkupVisitor.swift @@ -317,12 +317,56 @@ public protocol MarkupVisitor { } extension MarkupVisitor { + #if hasFeature(Embedded) + // Embedded Swift cannot call generic methods on existentials, so we use + // enum-based dispatch instead of the accept virtual dispatch pattern. + public mutating func visit(_ markup: Markup) -> Result { + switch markup._data.raw.markup.data { + case .blockQuote: return visitBlockQuote(BlockQuote(markup._data)) + case .codeBlock: return visitCodeBlock(CodeBlock(markup._data)) + case .customBlock: return visitCustomBlock(CustomBlock(markup._data)) + case .document: return visitDocument(Document(markup._data)) + case .heading: return visitHeading(Heading(markup._data)) + case .thematicBreak: return visitThematicBreak(ThematicBreak(markup._data)) + case .htmlBlock: return visitHTMLBlock(HTMLBlock(markup._data)) + case .listItem: return visitListItem(ListItem(markup._data)) + case .orderedList: return visitOrderedList(OrderedList(markup._data)) + case .unorderedList: return visitUnorderedList(UnorderedList(markup._data)) + case .paragraph: return visitParagraph(Paragraph(markup._data)) + case .blockDirective: return visitBlockDirective(BlockDirective(markup._data)) + case .inlineCode: return visitInlineCode(InlineCode(markup._data)) + case .customInline: return visitCustomInline(CustomInline(markup._data)) + case .emphasis: return visitEmphasis(Emphasis(markup._data)) + case .image: return visitImage(Image(markup._data)) + case .inlineHTML: return visitInlineHTML(InlineHTML(markup._data)) + case .lineBreak: return visitLineBreak(LineBreak(markup._data)) + case .link: return visitLink(Link(markup._data)) + case .softBreak: return visitSoftBreak(SoftBreak(markup._data)) + case .strong: return visitStrong(Strong(markup._data)) + case .text: return visitText(Text(markup._data)) + case .symbolLink: return visitSymbolLink(SymbolLink(markup._data)) + case .inlineAttributes: return visitInlineAttributes(InlineAttributes(markup._data)) + case .strikethrough: return visitStrikethrough(Strikethrough(markup._data)) + case .table: return visitTable(Table(markup._data)) + case .tableHead: return visitTableHead(Table.Head(markup._data)) + case .tableBody: return visitTableBody(Table.Body(markup._data)) + case .tableRow: return visitTableRow(Table.Row(markup._data)) + case .tableCell: return visitTableCell(Table.Cell(markup._data)) + case .doxygenDiscussion: return visitDoxygenDiscussion(DoxygenDiscussion(markup._data)) + case .doxygenNote: return visitDoxygenNote(DoxygenNote(markup._data)) + case .doxygenAbstract: return visitDoxygenAbstract(DoxygenAbstract(markup._data)) + case .doxygenParam: return visitDoxygenParameter(DoxygenParameter(markup._data)) + case .doxygenReturns: return visitDoxygenReturns(DoxygenReturns(markup._data)) + } + } + #else // Default implementation: call `accept` on the markup element, // dispatching into each leaf element's implementation, which then // dispatches to the correct visit___ method. public mutating func visit(_ markup: Markup) -> Result { return markup.accept(&self) } + #endif public mutating func visitBlockQuote(_ blockQuote: BlockQuote) -> Result { return defaultVisit(blockQuote) } diff --git a/Sources/Markdown/Walker/Walkers/HTMLFormatter.swift b/Sources/Markdown/Walker/Walkers/HTMLFormatter.swift index ab047c94..25c1f372 100644 --- a/Sources/Markdown/Walker/Walkers/HTMLFormatter.swift +++ b/Sources/Markdown/Walker/Walkers/HTMLFormatter.swift @@ -8,7 +8,23 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors */ +#if canImport(Foundation) import Foundation +#endif + +fileprivate extension String { + func _replacingOccurrences(of target: Character, with replacement: String) -> String { + var result = "" + for char in self { + if char == target { + result += replacement + } else { + result.append(char) + } + } + return result + } +} /// Options given to the ``HTMLFormatter``. public struct HTMLFormatterOptions: OptionSet { @@ -286,8 +302,9 @@ public struct HTMLFormatter: MarkupWalker { } public mutating func visitInlineAttributes(_ attributes: InlineAttributes) -> () { - result += " String { + guard let start = firstIndex(where: { $0 != " " && $0 != "\t" }), + let end = lastIndex(where: { $0 != " " && $0 != "\t" }) else { return "" } + return String(self[start...end]) } } @@ -275,7 +290,7 @@ struct MarkupTreeDumper: MarkupWalker { if tableCell.rowspan != 1 { desc += " rowspan: \(tableCell.rowspan)" } - desc = desc.trimmingCharacters(in: .whitespaces) + desc = desc._trimmingWhitespace() if !desc.isEmpty { dump(tableCell, customDescription: desc) } else {